diff --git a/.eslintignore b/.eslintignore index dda0884b381..f186c7ecd78 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,7 +3,6 @@ **/vs/css.build.js **/vs/css.js **/vs/loader.js -**/promise-polyfill/** **/insane/** **/marked/** **/test/**/*.js diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 4b385f04183..acb1cb3d9c6 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,6 +2,7 @@ name: Bug report about: Create a report to help us improve --- + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 2dc1460b16f..b9c6c83caa3 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,6 +4,7 @@ about: Suggest an idea for this project --- + diff --git a/.github/commands.json b/.github/commands.json new file mode 100644 index 00000000000..a4e1155e2a8 --- /dev/null +++ b/.github/commands.json @@ -0,0 +1,335 @@ +[ + { + "type": "comment", + "name": "question", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "updateLabels", + "addLabel": "*question" + }, + { + "type": "label", + "name": "*question", + "action": "close", + "comment": "Please ask your question on [StackOverflow](https://aka.ms/vscodestackoverflow). We have a great community over [there](https://aka.ms/vscodestackoverflow). They have already answered thousands of questions and are happy to answer yours as well. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" + }, + { + "type": "label", + "name": "*dev-question", + "action": "close", + "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", + "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](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" + }, + { + "type": "label", + "name": "*not-reproducible", + "action": "close", + "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](https://aka.ms/vscodeissuereporting) guidelines might help you with that.\n\nHappy Coding!" + }, + { + "type": "label", + "name": "*out-of-scope", + "action": "close", + "comment": "We closed this issue because we don't plan to address it in the foreseeable future. You can find more detailed information about our decision-making process [here](https://aka.ms/vscode-out-of-scope). 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](https://aka.ms/vscodeissuereporting) guidelines.\n\nThanks for your understanding and happy coding!" + }, + { + "type": "comment", + "name": "causedByExtension", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "updateLabels", + "addLabel": "*caused-by-extension" + }, + { + "type": "label", + "name": "*caused-by-extension", + "action": "close", + "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](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" + }, + { + "type": "label", + "name": "*as-designed", + "action": "close", + "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](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" + }, + { + "type": "label", + "name": "*english-please", + "action": "close", + "comment": "This issue is being closed because its description is not in English, that makes it hard for us to work on it. Please open a new issue with an English description. You might find [Bing Translator](https://www.bing.com/translator) useful." + }, + { + "type": "comment", + "name": "duplicate", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "updateLabels", + "addLabel": "*duplicate" + }, + { + "type": "label", + "name": "*duplicate", + "action": "close", + "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 existing issues [here](https://aka.ms/vscodeissuesearch). See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" + }, + { + "type": "comment", + "name": "verified", + "allowUsers": [ + "@author" + ], + "action": "updateLabels", + "addLabel": "author-verified", + "removeLabel": "author-verification-requested", + "requireLabel": "author-verification-requested", + "disallowLabel": "awaiting-insiders-release" + }, + { + "type": "comment", + "name": "confirm", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "updateLabels", + "addLabel": "confirmed", + "removeLabel": "confirmation-pending" + }, + { + "type": "comment", + "name": "confirmationPending", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "updateLabels", + "addLabel": "confirmation-pending", + "removeLabel": "confirmed" + }, + { + "type": "comment", + "name": "needsMoreInfo", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "updateLabels", + "addLabel": "~needs more info" + }, + { + "type": "label", + "name": "~needs more info", + "action": "updateLabels", + "addLabel": "needs more info", + "removeLabel": "~needs more info", + "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](https://aka.ms/vscodeissuereporting) guidelines. Please take the time to review these and update the issue.\n\nHappy Coding!" + }, + { + "type": "label", + "name": "~needs version info", + "action": "updateLabels", + "addLabel": "needs more info", + "removeLabel": "~needs version info", + "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](https://aka.ms/vscodeissuereporting) guidelines. Please take the time to review these and update the issue.\n\nHappy Coding!" + }, + { + "type": "comment", + "name": "a11ymas", + "allowUsers": [ + "AccessibilityTestingTeam-TCS", + "dixitsonali95", + "Mohini78", + "ChitrarupaSharma", + "mspatil110", + "umasarath52", + "v-umnaik" + ], + "action": "updateLabels", + "addLabel": "a11ymas" + }, + { + "type": "label", + "name": "*off-topic", + "action": "close", + "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](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" + }, + { + "type": "comment", + "name": "extPython", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "close", + "addLabel": "*caused-by-extension", + "comment": "It looks like this is caused by the Python extension. Please file it with the repository [here](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](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + }, + { + "type": "comment", + "name": "extC", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "close", + "addLabel": "*caused-by-extension", + "comment": "It looks like this is caused by the C extension. Please file it with the repository [here](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](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + }, + { + "type": "comment", + "name": "extC++", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "close", + "addLabel": "*caused-by-extension", + "comment": "It looks like this is caused by the C++ extension. Please file it with the repository [here](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](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + }, + { + "type": "comment", + "name": "extTS", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "close", + "addLabel": "*caused-by-extension", + "comment": "It looks like this is caused by the TypeScript language service. Please file it with the repository [here](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](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + }, + { + "type": "comment", + "name": "extJS", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "close", + "addLabel": "*caused-by-extension", + "comment": "It looks like this is caused by the TypeScript/JavaScript language service. Please file it with the repository [here](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](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + }, + { + "type": "comment", + "name": "extC#", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "close", + "addLabel": "*caused-by-extension", + "comment": "It looks like this is caused by the C# extension. Please file it with the repository [here](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](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + }, + { + "type": "comment", + "name": "extGo", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "close", + "addLabel": "*caused-by-extension", + "comment": "It looks like this is caused by the Go extension. Please file it with the repository [here](https://github.com/microsoft/vscode-go). Make sure to check their [contributing guidelines](https://github.com/microsoft/vscode-go/blob/master/CONTRIBUTING.md) and provide relevant information such as the extension version you're using. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + }, + { + "type": "comment", + "name": "extPowershell", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "close", + "addLabel": "*caused-by-extension", + "comment": "It looks like this is caused by the Powershell extension. Please file it with the repository [here](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](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + }, + { + "type": "comment", + "name": "extLiveShare", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "close", + "addLabel": "*caused-by-extension", + "comment": "It looks like this is caused by the LiveShare extension. Please file it with the repository [here](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](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + }, + { + "type": "comment", + "name": "extDocker", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "close", + "addLabel": "*caused-by-extension", + "comment": "It looks like this is caused by the Docker extension. Please file it with the repository [here](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](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + }, + { + "type": "comment", + "name": "extJava", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "close", + "addLabel": "*caused-by-extension", + "comment": "It looks like this is caused by the Java extension. Please file it with the repository [here](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](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + }, + { + "type": "comment", + "name": "extJavaDebug", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "close", + "addLabel": "*caused-by-extension", + "comment": "It looks like this is caused by the Java Debugger extension. Please file it with the repository [here](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](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!" + } +] diff --git a/.github/commands.yml b/.github/commands.yml index 24ac951d6f0..64fdf683bfe 100644 --- a/.github/commands.yml +++ b/.github/commands.yml @@ -1,141 +1,12 @@ { perform: true, commands: [ - { - type: 'comment', - name: 'question', - allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], - action: 'updateLabels', - addLabel: '*question' - }, - { - type: 'label', - name: '*question', - allowTriggerByBot: true, - action: 'close', - comment: "Please ask your question on [StackOverflow](https://aka.ms/vscodestackoverflow). We have a great community over [there](https://aka.ms/vscodestackoverflow). They have already answered thousands of questions and are happy to answer yours as well. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" - }, - { - type: 'label', - name: '*dev-question', - allowTriggerByBot: true, - action: 'close', - 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', - allowTriggerByBot: true, - action: 'close', - 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](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" - }, - { - type: 'label', - name: '*not-reproducible', - allowTriggerByBot: true, - action: 'close', - 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](https://aka.ms/vscodeissuereporting) guidelines might help you with that.\n\nHappy Coding!" - }, - { - type: 'label', - name: '*out-of-scope', - allowTriggerByBot: true, - action: 'close', - comment: "We closed this issue because we don't plan to address it in the foreseeable future. You can find more detailed information about our decision-making process [here](https://aka.ms/vscode-out-of-scope). 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](https://aka.ms/vscodeissuereporting) guidelines.\n\nThanks for your understanding and happy coding!" - }, - { - type: 'comment', - name: 'causedByExtension', - allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], - action: 'updateLabels', - addLabel: '*caused-by-extension' - }, - { - type: 'label', - name: '*caused-by-extension', - allowTriggerByBot: true, - action: 'close', - 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](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" - }, - { - type: 'label', - name: '*as-designed', - allowTriggerByBot: true, - action: 'close', - 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](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" - }, - { - type: 'label', - name: '*english-please', - allowTriggerByBot: true, - action: 'close', - comment: "This issue is being closed because its description is not in English, that makes it hard for us to work on it. Please open a new issue with an English description. You might find [Bing Translator](https://www.bing.com/translator) useful." - }, - { - type: 'comment', - name: 'duplicate', - allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], - action: 'updateLabels', - addLabel: '*duplicate' - }, - { - type: 'label', - name: '*duplicate', - allowTriggerByBot: true, - action: 'close', - 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 existing issues [here](https://aka.ms/vscodeissuesearch). See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" - }, - { - type: 'comment', - name: 'confirm', - allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], - action: 'updateLabels', - addLabel: 'confirmed', - removeLabel: 'confirmation-pending' - }, - { - type: 'comment', - name: 'confirmationPending', - allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], - action: 'updateLabels', - addLabel: 'confirmation-pending', - removeLabel: 'confirmed' - }, { type: 'comment', name: 'findDuplicates', allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], action: 'comment', comment: "Potential duplicates:\n${potentialDuplicates}" - }, - { - type: 'comment', - name: 'needsMoreInfo', - allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], - action: 'updateLabels', - addLabel: 'needs more info', - 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](https://aka.ms/vscodeissuereporting) guidelines. Please take the time to review these and update the issue.\n\nHappy Coding!" - }, - { - type: 'label', - name: '~needs more info', - action: 'updateLabels', - addLabel: 'needs more info', - removeLabel: '~needs more info', - 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](https://aka.ms/vscodeissuereporting) guidelines. Please take the time to review these and update the issue.\n\nHappy Coding!" - }, - { - type: 'comment', - name: 'a11ymas', - allowUsers: ['AccessibilityTestingTeam-TCS', 'dixitsonali95', 'Mohini78', 'ChitrarupaSharma', 'mspatil110', 'umasarath52', 'v-umnaik'], - action: 'updateLabels', - addLabel: 'a11ymas' - }, - { - type: 'label', - name: '*off-topic', - action: 'close', - 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](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" } ] } diff --git a/.github/copycat.yml b/.github/copycat.yml deleted file mode 100644 index 690c803bd0a..00000000000 --- a/.github/copycat.yml +++ /dev/null @@ -1,5 +0,0 @@ -{ - perform: true, - target_owner: 'chrmarti', - target_repo: 'testissues' -} diff --git a/.github/disabled_workflows/needs-more-info-labeler.yml b/.github/disabled_workflows/needs-more-info-labeler.yml new file mode 100644 index 00000000000..a58bc2af768 --- /dev/null +++ b/.github/disabled_workflows/needs-more-info-labeler.yml @@ -0,0 +1,23 @@ +name: Needs More Info Labeler +on: + issues: + types: [opened, edited] + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + uses: actions/checkout@v2 + with: + repository: 'JacksonKearl/vscode-triage-github-actions' + ref: v21 + - name: Run Needs More Info Labeler + uses: ./needs-more-info-labeler + with: + token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} + matcher: '\b(\d\.\d{2,3}\.\d|insiders?|1\.\d\d\d?)\b' + tags: feature_request_template + bots: vscodebot|vscode-triage-bot + comment: "Thanks for creating this issue! We detected it's missing some basic information, such as your VS Code and operating system versions, or in some other way doesn't follow our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines. Please take the time to review these guidelines and update the issue.\n\nThis comment will go away once sufficient information is included in your issue.\n\nHappy Coding!" + label: needs more info diff --git a/.github/feature-requests.yml b/.github/feature-requests.yml deleted file mode 100644 index 18055b84486..00000000000 --- a/.github/feature-requests.yml +++ /dev/null @@ -1,34 +0,0 @@ -{ - typeLabel: { - name: 'feature-request' - }, - candidateMilestone: { - number: 107, - name: 'Backlog Candidates' - }, - approvedMilestone: { - number: 8, - name: 'Backlog' - }, - onLabeled: { - delay: 60, - perform: true - }, - onCandidateMilestoned: { - candidatesComment: "This feature request is now a candidate for our backlog. The community has 60 days to upvote the issue. If it receives 20 upvotes we will move it to our backlog. If not, we will close it. To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding!", - perform: true - }, - onMonitorUpvotes: { - upvoteThreshold: 20, - acceptanceComment: ":slightly_smiling_face: This feature request received a sufficient number of community upvotes and we moved it to our backlog. To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding!", - perform: true - }, - onMonitorDaysOnCandidateMilestone: { - daysOnMilestone: 60, - warningPeriod: 10, - numberOfCommentsToPreventAutomaticRejection: 20, - rejectionComment: ":slightly_frowning_face: In the last 60 days, this feature request has received less than 20 community upvotes and we closed it. Still a big Thank You to you for taking the time to create this issue! To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding!", - warningComment: "This feature request has not yet received the 20 community upvotes it takes to make to our backlog. 10 days to go. To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding", - perform: true - } -} diff --git a/.github/locker.yml b/.github/locker.yml deleted file mode 100644 index 6d8feccae14..00000000000 --- a/.github/locker.yml +++ /dev/null @@ -1,6 +0,0 @@ -{ - daysAfterClose: 45, - daysSinceLastUpdate: 3, - ignoredLabels: ['*out-of-scope'], - perform: true -} diff --git a/.github/needs_more_info.yml b/.github/needs_more_info.yml deleted file mode 100644 index e5b2cddd880..00000000000 --- a/.github/needs_more_info.yml +++ /dev/null @@ -1,6 +0,0 @@ -{ - daysUntilClose: 7, - needsMoreInfoLabel: 'needs more info', - perform: true, - 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/new_release.yml b/.github/new_release.yml deleted file mode 100644 index 7482b60b108..00000000000 --- a/.github/new_release.yml +++ /dev/null @@ -1,6 +0,0 @@ -{ - newReleaseLabel: 'new release', - newReleaseColor: '006b75', - daysAfterRelease: 5, - perform: true -} diff --git a/.github/workflows/author-verified.yml b/.github/workflows/author-verified.yml new file mode 100644 index 00000000000..22976a62dca --- /dev/null +++ b/.github/workflows/author-verified.yml @@ -0,0 +1,32 @@ +name: Author Verified +on: + repository_dispatch: + schedule: + - cron: 20 14 * * * # 4:20pm Zurich + issues: + types: [labeled, closed] + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'author-verification-requested') + uses: actions/checkout@v2 + with: + repository: 'JacksonKearl/vscode-triage-github-actions' + ref: v21 + path: ./actions + - name: Checkout Repo + if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'author-verification-requested') + uses: actions/checkout@v2 + with: + path: ./repo + fetch-depth: 0 + - name: Run Author Verified + if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'author-verification-requested') + uses: ./actions/author-verified + with: + requestVerificationComment: "This bug has been fixed in to the latest release of [VS Code Insiders](https://code.visualstudio.com/insiders/)!\n\n@${author}, you can help us out by confirming things are working as expected in the latest Insiders release. If things look good, please leave a comment with the text `/verified` to let us know. If not, please ensure you're on version ${commit} of Insiders (today's or later - you can use `Help: About` in the command pallete to check), and leave a comment letting us know what isn't working as expected.\n\nHappy Coding!" + pendingReleaseLabel: awaiting-insiders-release + authorVerificationRequestedLabel: author-verification-requested diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml new file mode 100644 index 00000000000..41aa81c0bdd --- /dev/null +++ b/.github/workflows/commands.yml @@ -0,0 +1,21 @@ +name: Commands +on: + issue_comment: + types: [created] + issues: + types: [labeled] + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + uses: actions/checkout@v2 + with: + repository: 'JacksonKearl/vscode-triage-github-actions' + ref: v21 + - name: Run Commands + uses: ./commands + with: + token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} + config-path: commands diff --git a/.github/workflows/copycat.yml b/.github/workflows/copycat.yml new file mode 100644 index 00000000000..ce0a6c56d62 --- /dev/null +++ b/.github/workflows/copycat.yml @@ -0,0 +1,26 @@ +name: CopyCat +on: + issues: + types: [opened] + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + uses: actions/checkout@v2 + with: + repository: 'JacksonKearl/vscode-triage-github-actions' + ref: v21 + - name: Run CopyCat (JacksonKearl/testissues) + uses: ./copycat + with: + token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} + owner: JacksonKearl + repo: testissues + - name: Run CopyCat (chrmarti/testissues) + uses: ./copycat + with: + token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} + owner: chrmarti + repo: testissues diff --git a/.github/workflows/feature-request.yml b/.github/workflows/feature-request.yml new file mode 100644 index 00000000000..6e5e620dfe0 --- /dev/null +++ b/.github/workflows/feature-request.yml @@ -0,0 +1,36 @@ +name: Feature Request Manager +on: + repository_dispatch: + issues: + types: [labeled, milestoned] + schedule: + - cron: 20 2 * * * # 4:20am Zurich + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'feature-request') + uses: actions/checkout@v2 + with: + repository: 'JacksonKearl/vscode-triage-github-actions' + ref: v21 + - name: Run Feature Request Manager + if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'feature-request') + uses: ./feature-request + with: + token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} + candidateMilestoneID: 107 + candidateMilestoneName: Backlog Candidates + backlogMilestoneID: 8 + featureRequestLabel: feature-request + upvotesRequired: 20 + numCommentsOverride: 20 + initComment: "This feature request is now a candidate for our backlog. The community has 60 days to upvote the issue. If it receives 20 upvotes we will move it to our backlog. If not, we will close it. To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding!" + warnComment: "This feature request has not yet received the 20 community upvotes it takes to make to our backlog. 10 days to go. To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding" + acceptComment: ":slightly_smiling_face: This feature request received a sufficient number of community upvotes and we moved it to our backlog. To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding!" + rejectComment: ":slightly_frowning_face: In the last 60 days, this feature request has received less than 20 community upvotes and we closed it. Still a big Thank You to you for taking the time to create this issue! To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding!" + warnDays: 10 + closeDays: 60 + milestoneDelaySeconds: 60 diff --git a/.github/workflows/locker.yml b/.github/workflows/locker.yml new file mode 100644 index 00000000000..c52d657f9ce --- /dev/null +++ b/.github/workflows/locker.yml @@ -0,0 +1,27 @@ +name: Locker +on: + schedule: + - cron: 20 23 * * * # 4:20pm Redmond + repository_dispatch: + +# Note for locker: +# The query for is:unlocked will return issues that have only recently been locked +# `Recent` can be a large number of days (I've seen up to 10) +# This thus has the potential to burn through more quota than it probably ought to +# Run sparingly. + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + uses: actions/checkout@v2 + with: + repository: 'JacksonKearl/vscode-triage-github-actions' + ref: v21 + - name: Run Locker + uses: ./locker + with: + daysSinceClose: 45 + daysSinceUpdate: 3 + ignoredLabel: "*out-of-scope" diff --git a/.github/workflows/needs-more-info-closer.yml b/.github/workflows/needs-more-info-closer.yml new file mode 100644 index 00000000000..9b040e2a8cb --- /dev/null +++ b/.github/workflows/needs-more-info-closer.yml @@ -0,0 +1,21 @@ +name: Needs More Info Closer +on: + schedule: + - cron: 20 11 * * * # 4:20am Redmond + repository_dispatch: + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + uses: actions/checkout@v2 + with: + repository: 'JacksonKearl/vscode-triage-github-actions' + ref: v21 + - name: Run Needs More Info Closer + uses: ./needs-more-info-closer + with: + label: needs more info + days: 7 + comment: "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/new-release.yml b/.github/workflows/new-release.yml new file mode 100644 index 00000000000..6cef8460a12 --- /dev/null +++ b/.github/workflows/new-release.yml @@ -0,0 +1,21 @@ +name: New Release +on: + issues: + types: [opened] + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + uses: actions/checkout@v2 + with: + repository: 'JacksonKearl/vscode-triage-github-actions' + ref: v21 + - name: Run New Release + uses: ./new-release + with: + label: new release + labelColor: "006b75" + labelDescription: Issues found in a recent release of VS Code + days: 5 diff --git a/.github/workflows/test-plan-item-validator.yml b/.github/workflows/test-plan-item-validator.yml new file mode 100644 index 00000000000..0b5dbaf307b --- /dev/null +++ b/.github/workflows/test-plan-item-validator.yml @@ -0,0 +1,22 @@ +name: Test Plan Item Validator +on: + issues: + types: [edited, labeled] + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + if: contains(github.event.issue.labels.*.name, 'testplan-item') || contains(github.event.issue.labels.*.name, 'invalid-testplan-item') + uses: actions/checkout@v2 + with: + repository: 'JacksonKearl/vscode-triage-github-actions' + ref: v21 + - name: Run Test Plan Item Validator + if: contains(github.event.issue.labels.*.name, 'testplan-item') || contains(github.event.issue.labels.*.name, 'invalid-testplan-item') + uses: ./test-plan-item-validator + with: + 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/.vscode/launch.json b/.vscode/launch.json index caaed4c25a7..a9929675583 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -183,6 +183,7 @@ ], "webRoot": "${workspaceFolder}", // Settings for js-debug: + "userDataDir": false, "pauseForSourceMap": false, "outFiles": [ "${workspaceFolder}/out/**/*.js" diff --git a/.vscode/searches/es6.code-search b/.vscode/searches/es6.code-search new file mode 100644 index 00000000000..df7088e5a1d --- /dev/null +++ b/.vscode/searches/es6.code-search @@ -0,0 +1,63 @@ +# Query: @deprecated ES6 +# Flags: CaseSensitive WordMatch +# ContextLines: 2 + +9 results - 4 files + +src/vs/base/common/arrays.ts: + 401 + 402 /** + 403: * @deprecated ES6: use `Array.findIndex` + 404 */ + 405 export function firstIndex(array: ReadonlyArray, fn: (item: T) => boolean): number { + + 417 + 418 /** + 419: * @deprecated ES6: use `Array.find` + 420 */ + 421 export function first(array: ReadonlyArray, fn: (item: T) => boolean, notFoundValue: T): T; + + 560 + 561 /** + 562: * @deprecated ES6: use `Array.find` + 563 */ + 564 export function find(arr: ArrayLike, predicate: (value: T, index: number, arr: ArrayLike) => any): T | undefined { + +src/vs/base/common/map.ts: + 9 + 10 /** + 11: * @deprecated ES6: use `[...SetOrMap.values()]` + 12 */ + 13 export function values(set: Set): V[]; + + 20 + 21 /** + 22: * @deprecated ES6: use `[...map.keys()]` + 23 */ + 24 export function keys(map: Map): K[] { + +src/vs/base/common/objects.ts: + 115 + 116 /** + 117: * @deprecated ES6 + 118 */ + 119 export function assign(destination: T): T; + +src/vs/base/common/strings.ts: + 15 + 16 /** + 17: * @deprecated ES6: use `String.padStart` + 18 */ + 19 export function pad(n: number, l: number, char: string = '0'): string { + + 146 + 147 /** + 148: * @deprecated ES6: use `String.startsWith` + 149 */ + 150 export function startsWith(haystack: string, needle: string): boolean { + + 167 + 168 /** + 169: * @deprecated ES6: use `String.endsWith` + 170 */ + 171 export function endsWith(haystack: string, needle: string): boolean { diff --git a/.vscode/settings.json b/.vscode/settings.json index d56f75c41d4..2a9a8093ced 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -69,5 +69,8 @@ "msjsdiag.debugger-for-chrome": "workspace" }, "gulp.autoDetect": "off", - "files.insertFinalNewline": true + "files.insertFinalNewline": true, + "[typescript]": { + "editor.defaultFormatter": "vscode.typescript-language-features" + } } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 34703fae665..8cfe4b7be2a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,9 +51,9 @@ The built-in tool for reporting an issue, which you can access by using `Report Please include the following with each issue: -* Version of VS Code +* Version of VS Code -* Your operating system +* Your operating system * List of extensions that you have installed @@ -87,10 +87,11 @@ Once submitted, your report will go into the [issue tracking](https://github.com ## Automated Issue Management -We use a bot to help us manage issues. This bot currently: +We use GitHub Actions to help us manage issues. These Actions and their descriptions can be [viewed here](https://github.com/JacksonKearl/vscode-triage-github-actions/blob/master/README.md). Some examples of what these Actions do are: * Automatically closes any issue marked `needs-more-info` if there has been no response in the past 7 days. -* Automatically locks issues 45 days after they are closed. +* Automatically lock issues 45 days after they are closed. +* Automatically implement the VS Code [feature request pipeline](https://github.com/microsoft/vscode/wiki/Issues-Triaging#managing-feature-requests). If you believe the bot got something wrong, please open a new issue and let us know. diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index 7eaccc2a893..18299cdf169 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -28,7 +28,7 @@ This project incorporates components from the projects listed below. The origina 21. Ionic documentation version 1.2.4 (https://github.com/ionic-team/ionic-site) 22. ionide/ionide-fsgrammar (https://github.com/ionide/ionide-fsgrammar) 23. jeff-hykin/cpp-textmate-grammar version 1.12.11 (https://github.com/jeff-hykin/cpp-textmate-grammar) -24. jeff-hykin/cpp-textmate-grammar version 1.14.20 (https://github.com/jeff-hykin/cpp-textmate-grammar) +24. jeff-hykin/cpp-textmate-grammar version 1.14.15 (https://github.com/jeff-hykin/cpp-textmate-grammar) 25. js-beautify version 1.6.8 (https://github.com/beautify-web/js-beautify) 26. Jxck/assert version 1.0.0 (https://github.com/Jxck/assert) 27. language-docker (https://github.com/moby/moby) @@ -44,29 +44,28 @@ This project incorporates components from the projects listed below. The origina 37. mmims/language-batchfile version 0.7.5 (https://github.com/mmims/language-batchfile) 38. octref/language-css version 0.42.11 (https://github.com/octref/language-css) 39. PowerShell/EditorSyntax version 1.0.0 (https://github.com/PowerShell/EditorSyntax) -40. promise-polyfill version 8.0.0 (https://github.com/taylorhakes/promise-polyfill) -41. seti-ui version 0.1.0 (https://github.com/jesseweed/seti-ui) -42. shaders-tmLanguage version 0.1.0 (https://github.com/tgjones/shaders-tmLanguage) -43. textmate/asp.vb.net.tmbundle (https://github.com/textmate/asp.vb.net.tmbundle) -44. textmate/c.tmbundle (https://github.com/textmate/c.tmbundle) -45. textmate/diff.tmbundle (https://github.com/textmate/diff.tmbundle) -46. textmate/git.tmbundle (https://github.com/textmate/git.tmbundle) -47. textmate/groovy.tmbundle (https://github.com/textmate/groovy.tmbundle) -48. textmate/html.tmbundle (https://github.com/textmate/html.tmbundle) -49. textmate/ini.tmbundle (https://github.com/textmate/ini.tmbundle) -50. textmate/javascript.tmbundle (https://github.com/textmate/javascript.tmbundle) -51. textmate/lua.tmbundle (https://github.com/textmate/lua.tmbundle) -52. textmate/markdown.tmbundle (https://github.com/textmate/markdown.tmbundle) -53. textmate/perl.tmbundle (https://github.com/textmate/perl.tmbundle) -54. textmate/ruby.tmbundle (https://github.com/textmate/ruby.tmbundle) -55. textmate/yaml.tmbundle (https://github.com/textmate/yaml.tmbundle) -56. TypeScript-TmLanguage version 0.1.8 (https://github.com/Microsoft/TypeScript-TmLanguage) -57. TypeScript-TmLanguage version 1.0.0 (https://github.com/Microsoft/TypeScript-TmLanguage) -58. Unicode version 12.0.0 (http://www.unicode.org/) -59. vscode-codicons version 0.0.1 (https://github.com/microsoft/vscode-codicons) -60. vscode-logfile-highlighter version 2.6.0 (https://github.com/emilast/vscode-logfile-highlighter) -61. vscode-swift version 0.0.1 (https://github.com/owensd/vscode-swift) -62. Web Background Synchronization (https://github.com/WICG/BackgroundSync) +40. seti-ui version 0.1.0 (https://github.com/jesseweed/seti-ui) +41. shaders-tmLanguage version 0.1.0 (https://github.com/tgjones/shaders-tmLanguage) +42. textmate/asp.vb.net.tmbundle (https://github.com/textmate/asp.vb.net.tmbundle) +43. textmate/c.tmbundle (https://github.com/textmate/c.tmbundle) +44. textmate/diff.tmbundle (https://github.com/textmate/diff.tmbundle) +45. textmate/git.tmbundle (https://github.com/textmate/git.tmbundle) +46. textmate/groovy.tmbundle (https://github.com/textmate/groovy.tmbundle) +47. textmate/html.tmbundle (https://github.com/textmate/html.tmbundle) +48. textmate/ini.tmbundle (https://github.com/textmate/ini.tmbundle) +49. textmate/javascript.tmbundle (https://github.com/textmate/javascript.tmbundle) +50. textmate/lua.tmbundle (https://github.com/textmate/lua.tmbundle) +51. textmate/markdown.tmbundle (https://github.com/textmate/markdown.tmbundle) +52. textmate/perl.tmbundle (https://github.com/textmate/perl.tmbundle) +53. textmate/ruby.tmbundle (https://github.com/textmate/ruby.tmbundle) +54. textmate/yaml.tmbundle (https://github.com/textmate/yaml.tmbundle) +55. TypeScript-TmLanguage version 0.1.8 (https://github.com/Microsoft/TypeScript-TmLanguage) +56. TypeScript-TmLanguage version 1.0.0 (https://github.com/Microsoft/TypeScript-TmLanguage) +57. Unicode version 12.0.0 (http://www.unicode.org/) +58. vscode-codicons version 0.0.1 (https://github.com/microsoft/vscode-codicons) +59. vscode-logfile-highlighter version 2.6.0 (https://github.com/emilast/vscode-logfile-highlighter) +60. vscode-swift version 0.0.1 (https://github.com/owensd/vscode-swift) +61. Web Background Synchronization (https://github.com/WICG/BackgroundSync) %% atom/language-clojure NOTICES AND INFORMATION BEGIN HERE @@ -1739,33 +1738,6 @@ SOFTWARE. ========================================= END OF PowerShell/EditorSyntax NOTICES AND INFORMATION -%% promise-polyfill NOTICES AND INFORMATION BEGIN HERE -========================================= -The MIT License (MIT) - -Copyright (c) 2014 Taylor Hakes -Copyright (c) 2014 Forbes Lindesay - -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 promise-polyfill NOTICES AND INFORMATION - %% seti-ui NOTICES AND INFORMATION BEGIN HERE ========================================= Copyright (c) 2014 Jesse Weed diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index cbfcbfc50ea..d77dbbd2e0f 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -77,6 +77,23 @@ steps: yarn postinstall displayName: Run postinstall scripts condition: and(succeeded(), eq(variables['CacheRestored'], 'true')) + env: + OSS_GITHUB_ID: "a5d3c261b032765a78de" + OSS_GITHUB_SECRET: $(oss-github-client-secret) + INSIDERS_GITHUB_ID: "31f02627809389d9f111" + INSIDERS_GITHUB_SECRET: $(insiders-github-client-secret) + STABLE_GITHUB_ID: "baa8a44b5e861d918709" + STABLE_GITHUB_SECRET: $(stable-github-client-secret) + EXPLORATION_GITHUB_ID: "94e8376d3a90429aeaea" + EXPLORATION_GITHUB_SECRET: $(exploration-github-client-secret) + VSO_GITHUB_ID: "3d4be8f37a0325b5817d" + VSO_GITHUB_SECRET: $(vso-github-client-secret) + VSO_PPE_GITHUB_ID: "eabf35024dc2e891a492" + VSO_PPE_GITHUB_SECRET: $(vso-ppe-github-client-secret) + VSO_DEV_GITHUB_ID: "84383ebd8a7c5f5efc5c" + VSO_DEV_GITHUB_SECRET: $(vso-dev-github-client-secret) + GITHUB_APP_ID: "Iv1.ae51e546bef24ff1" + GITHUB_APP_SECRET: $(github-app-client-secret) - script: | set -e @@ -135,6 +152,23 @@ steps: displayName: Run integration tests (Browser) condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) +- script: | + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-darwin + APP_NAME="`ls $APP_ROOT | head -n 1`" + yarn smoketest --build "$APP_ROOT/$APP_NAME" + continueOnError: true + displayName: Run smoke tests (Electron) + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + +- script: | + set -e + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin" \ + yarn smoketest --web --headless + continueOnError: true + displayName: Run smoke tests (Browser) + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - script: | set -e security create-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain @@ -179,13 +213,6 @@ steps: zip -d $(agent.builddirectory)/VSCode-darwin.zip "*.pkg" displayName: Clean Archive -- script: | - set -e - AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ - AZURE_STORAGE_ACCESS_KEY_2="$(vscode-storage-key)" \ - node build/azure-pipelines/common/createAsset.js darwin-unnotarized archive "VSCode-darwin-$VSCODE_QUALITY.zip" $(agent.builddirectory)/VSCode-darwin.zip - displayName: Publish Unnotarized Build - - script: | APP_ROOT=$(agent.builddirectory)/VSCode-darwin APP_NAME="`ls $APP_ROOT | head -n 1`" diff --git a/build/azure-pipelines/linux/product-build-linux-multiarch.yml b/build/azure-pipelines/linux/product-build-linux-multiarch.yml index 68ae4ee8b67..066e42af3d0 100644 --- a/build/azure-pipelines/linux/product-build-linux-multiarch.yml +++ b/build/azure-pipelines/linux/product-build-linux-multiarch.yml @@ -86,6 +86,23 @@ steps: yarn postinstall displayName: Run postinstall scripts condition: and(succeeded(), eq(variables['CacheRestored'], 'true')) + env: + OSS_GITHUB_ID: "a5d3c261b032765a78de" + OSS_GITHUB_SECRET: $(oss-github-client-secret) + INSIDERS_GITHUB_ID: "31f02627809389d9f111" + INSIDERS_GITHUB_SECRET: $(insiders-github-client-secret) + STABLE_GITHUB_ID: "baa8a44b5e861d918709" + STABLE_GITHUB_SECRET: $(stable-github-client-secret) + EXPLORATION_GITHUB_ID: "94e8376d3a90429aeaea" + EXPLORATION_GITHUB_SECRET: $(exploration-github-client-secret) + VSO_GITHUB_ID: "3d4be8f37a0325b5817d" + VSO_GITHUB_SECRET: $(vso-github-client-secret) + VSO_PPE_GITHUB_ID: "eabf35024dc2e891a492" + VSO_PPE_GITHUB_SECRET: $(vso-ppe-github-client-secret) + VSO_DEV_GITHUB_ID: "84383ebd8a7c5f5efc5c" + VSO_DEV_GITHUB_SECRET: $(vso-dev-github-client-secret) + GITHUB_APP_ID: "Iv1.ae51e546bef24ff1" + GITHUB_APP_SECRET: $(github-app-client-secret) - script: | set -e diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index cbe3bf051e6..119f80cd929 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -76,6 +76,23 @@ steps: yarn postinstall displayName: Run postinstall scripts condition: and(succeeded(), eq(variables['CacheRestored'], 'true')) + env: + OSS_GITHUB_ID: "a5d3c261b032765a78de" + OSS_GITHUB_SECRET: $(oss-github-client-secret) + INSIDERS_GITHUB_ID: "31f02627809389d9f111" + INSIDERS_GITHUB_SECRET: $(insiders-github-client-secret) + STABLE_GITHUB_ID: "baa8a44b5e861d918709" + STABLE_GITHUB_SECRET: $(stable-github-client-secret) + EXPLORATION_GITHUB_ID: "94e8376d3a90429aeaea" + EXPLORATION_GITHUB_SECRET: $(exploration-github-client-secret) + VSO_GITHUB_ID: "3d4be8f37a0325b5817d" + VSO_GITHUB_SECRET: $(vso-github-client-secret) + VSO_PPE_GITHUB_ID: "eabf35024dc2e891a492" + VSO_PPE_GITHUB_SECRET: $(vso-ppe-github-client-secret) + VSO_DEV_GITHUB_ID: "84383ebd8a7c5f5efc5c" + VSO_DEV_GITHUB_SECRET: $(vso-dev-github-client-secret) + GITHUB_APP_ID: "Iv1.ae51e546bef24ff1" + GITHUB_APP_SECRET: $(github-app-client-secret) - script: | set -e diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index 48916189326..6c28724824d 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -92,6 +92,8 @@ steps: VSO_PPE_GITHUB_SECRET: $(vso-ppe-github-client-secret) VSO_DEV_GITHUB_ID: "84383ebd8a7c5f5efc5c" VSO_DEV_GITHUB_SECRET: $(vso-dev-github-client-secret) + GITHUB_APP_ID: "Iv1.ae51e546bef24ff1" + GITHUB_APP_SECRET: $(github-app-client-secret) # Mixin must run before optimize, because the CSS loader will # inline small SVGs diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 7a8a12aa280..20699bf9225 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -86,6 +86,23 @@ steps: exec { yarn postinstall } displayName: Run postinstall scripts condition: and(succeeded(), eq(variables['CacheRestored'], 'true')) + env: + OSS_GITHUB_ID: "a5d3c261b032765a78de" + OSS_GITHUB_SECRET: $(oss-github-client-secret) + INSIDERS_GITHUB_ID: "31f02627809389d9f111" + INSIDERS_GITHUB_SECRET: $(insiders-github-client-secret) + STABLE_GITHUB_ID: "baa8a44b5e861d918709" + STABLE_GITHUB_SECRET: $(stable-github-client-secret) + EXPLORATION_GITHUB_ID: "94e8376d3a90429aeaea" + EXPLORATION_GITHUB_SECRET: $(exploration-github-client-secret) + VSO_GITHUB_ID: "3d4be8f37a0325b5817d" + VSO_GITHUB_SECRET: $(vso-github-client-secret) + VSO_PPE_GITHUB_ID: "eabf35024dc2e891a492" + VSO_PPE_GITHUB_SECRET: $(vso-ppe-github-client-secret) + VSO_DEV_GITHUB_ID: "84383ebd8a7c5f5efc5c" + VSO_DEV_GITHUB_SECRET: $(vso-dev-github-client-secret) + GITHUB_APP_ID: "Iv1.ae51e546bef24ff1" + GITHUB_APP_SECRET: $(github-app-client-secret) - powershell: | . build/azure-pipelines/win32/exec.ps1 diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index bd9188f8ee1..accf81beaeb 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -1,7 +1,7 @@ [ { "name": "ms-vscode.node-debug", - "version": "1.43.2", + "version": "1.44.4", "repo": "https://github.com/Microsoft/vscode-node-debug", "metadata": { "id": "b6ded8fb-a0a0-4c1c-acbd-ab2a3bc995a6", @@ -31,7 +31,7 @@ }, { "name": "ms-vscode.references-view", - "version": "0.0.47", + "version": "0.0.50", "repo": "https://github.com/Microsoft/vscode-reference-view", "metadata": { "id": "dc489f46-520d-4556-ae85-1f9eab3c412d", @@ -46,7 +46,7 @@ }, { "name": "ms-vscode.js-debug-nightly", - "version": "2020.2.2617", + "version": "2020.3.3117", "forQualities": [ "insider" ], @@ -61,5 +61,20 @@ }, "publisherDisplayName": "Microsoft" } + }, + { + "name": "ms-vscode.js-debug-companion", + "version": "0.0.4", + "repo": "https://github.com/microsoft/vscode-js-debug-companion", + "metadata": { + "id": "99cb0b7f-7354-4278-b8da-6cc79972169d", + "publisherId": { + "publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee", + "publisherName": "ms-vscode", + "displayName": "Microsoft", + "flags": "verified" + }, + "publisherDisplayName": "Microsoft" + } } ] diff --git a/build/builtin/browser-main.js b/build/builtin/browser-main.js index a7618454656..4a7faf40eb3 100644 --- a/build/builtin/browser-main.js +++ b/build/builtin/browser-main.js @@ -6,7 +6,6 @@ const fs = require('fs'); const path = require('path'); const os = require('os'); -// @ts-ignore review const { remote } = require('electron'); const dialog = remote.dialog; diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index 9278f689ec3..21ba45a7360 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -72,13 +72,8 @@ const extractEditorSrcTask = task.define('extract-editor-src', () => { apiusages, extrausages ], - libs: [ - `lib.es5.d.ts`, - `lib.dom.d.ts`, - `lib.webworker.importscripts.d.ts` - ], shakeLevel: 2, // 0-Files, 1-InnerFile, 2-ClassMembers - importIgnorePattern: /(^vs\/css!)|(promise-polyfill\/polyfill)/, + importIgnorePattern: /(^vs\/css!)/, destRoot: path.join(root, 'out-editor-src'), redirects: [] }); @@ -131,6 +126,7 @@ const createESMSourcesAndResourcesTask = task.define('extract-editor-esm', () => }); const compileEditorESMTask = task.define('compile-editor-esm', () => { + const KEEP_PREV_ANALYSIS = false; console.log(`Launching the TS compiler at ${path.join(__dirname, '../out-editor-esm')}...`); let result; if (process.platform === 'win32') { @@ -149,41 +145,45 @@ const compileEditorESMTask = task.define('compile-editor-esm', () => { if (result.status !== 0) { console.log(`The TS Compilation failed, preparing analysis folder...`); const destPath = path.join(__dirname, '../../vscode-monaco-editor-esm-analysis'); - return util.rimraf(destPath)().then(() => { - fs.mkdirSync(destPath); - - // initialize a new repository - cp.spawnSync(`git`, [`init`], { - cwd: destPath - }); - + const keepPrevAnalysis = (KEEP_PREV_ANALYSIS && fs.existsSync(destPath)); + const cleanDestPath = (keepPrevAnalysis ? Promise.resolve() : util.rimraf(destPath)()); + return cleanDestPath.then(() => { // build a list of files to copy const files = util.rreddir(path.join(__dirname, '../out-editor-esm')); - // copy files from src - for (const file of files) { - const srcFilePath = path.join(__dirname, '../src', file); - const dstFilePath = path.join(destPath, file); - if (fs.existsSync(srcFilePath)) { - util.ensureDir(path.dirname(dstFilePath)); - const contents = fs.readFileSync(srcFilePath).toString().replace(/\r\n|\r|\n/g, '\n'); - fs.writeFileSync(dstFilePath, contents); + if (!keepPrevAnalysis) { + fs.mkdirSync(destPath); + + // initialize a new repository + cp.spawnSync(`git`, [`init`], { + cwd: destPath + }); + + // copy files from src + for (const file of files) { + const srcFilePath = path.join(__dirname, '../src', file); + const dstFilePath = path.join(destPath, file); + if (fs.existsSync(srcFilePath)) { + util.ensureDir(path.dirname(dstFilePath)); + const contents = fs.readFileSync(srcFilePath).toString().replace(/\r\n|\r|\n/g, '\n'); + fs.writeFileSync(dstFilePath, contents); + } } + + // create an initial commit to diff against + cp.spawnSync(`git`, [`add`, `.`], { + cwd: destPath + }); + + // create the commit + cp.spawnSync(`git`, [`commit`, `-m`, `"original sources"`, `--no-gpg-sign`], { + cwd: destPath + }); } - // create an initial commit to diff against - cp.spawnSync(`git`, [`add`, `.`], { - cwd: destPath - }); - - // create the commit - cp.spawnSync(`git`, [`commit`, `-m`, `"original sources"`, `--no-gpg-sign`], { - cwd: destPath - }); - - // copy files from esm + // copy files from tree shaken src for (const file of files) { - const srcFilePath = path.join(__dirname, '../out-editor-esm', file); + const srcFilePath = path.join(__dirname, '../out-editor-src', file); const dstFilePath = path.join(destPath, file); if (fs.existsSync(srcFilePath)) { util.ensureDir(path.dirname(dstFilePath)); @@ -334,6 +334,13 @@ const finalEditorResourcesTask = task.define('final-editor-resources', () => { ); }); +gulp.task('extract-editor-src', + task.series( + util.rimraf('out-editor-src'), + extractEditorSrcTask + ) +); + gulp.task('editor-distro', task.series( task.parallel( @@ -448,10 +455,8 @@ function createTscCompileTask(watch) { // e.g. src/vs/base/common/strings.ts(663,5): error TS2322: Type '1234' is not assignable to type 'string'. let fullpath = path.join(root, match[1]); let message = match[3]; - // @ts-ignore reporter(fullpath + message); } else { - // @ts-ignore reporter(str); } } diff --git a/build/gulpfile.hygiene.js b/build/gulpfile.hygiene.js index ccf965a9dc4..75c8413ae5d 100644 --- a/build/gulpfile.hygiene.js +++ b/build/gulpfile.hygiene.js @@ -114,7 +114,6 @@ const copyrightFilter = [ '!**/*.disabled', '!**/*.code-workspace', '!**/*.js.map', - '!**/promise-polyfill/polyfill.js', '!build/**/*.init', '!resources/linux/snap/snapcraft.yaml', '!resources/linux/snap/electron-launch', diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 4906bfdb1a2..fb543f3991b 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -37,10 +37,8 @@ const { compileBuildTask } = require('./gulpfile.compile'); const { compileExtensionsBuildTask } = require('./gulpfile.extensions'); const productionDependencies = deps.getProductionDependencies(path.dirname(__dirname)); -// @ts-ignore const baseModules = Object.keys(process.binding('natives')).filter(n => !/^_|\//.test(n)); const nodeModules = ['electron', 'original-fs'] - // @ts-ignore JSON checking: dependencies property is optional .concat(Object.keys(product.dependencies || {})) .concat(_.uniq(productionDependencies.map(d => d.name))) .concat(baseModules); @@ -93,7 +91,6 @@ const optimizeVSCodeTask = task.define('optimize-vscode', task.series( resources: vscodeResources, loaderConfig: common.loaderConfig(nodeModules), out: 'out-vscode', - inlineAmdImages: true, bundleInfo: undefined }) )); diff --git a/build/gulpfile.vscode.linux.js b/build/gulpfile.vscode.linux.js index 51c7002f5b1..bd8da32d7ad 100644 --- a/build/gulpfile.vscode.linux.js +++ b/build/gulpfile.vscode.linux.js @@ -91,9 +91,7 @@ function prepareDebPackage(arch) { const postinst = gulp.src('resources/linux/debian/postinst.template', { base: '.' }) .pipe(replace('@@NAME@@', product.applicationName)) .pipe(replace('@@ARCHITECTURE@@', debArch)) - // @ts-ignore JSON checking: quality is optional .pipe(replace('@@QUALITY@@', product.quality || '@@QUALITY@@')) - // @ts-ignore JSON checking: updateUrl is optional .pipe(replace('@@UPDATEURL@@', product.updateUrl || '@@UPDATEURL@@')) .pipe(rename('DEBIAN/postinst')); @@ -167,9 +165,7 @@ function prepareRpmPackage(arch) { .pipe(replace('@@RELEASE@@', linuxPackageRevision)) .pipe(replace('@@ARCHITECTURE@@', rpmArch)) .pipe(replace('@@LICENSE@@', product.licenseName)) - // @ts-ignore JSON checking: quality is optional .pipe(replace('@@QUALITY@@', product.quality || '@@QUALITY@@')) - // @ts-ignore JSON checking: updateUrl is optional .pipe(replace('@@UPDATEURL@@', product.updateUrl || '@@UPDATEURL@@')) .pipe(replace('@@DEPENDENCIES@@', rpmDependencies[rpmArch].join(', '))) .pipe(rename('SPECS/' + product.applicationName + '.spec')); diff --git a/build/lib/asar.js b/build/lib/asar.js index 21c5f65a45b..4a15a200be5 100644 --- a/build/lib/asar.js +++ b/build/lib/asar.js @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); +exports.createAsar = void 0; const path = require("path"); const es = require("event-stream"); const pickle = require('chromium-pickle-js'); diff --git a/build/lib/bundle.js b/build/lib/bundle.js index 881e8ff6c7f..7d0c8d9b55e 100644 --- a/build/lib/bundle.js +++ b/build/lib/bundle.js @@ -4,6 +4,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); +exports.bundle = void 0; const fs = require("fs"); const path = require("path"); const vm = require("vm"); diff --git a/build/lib/compilation.js b/build/lib/compilation.js index 59bf1a250f6..c4a3230424b 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); +exports.watchTask = exports.compileTask = void 0; const es = require("event-stream"); const fs = require("fs"); const gulp = require("gulp"); diff --git a/build/lib/electron.js b/build/lib/electron.js index b38a1f6edc9..abf6baab419 100644 --- a/build/lib/electron.js +++ b/build/lib/electron.js @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); +exports.config = exports.getElectronVersion = void 0; const fs = require("fs"); const path = require("path"); const vfs = require("vinyl-fs"); diff --git a/build/lib/eslint/utils.js b/build/lib/eslint/utils.js index ec59aef3b7d..c58e4e24be1 100644 --- a/build/lib/eslint/utils.js +++ b/build/lib/eslint/utils.js @@ -4,6 +4,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); +exports.createImportRuleListener = void 0; function createImportRuleListener(validateImport) { function _checkImport(node) { if (node && node.type === 'Literal' && typeof node.value === 'string') { diff --git a/build/lib/extensions.js b/build/lib/extensions.js index e45b0d4e35a..1e050a3a3d5 100644 --- a/build/lib/extensions.js +++ b/build/lib/extensions.js @@ -4,6 +4,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); +exports.packageMarketplaceExtensionsStream = exports.packageLocalExtensionsStream = exports.fromMarketplace = void 0; const es = require("event-stream"); const fs = require("fs"); const glob = require("glob"); @@ -181,6 +182,7 @@ function fromMarketplace(extensionName, version, metadata) { exports.fromMarketplace = fromMarketplace; const excludedExtensions = [ 'vscode-api-tests', + 'vscode-web-playground', 'vscode-colorize-tests', 'vscode-test-resolver', 'ms-vscode.node-debug', diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index 9c90cb60c47..f7d59264ae3 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -216,6 +216,7 @@ export function fromMarketplace(extensionName: string, version: string, metadata const excludedExtensions = [ 'vscode-api-tests', + 'vscode-web-playground', 'vscode-colorize-tests', 'vscode-test-resolver', 'ms-vscode.node-debug', diff --git a/build/lib/git.js b/build/lib/git.js index da5d66fd8d2..1726f76fcc7 100644 --- a/build/lib/git.js +++ b/build/lib/git.js @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ '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/i18n.js b/build/lib/i18n.js index 27a4054a1e4..2e7415cd721 100644 --- a/build/lib/i18n.js +++ b/build/lib/i18n.js @@ -4,6 +4,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); +exports.prepareIslFiles = exports.prepareI18nPackFiles = exports.pullI18nPackFiles = exports.prepareI18nFiles = exports.pullSetupXlfFiles = exports.pullCoreAndExtensionsXlfFiles = exports.findObsoleteResources = exports.pushXlfFiles = exports.createXlfFilesForIsl = exports.createXlfFilesForExtensions = exports.createXlfFilesForCoreBundle = exports.getResource = exports.processNlsFiles = exports.Limiter = exports.XLF = exports.Line = exports.externalExtensionsWithTranslations = exports.extraLanguages = exports.defaultLanguages = void 0; const path = require("path"); const fs = require("fs"); const event_stream_1 = require("event-stream"); @@ -100,155 +101,161 @@ class TextModel { return this._lines; } } -class XLF { - constructor(project) { - this.project = project; - this.buffer = []; - this.files = Object.create(null); - this.numberOfMessages = 0; - } - toString() { - this.appendHeader(); - for (let file in this.files) { - this.appendNewLine(``, 2); - for (let item of this.files[file]) { - this.addStringItem(item); - } - this.appendNewLine('', 2); +let XLF = /** @class */ (() => { + class XLF { + constructor(project) { + this.project = project; + this.buffer = []; + this.files = Object.create(null); + this.numberOfMessages = 0; } - this.appendFooter(); - return this.buffer.join('\r\n'); - } - addFile(original, keys, messages) { - if (keys.length === 0) { - console.log('No keys in ' + original); - return; - } - if (keys.length !== messages.length) { - throw new Error(`Unmatching keys(${keys.length}) and messages(${messages.length}).`); - } - this.numberOfMessages += keys.length; - this.files[original] = []; - let existingKeys = new Set(); - for (let i = 0; i < keys.length; i++) { - let key = keys[i]; - let realKey; - let comment; - if (Is.string(key)) { - realKey = key; - comment = undefined; - } - else if (LocalizeInfo.is(key)) { - realKey = key.key; - if (key.comment && key.comment.length > 0) { - comment = key.comment.map(comment => encodeEntities(comment)).join('\r\n'); + toString() { + this.appendHeader(); + for (let file in this.files) { + this.appendNewLine(``, 2); + for (let item of this.files[file]) { + this.addStringItem(file, item); } + this.appendNewLine('', 2); } - if (!realKey || existingKeys.has(realKey)) { - continue; + this.appendFooter(); + return this.buffer.join('\r\n'); + } + addFile(original, keys, messages) { + if (keys.length === 0) { + console.log('No keys in ' + original); + return; } - existingKeys.add(realKey); - let message = encodeEntities(messages[i]); - this.files[original].push({ id: realKey, message: message, comment: comment }); + if (keys.length !== messages.length) { + throw new Error(`Unmatching keys(${keys.length}) and messages(${messages.length}).`); + } + this.numberOfMessages += keys.length; + this.files[original] = []; + let existingKeys = new Set(); + for (let i = 0; i < keys.length; i++) { + let key = keys[i]; + let realKey; + let comment; + if (Is.string(key)) { + realKey = key; + comment = undefined; + } + else if (LocalizeInfo.is(key)) { + realKey = key.key; + if (key.comment && key.comment.length > 0) { + comment = key.comment.map(comment => encodeEntities(comment)).join('\r\n'); + } + } + if (!realKey || existingKeys.has(realKey)) { + continue; + } + existingKeys.add(realKey); + let message = encodeEntities(messages[i]); + this.files[original].push({ id: realKey, message: message, comment: comment }); + } + } + addStringItem(file, item) { + if (!item.id || item.message === undefined || item.message === null) { + throw new Error(`No item ID or value specified: ${JSON.stringify(item)}. File: ${file}`); + } + if (item.message.length === 0) { + log(`Item with id ${item.id} in file ${file} has an empty message.`); + } + this.appendNewLine(``, 4); + this.appendNewLine(`${item.message}`, 6); + if (item.comment) { + this.appendNewLine(`${item.comment}`, 6); + } + this.appendNewLine('', 4); + } + appendHeader() { + this.appendNewLine('', 0); + this.appendNewLine('', 0); + } + appendFooter() { + this.appendNewLine('', 0); + } + appendNewLine(content, indent) { + let line = new Line(indent); + line.append(content); + this.buffer.push(line.toString()); } } - addStringItem(item) { - if (!item.id || !item.message) { - throw new Error(`No item ID or value specified: ${JSON.stringify(item)}`); - } - this.appendNewLine(``, 4); - this.appendNewLine(`${item.message}`, 6); - if (item.comment) { - this.appendNewLine(`${item.comment}`, 6); - } - this.appendNewLine('', 4); - } - appendHeader() { - this.appendNewLine('', 0); - this.appendNewLine('', 0); - } - appendFooter() { - this.appendNewLine('', 0); - } - appendNewLine(content, indent) { - let line = new Line(indent); - line.append(content); - this.buffer.push(line.toString()); - } -} + XLF.parsePseudo = function (xlfString) { + return new Promise((resolve) => { + let parser = new xml2js.Parser(); + let files = []; + parser.parseString(xlfString, function (_err, result) { + const fileNodes = result['xliff']['file']; + fileNodes.forEach(file => { + const originalFilePath = file.$.original; + const messages = {}; + const transUnits = file.body[0]['trans-unit']; + if (transUnits) { + transUnits.forEach((unit) => { + const key = unit.$.id; + const val = pseudify(unit.source[0]['_'].toString()); + if (key && val) { + messages[key] = decodeEntities(val); + } + }); + files.push({ messages: messages, originalFilePath: originalFilePath, language: 'ps' }); + } + }); + resolve(files); + }); + }); + }; + XLF.parse = function (xlfString) { + return new Promise((resolve, reject) => { + let parser = new xml2js.Parser(); + let files = []; + parser.parseString(xlfString, function (err, result) { + if (err) { + reject(new Error(`XLF parsing error: Failed to parse XLIFF string. ${err}`)); + } + const fileNodes = result['xliff']['file']; + if (!fileNodes) { + reject(new Error(`XLF parsing error: XLIFF file does not contain "xliff" or "file" node(s) required for parsing.`)); + } + fileNodes.forEach((file) => { + const originalFilePath = file.$.original; + if (!originalFilePath) { + reject(new Error(`XLF parsing error: XLIFF file node does not contain original attribute to determine the original location of the resource file.`)); + } + let language = file.$['target-language']; + if (!language) { + reject(new Error(`XLF parsing error: XLIFF file node does not contain target-language attribute to determine translated language.`)); + } + const messages = {}; + const transUnits = file.body[0]['trans-unit']; + if (transUnits) { + transUnits.forEach((unit) => { + const key = unit.$.id; + if (!unit.target) { + return; // No translation available + } + let val = unit.target[0]; + if (typeof val !== 'string') { + val = val._; + } + if (key && val) { + messages[key] = decodeEntities(val); + } + else { + reject(new Error(`XLF parsing error: XLIFF file ${originalFilePath} does not contain full localization data. ID or target translation for one of the trans-unit nodes is not present.`)); + } + }); + files.push({ messages: messages, originalFilePath: originalFilePath, language: language.toLowerCase() }); + } + }); + resolve(files); + }); + }); + }; + return XLF; +})(); exports.XLF = XLF; -XLF.parsePseudo = function (xlfString) { - return new Promise((resolve) => { - let parser = new xml2js.Parser(); - let files = []; - parser.parseString(xlfString, function (_err, result) { - const fileNodes = result['xliff']['file']; - fileNodes.forEach(file => { - const originalFilePath = file.$.original; - const messages = {}; - const transUnits = file.body[0]['trans-unit']; - if (transUnits) { - transUnits.forEach((unit) => { - const key = unit.$.id; - const val = pseudify(unit.source[0]['_'].toString()); - if (key && val) { - messages[key] = decodeEntities(val); - } - }); - files.push({ messages: messages, originalFilePath: originalFilePath, language: 'ps' }); - } - }); - resolve(files); - }); - }); -}; -XLF.parse = function (xlfString) { - return new Promise((resolve, reject) => { - let parser = new xml2js.Parser(); - let files = []; - parser.parseString(xlfString, function (err, result) { - if (err) { - reject(new Error(`XLF parsing error: Failed to parse XLIFF string. ${err}`)); - } - const fileNodes = result['xliff']['file']; - if (!fileNodes) { - reject(new Error(`XLF parsing error: XLIFF file does not contain "xliff" or "file" node(s) required for parsing.`)); - } - fileNodes.forEach((file) => { - const originalFilePath = file.$.original; - if (!originalFilePath) { - reject(new Error(`XLF parsing error: XLIFF file node does not contain original attribute to determine the original location of the resource file.`)); - } - let language = file.$['target-language']; - if (!language) { - reject(new Error(`XLF parsing error: XLIFF file node does not contain target-language attribute to determine translated language.`)); - } - const messages = {}; - const transUnits = file.body[0]['trans-unit']; - if (transUnits) { - transUnits.forEach((unit) => { - const key = unit.$.id; - if (!unit.target) { - return; // No translation available - } - let val = unit.target[0]; - if (typeof val !== 'string') { - val = val._; - } - if (key && val) { - messages[key] = decodeEntities(val); - } - else { - reject(new Error(`XLF parsing error: XLIFF file ${originalFilePath} does not contain full localization data. ID or target translation for one of the trans-unit nodes is not present.`)); - } - }); - files.push({ messages: messages, originalFilePath: originalFilePath, language: language.toLowerCase() }); - } - }); - resolve(files); - }); - }); -}; class Limiter { constructor(maxDegreeOfParalellism) { this.maxDegreeOfParalellism = maxDegreeOfParalellism; diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index ae71fd3bf63..dd2b3cd1d60 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -110,10 +110,6 @@ "name": "vs/workbench/contrib/output", "project": "vscode-workbench" }, - { - "name": "vs/workbench/contrib/openInDesktop", - "project": "vscode-workbench" - }, { "name": "vs/workbench/contrib/performance", "project": "vscode-workbench" @@ -123,7 +119,11 @@ "project": "vscode-workbench" }, { - "name": "vs/workbench/contrib/quickopen", + "name": "vs/workbench/contrib/notebook", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/quickaccess", "project": "vscode-workbench" }, { @@ -262,6 +262,10 @@ "name": "vs/workbench/services/files", "project": "vscode-workbench" }, + { + "name": "vs/workbench/services/log", + "project": "vscode-workbench" + }, { "name": "vs/workbench/services/integrity", "project": "vscode-workbench" @@ -337,6 +341,10 @@ { "name": "vs/workbench/contrib/timeline", "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/authentication", + "project": "vscode-workbench" } ] } diff --git a/build/lib/i18n.ts b/build/lib/i18n.ts index d69109a9d05..b9fb3879872 100644 --- a/build/lib/i18n.ts +++ b/build/lib/i18n.ts @@ -201,7 +201,7 @@ export class XLF { for (let file in this.files) { this.appendNewLine(``, 2); for (let item of this.files[file]) { - this.addStringItem(item); + this.addStringItem(file, item); } this.appendNewLine('', 2); } @@ -243,9 +243,12 @@ export class XLF { } } - private addStringItem(item: Item): void { - if (!item.id || !item.message) { - throw new Error(`No item ID or value specified: ${JSON.stringify(item)}`); + private addStringItem(file: string, item: Item): void { + if (!item.id || item.message === undefined || item.message === null) { + throw new Error(`No item ID or value specified: ${JSON.stringify(item)}. File: ${file}`); + } + if (item.message.length === 0) { + log(`Item with id ${item.id} in file ${file} has an empty message.`); } this.appendNewLine(``, 4); diff --git a/build/lib/optimize.js b/build/lib/optimize.js index 45f11698463..a3d49ea2e7a 100644 --- a/build/lib/optimize.js +++ b/build/lib/optimize.js @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); +exports.minifyTask = exports.optimizeTask = exports.loaderConfig = void 0; const es = require("event-stream"); -const fs = require("fs"); const gulp = require("gulp"); const concat = require("gulp-concat"); const minifyCSS = require("gulp-cssnano"); @@ -133,14 +133,6 @@ function optimizeTask(opts) { if (err || !result) { return bundlesStream.emit('error', JSON.stringify(err)); } - if (opts.inlineAmdImages) { - try { - result = inlineAmdImages(src, result); - } - catch (err) { - return bundlesStream.emit('error', JSON.stringify(err)); - } - } toBundleStream(src, bundledFileHeader, result.files).pipe(bundlesStream); // Remove css inlined resources const filteredResources = resources.slice(); @@ -176,39 +168,6 @@ function optimizeTask(opts) { }; } exports.optimizeTask = optimizeTask; -function inlineAmdImages(src, result) { - for (const outputFile of result.files) { - for (const sourceFile of outputFile.sources) { - if (sourceFile.path && /\.js$/.test(sourceFile.path)) { - sourceFile.contents = sourceFile.contents.replace(/\([^.]+\.registerAndGetAmdImageURL\(([^)]+)\)\)/g, (_, m0) => { - let imagePath = m0; - // remove `` or '' - if ((imagePath.charAt(0) === '`' && imagePath.charAt(imagePath.length - 1) === '`') - || (imagePath.charAt(0) === '\'' && imagePath.charAt(imagePath.length - 1) === '\'')) { - imagePath = imagePath.substr(1, imagePath.length - 2); - } - if (!/\.(png|svg)$/.test(imagePath)) { - console.log(`original: ${_}`); - return _; - } - const repoLocation = path.join(src, imagePath); - const absoluteLocation = path.join(REPO_ROOT_PATH, repoLocation); - if (!fs.existsSync(absoluteLocation)) { - const message = `Invalid amd image url in file ${sourceFile.path}: ${imagePath}`; - console.log(message); - throw new Error(message); - } - const fileContents = fs.readFileSync(absoluteLocation); - const mime = /\.svg$/.test(imagePath) ? 'image/svg+xml' : 'image/png'; - // Mark the file as inlined so we don't ship it by itself - result.cssInlinedResources.push(repoLocation); - return `("data:${mime};base64,${fileContents.toString('base64')}")`; - }); - } - } - } - return result; -} /** * Wrap around uglify and allow the preserveComments function * to have a file "context" to include our copyright only once per file. diff --git a/build/lib/optimize.ts b/build/lib/optimize.ts index 46189514c30..0d6bead1d53 100644 --- a/build/lib/optimize.ts +++ b/build/lib/optimize.ts @@ -6,7 +6,6 @@ 'use strict'; import * as es from 'event-stream'; -import * as fs from 'fs'; import * as gulp from 'gulp'; import * as concat from 'gulp-concat'; import * as minifyCSS from 'gulp-cssnano'; @@ -160,10 +159,6 @@ export interface IOptimizeTaskOpts { * (emit bundleInfo.json file) */ bundleInfo: boolean; - /** - * replace calls to `registerAndGetAmdImageURL` with data uris - */ - inlineAmdImages: boolean; /** * (out folder name) */ @@ -197,14 +192,6 @@ export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStr bundle.bundle(entryPoints, loaderConfig, function (err, result) { if (err || !result) { return bundlesStream.emit('error', JSON.stringify(err)); } - if (opts.inlineAmdImages) { - try { - result = inlineAmdImages(src, result); - } catch (err) { - return bundlesStream.emit('error', JSON.stringify(err)); - } - } - toBundleStream(src, bundledFileHeader, result.files).pipe(bundlesStream); // Remove css inlined resources @@ -249,42 +236,6 @@ export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStr }; } -function inlineAmdImages(src: string, result: bundle.IBundleResult): bundle.IBundleResult { - for (const outputFile of result.files) { - for (const sourceFile of outputFile.sources) { - if (sourceFile.path && /\.js$/.test(sourceFile.path)) { - sourceFile.contents = sourceFile.contents.replace(/\([^.]+\.registerAndGetAmdImageURL\(([^)]+)\)\)/g, (_, m0) => { - let imagePath = m0; - // remove `` or '' - if ((imagePath.charAt(0) === '`' && imagePath.charAt(imagePath.length - 1) === '`') - || (imagePath.charAt(0) === '\'' && imagePath.charAt(imagePath.length - 1) === '\'')) { - imagePath = imagePath.substr(1, imagePath.length - 2); - } - if (!/\.(png|svg)$/.test(imagePath)) { - console.log(`original: ${_}`); - return _; - } - const repoLocation = path.join(src, imagePath); - const absoluteLocation = path.join(REPO_ROOT_PATH, repoLocation); - if (!fs.existsSync(absoluteLocation)) { - const message = `Invalid amd image url in file ${sourceFile.path}: ${imagePath}`; - console.log(message); - throw new Error(message); - } - const fileContents = fs.readFileSync(absoluteLocation); - const mime = /\.svg$/.test(imagePath) ? 'image/svg+xml' : 'image/png'; - - // Mark the file as inlined so we don't ship it by itself - result.cssInlinedResources.push(repoLocation); - - return `("data:${mime};base64,${fileContents.toString('base64')}")`; - }); - } - } - } - return result; -} - declare class FileWithCopyright extends VinylFile { public __hasOurCopyright: boolean; } diff --git a/build/lib/reporter.js b/build/lib/reporter.js index e0461dc6d9d..67615bf48dc 100644 --- a/build/lib/reporter.js +++ b/build/lib/reporter.js @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); +exports.createReporter = void 0; const es = require("event-stream"); const _ = require("underscore"); const fancyLog = require("fancy-log"); diff --git a/build/lib/standalone.js b/build/lib/standalone.js index ccfd7b45d23..531194c35fd 100644 --- a/build/lib/standalone.js +++ b/build/lib/standalone.js @@ -4,6 +4,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); +exports.createESMSourcesAndResources2 = exports.extractEditor = void 0; const ts = require("typescript"); const fs = require("fs"); const path = require("path"); diff --git a/build/lib/stats.js b/build/lib/stats.js index 99ad665f223..2ff02e405a6 100644 --- a/build/lib/stats.js +++ b/build/lib/stats.js @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); +exports.submitAllStats = exports.createStatsStream = void 0; const es = require("event-stream"); const fancyLog = require("fancy-log"); const ansiColors = require("ansi-colors"); diff --git a/build/lib/task.js b/build/lib/task.js index f1e6e3f6245..d08ab8acde8 100644 --- a/build/lib/task.js +++ b/build/lib/task.js @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); +exports.define = exports.parallel = exports.series = void 0; const fancyLog = require("fancy-log"); const ansiColors = require("ansi-colors"); function _isPromise(p) { diff --git a/build/lib/treeshaking.js b/build/lib/treeshaking.js index 65dd88f585c..bf16c0fa839 100644 --- a/build/lib/treeshaking.js +++ b/build/lib/treeshaking.js @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); +exports.shake = exports.toStringShakeLevel = exports.ShakeLevel = void 0; const fs = require("fs"); const path = require("path"); const ts = require("typescript"); @@ -75,11 +76,7 @@ function createTypeScriptLanguageService(options) { FILES[typing] = fs.readFileSync(filePath).toString(); }); // Resolve libs - const RESOLVED_LIBS = {}; - options.libs.forEach((filename) => { - const filepath = path.join(TYPESCRIPT_LIB_FOLDER, filename); - RESOLVED_LIBS[`defaultLib:${filename}`] = fs.readFileSync(filepath).toString(); - }); + const RESOLVED_LIBS = processLibFiles(options); const compilerOptions = ts.convertCompilerOptionsFromJson(options.compilerOptions, options.sourcesRoot).options; const host = new TypeScriptLanguageServiceHost(RESOLVED_LIBS, FILES, compilerOptions); return ts.createLanguageService(host); @@ -137,6 +134,29 @@ function discoverAndReadFiles(options) { } return FILES; } +/** + * Read lib files and follow lib references + */ +function processLibFiles(options) { + const stack = [...options.compilerOptions.lib]; + const result = {}; + while (stack.length > 0) { + const filename = `lib.${stack.shift().toLowerCase()}.d.ts`; + const key = `defaultLib:${filename}`; + if (!result[key]) { + // add this file + const filepath = path.join(TYPESCRIPT_LIB_FOLDER, filename); + const sourceText = fs.readFileSync(filepath).toString(); + result[key] = sourceText; + // precess dependencies and "recurse" + const info = ts.preProcessFile(sourceText); + for (let ref of info.libReferenceDirectives) { + stack.push(ref.fileName); + } + } + } + return result; +} /** * A TypeScript language service host */ @@ -234,6 +254,7 @@ function markNodes(languageService, options) { } const black_queue = []; const gray_queue = []; + const export_import_queue = []; const sourceFilesLoaded = {}; function enqueueTopLevelModuleStatements(sourceFile) { sourceFile.forEachChild((node) => { @@ -245,10 +266,16 @@ function markNodes(languageService, options) { return; } if (ts.isExportDeclaration(node)) { - if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) { + if (!node.exportClause && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) { + // export * from "foo"; setColor(node, 2 /* Black */); enqueueImport(node, node.moduleSpecifier.text); } + if (node.exportClause && ts.isNamedExports(node.exportClause)) { + for (const exportSpecifier of node.exportClause.elements) { + export_import_queue.push(exportSpecifier); + } + } return; } if (ts.isExpressionStatement(node) @@ -306,7 +333,7 @@ function markNodes(languageService, options) { } setColor(node, 2 /* Black */); black_queue.push(node); - if (options.shakeLevel === 2 /* ClassMembers */ && (ts.isMethodDeclaration(node) || ts.isMethodSignature(node) || ts.isPropertySignature(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node))) { + if (options.shakeLevel === 2 /* ClassMembers */ && (ts.isMethodDeclaration(node) || ts.isMethodSignature(node) || ts.isPropertySignature(node) || ts.isPropertyDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node))) { const references = languageService.getReferencesAtPosition(node.getSourceFile().fileName, node.name.pos + node.name.getLeadingTriviaWidth()); if (references) { for (let i = 0, len = references.length; i < len; i++) { @@ -402,6 +429,7 @@ function markNodes(languageService, options) { || ts.isConstructSignatureDeclaration(member) || ts.isIndexSignatureDeclaration(member) || ts.isCallSignatureDeclaration(member) + || memberName === '[Symbol.iterator]' || memberName === 'toJSON' || memberName === 'toString' || memberName === 'dispose' // TODO: keeping all `dispose` methods @@ -426,6 +454,22 @@ function markNodes(languageService, options) { }; node.forEachChild(loop); } + while (export_import_queue.length > 0) { + const node = export_import_queue.shift(); + if (nodeOrParentIsBlack(node)) { + continue; + } + const symbol = node.symbol; + if (!symbol) { + continue; + } + const aliased = checker.getAliasedSymbol(symbol); + if (aliased.declarations && aliased.declarations.length > 0) { + if (nodeOrParentIsBlack(aliased.declarations[0]) || nodeOrChildIsBlack(aliased.declarations[0])) { + setColor(node, 2 /* Black */); + } + } + } } function nodeIsInItsOwnDeclaration(nodeSourceFile, node, symbol) { for (let i = 0, len = symbol.declarations.length; i < len; i++) { @@ -517,6 +561,21 @@ function generateResult(languageService, shakeLevel) { } } } + if (ts.isExportDeclaration(node)) { + if (node.exportClause && node.moduleSpecifier && ts.isNamedExports(node.exportClause)) { + let survivingExports = []; + for (const exportSpecifier of node.exportClause.elements) { + if (getColor(exportSpecifier) === 2 /* Black */) { + survivingExports.push(exportSpecifier.getFullText(sourceFile)); + } + } + const leadingTriviaWidth = node.getLeadingTriviaWidth(); + const leadingTrivia = sourceFile.text.substr(node.pos, leadingTriviaWidth); + if (survivingExports.length > 0) { + return write(`${leadingTrivia}export {${survivingExports.join(',')} } from${node.moduleSpecifier.getFullText(sourceFile)};`); + } + } + } if (shakeLevel === 2 /* ClassMembers */ && (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) && nodeOrChildIsBlack(node)) { let toWrite = node.getFullText(); for (let i = node.members.length - 1; i >= 0; i--) { @@ -567,7 +626,7 @@ function getRealNodeSymbol(checker, node) { // (2) when the aliased symbol is originating from an import. // function shouldSkipAlias(node, declaration) { - if (node.kind !== ts.SyntaxKind.Identifier) { + if (!ts.isShorthandPropertyAssignment(node) && node.kind !== ts.SyntaxKind.Identifier) { return false; } if (node.parent === declaration) { @@ -589,7 +648,9 @@ function getRealNodeSymbol(checker, node) { } } const { parent } = node; - let symbol = checker.getSymbolAtLocation(node); + let symbol = (ts.isShorthandPropertyAssignment(node) + ? checker.getShorthandAssignmentValueSymbol(node) + : checker.getSymbolAtLocation(node)); let importNode = null; // If this is an alias, and the request came at the declaration location // get the aliased symbol instead. This allows for goto def on an import e.g. diff --git a/build/lib/treeshaking.ts b/build/lib/treeshaking.ts index 89f562ad1b8..16d9ab6e3e9 100644 --- a/build/lib/treeshaking.ts +++ b/build/lib/treeshaking.ts @@ -18,7 +18,7 @@ export const enum ShakeLevel { } export function toStringShakeLevel(shakeLevel: ShakeLevel): string { - switch(shakeLevel) { + switch (shakeLevel) { case ShakeLevel.Files: return 'Files (0)'; case ShakeLevel.InnerFile: @@ -42,11 +42,6 @@ export interface ITreeShakingOptions { * Inline usages. */ inlineEntryPoints: string[]; - /** - * TypeScript libs. - * e.g. `lib.d.ts`, `lib.es2015.collection.d.ts` - */ - libs: string[]; /** * Other .d.ts files */ @@ -130,11 +125,7 @@ function createTypeScriptLanguageService(options: ITreeShakingOptions): ts.Langu }); // Resolve libs - const RESOLVED_LIBS: ILibMap = {}; - options.libs.forEach((filename) => { - const filepath = path.join(TYPESCRIPT_LIB_FOLDER, filename); - RESOLVED_LIBS[`defaultLib:${filename}`] = fs.readFileSync(filepath).toString(); - }); + const RESOLVED_LIBS = processLibFiles(options); const compilerOptions = ts.convertCompilerOptionsFromJson(options.compilerOptions, options.sourcesRoot).options; @@ -205,6 +196,34 @@ function discoverAndReadFiles(options: ITreeShakingOptions): IFileMap { return FILES; } +/** + * Read lib files and follow lib references + */ +function processLibFiles(options: ITreeShakingOptions): ILibMap { + + const stack: string[] = [...options.compilerOptions.lib]; + const result: ILibMap = {}; + + while (stack.length > 0) { + const filename = `lib.${stack.shift()!.toLowerCase()}.d.ts`; + const key = `defaultLib:${filename}`; + if (!result[key]) { + // add this file + const filepath = path.join(TYPESCRIPT_LIB_FOLDER, filename); + const sourceText = fs.readFileSync(filepath).toString(); + result[key] = sourceText; + + // precess dependencies and "recurse" + const info = ts.preProcessFile(sourceText); + for (let ref of info.libReferenceDirectives) { + stack.push(ref.fileName); + } + } + } + + return result; +} + interface ILibMap { [libName: string]: string; } interface IFileMap { [fileName: string]: string; } @@ -317,6 +336,7 @@ function markNodes(languageService: ts.LanguageService, options: ITreeShakingOpt const black_queue: ts.Node[] = []; const gray_queue: ts.Node[] = []; + const export_import_queue: ts.Node[] = []; const sourceFilesLoaded: { [fileName: string]: boolean } = {}; function enqueueTopLevelModuleStatements(sourceFile: ts.SourceFile): void { @@ -332,10 +352,16 @@ function markNodes(languageService: ts.LanguageService, options: ITreeShakingOpt } if (ts.isExportDeclaration(node)) { - if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) { + if (!node.exportClause && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) { + // export * from "foo"; setColor(node, NodeColor.Black); enqueueImport(node, node.moduleSpecifier.text); } + if (node.exportClause && ts.isNamedExports(node.exportClause)) { + for (const exportSpecifier of node.exportClause.elements) { + export_import_queue.push(exportSpecifier); + } + } return; } @@ -410,7 +436,7 @@ function markNodes(languageService: ts.LanguageService, options: ITreeShakingOpt setColor(node, NodeColor.Black); black_queue.push(node); - if (options.shakeLevel === ShakeLevel.ClassMembers && (ts.isMethodDeclaration(node) || ts.isMethodSignature(node) || ts.isPropertySignature(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node))) { + if (options.shakeLevel === ShakeLevel.ClassMembers && (ts.isMethodDeclaration(node) || ts.isMethodSignature(node) || ts.isPropertySignature(node) || ts.isPropertyDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node))) { const references = languageService.getReferencesAtPosition(node.getSourceFile().fileName, node.name.pos + node.name.getLeadingTriviaWidth()); if (references) { for (let i = 0, len = references.length; i < len; i++) { @@ -475,7 +501,7 @@ function markNodes(languageService: ts.LanguageService, options: ITreeShakingOpt } if (black_queue.length === 0) { - for (let i = 0; i< gray_queue.length; i++) { + for (let i = 0; i < gray_queue.length; i++) { const node = gray_queue[i]; const nodeParent = node.parent; if ((ts.isClassDeclaration(nodeParent) || ts.isInterfaceDeclaration(nodeParent)) && nodeOrChildIsBlack(nodeParent)) { @@ -521,6 +547,7 @@ function markNodes(languageService: ts.LanguageService, options: ITreeShakingOpt || ts.isConstructSignatureDeclaration(member) || ts.isIndexSignatureDeclaration(member) || ts.isCallSignatureDeclaration(member) + || memberName === '[Symbol.iterator]' || memberName === 'toJSON' || memberName === 'toString' || memberName === 'dispose'// TODO: keeping all `dispose` methods @@ -545,6 +572,23 @@ function markNodes(languageService: ts.LanguageService, options: ITreeShakingOpt }; node.forEachChild(loop); } + + while (export_import_queue.length > 0) { + const node = export_import_queue.shift()!; + if (nodeOrParentIsBlack(node)) { + continue; + } + const symbol: ts.Symbol | undefined = (node).symbol; + if (!symbol) { + continue; + } + const aliased = checker.getAliasedSymbol(symbol); + if (aliased.declarations && aliased.declarations.length > 0) { + if (nodeOrParentIsBlack(aliased.declarations[0]) || nodeOrChildIsBlack(aliased.declarations[0])) { + setColor(node, NodeColor.Black); + } + } + } } function nodeIsInItsOwnDeclaration(nodeSourceFile: ts.SourceFile, node: ts.Node, symbol: ts.Symbol): boolean { @@ -646,6 +690,22 @@ function generateResult(languageService: ts.LanguageService, shakeLevel: ShakeLe } } + if (ts.isExportDeclaration(node)) { + if (node.exportClause && node.moduleSpecifier && ts.isNamedExports(node.exportClause)) { + let survivingExports: string[] = []; + for (const exportSpecifier of node.exportClause.elements) { + if (getColor(exportSpecifier) === NodeColor.Black) { + survivingExports.push(exportSpecifier.getFullText(sourceFile)); + } + } + const leadingTriviaWidth = node.getLeadingTriviaWidth(); + const leadingTrivia = sourceFile.text.substr(node.pos, leadingTriviaWidth); + if (survivingExports.length > 0) { + return write(`${leadingTrivia}export {${survivingExports.join(',')} } from${node.moduleSpecifier.getFullText(sourceFile)};`); + } + } + } + if (shakeLevel === ShakeLevel.ClassMembers && (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) && nodeOrChildIsBlack(node)) { let toWrite = node.getFullText(); for (let i = node.members.length - 1; i >= 0; i--) { @@ -708,7 +768,7 @@ function getRealNodeSymbol(checker: ts.TypeChecker, node: ts.Node): [ts.Symbol | // (2) when the aliased symbol is originating from an import. // function shouldSkipAlias(node: ts.Node, declaration: ts.Node): boolean { - if (node.kind !== ts.SyntaxKind.Identifier) { + if (!ts.isShorthandPropertyAssignment(node) && node.kind !== ts.SyntaxKind.Identifier) { return false; } if (node.parent === declaration) { @@ -733,7 +793,12 @@ function getRealNodeSymbol(checker: ts.TypeChecker, node: ts.Node): [ts.Symbol | const { parent } = node; - let symbol = checker.getSymbolAtLocation(node); + let symbol = ( + ts.isShorthandPropertyAssignment(node) + ? checker.getShorthandAssignmentValueSymbol(node) + : checker.getSymbolAtLocation(node) + ); + let importNode: ts.Declaration | null = null; // If this is an alias, and the request came at the declaration location // get the aliased symbol instead. This allows for goto def on an import e.g. diff --git a/build/lib/util.js b/build/lib/util.js index 752d9fb63f0..d42670e67a5 100644 --- a/build/lib/util.js +++ b/build/lib/util.js @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); +exports.streamToPromise = exports.versionStringToNumber = exports.filter = exports.rebase = exports.getVersion = exports.ensureDir = exports.rreddir = exports.rimraf = exports.stripSourceMappingURL = exports.loadSourcemaps = exports.cleanNodeModules = exports.skipDirectories = exports.toFileUri = exports.setExecutableBit = exports.fixWin32DirectoryPermissions = exports.incremental = void 0; const es = require("event-stream"); const debounce = require("debounce"); const _filter = require("gulp-filter"); diff --git a/build/lib/watch/watch-win32.js b/build/lib/watch/watch-win32.js index d0cd307ba16..91c0eae7565 100644 --- a/build/lib/watch/watch-win32.js +++ b/build/lib/watch/watch-win32.js @@ -25,7 +25,6 @@ function watch(root) { var child = cp.spawn(watcherPath, [root]); child.stdout.on('data', function (data) { - // @ts-ignore var lines = data.toString('utf8').split('\n'); for (var i = 0; i < lines.length; i++) { var line = lines[i].trim(); @@ -47,7 +46,6 @@ function watch(root) { path: changePathFull, base: root }); - //@ts-ignore file.event = toChangeType(changeType); result.emit('data', file); } @@ -106,4 +104,4 @@ module.exports = function (pattern, options) { }); })) .pipe(rebase); -}; \ No newline at end of file +}; diff --git a/build/lib/watch/yarn.lock b/build/lib/watch/yarn.lock index 3f330da1764..edbfe1f3121 100644 --- a/build/lib/watch/yarn.lock +++ b/build/lib/watch/yarn.lock @@ -230,9 +230,9 @@ for-own@^0.1.4: for-in "^1.0.1" fsevents@~2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.1.tgz#74c64e21df71721845d0c44fe54b7f56b82995a9" - integrity sha512-4FRPXWETxtigtJW/gxzEDsX1LVbPAM93VleB83kZB+ellqbHMkyt2aJfuzNLRvFPnGi6bcE5SvfxgbXPeKteJw== + version "2.1.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" + integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA== glob-base@^0.3.0: version "0.3.0" @@ -258,9 +258,9 @@ glob-parent@^3.0.1: path-dirname "^1.0.0" glob-parent@~5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" - integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== + version "5.1.1" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" + integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== dependencies: is-glob "^4.0.1" @@ -405,9 +405,9 @@ kind-of@^3.0.2: is-buffer "^1.1.5" kind-of@^6.0.0: - version "6.0.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" - integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== math-random@^1.0.1: version "1.0.4" @@ -479,9 +479,9 @@ path-is-absolute@^1.0.1: integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= picomatch@^2.0.4: - version "2.1.0" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.1.0.tgz#0fd042f568d08b1ad9ff2d3ec0f0bfb3cb80e177" - integrity sha512-uhnEDzAbrcJ8R3g2fANnSuXZMBtkpSjxTTgn2LeSiQlfmq72enQJWdQllXW24MBLYnA1SBD2vfvx2o0Zw3Ielw== + version "2.2.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" + integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== pify@^2.3.0: version "2.3.0" @@ -530,9 +530,9 @@ randomatic@^3.0.0: math-random "^1.0.1" readable-stream@^2.0.2, readable-stream@^2.2.2, readable-stream@^2.3.5: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" - integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== dependencies: core-util-is "~1.0.0" inherits "~2.0.3" diff --git a/build/monaco/ThirdPartyNotices.txt b/build/monaco/ThirdPartyNotices.txt index 1de70ddaab6..8b488daf191 100644 --- a/build/monaco/ThirdPartyNotices.txt +++ b/build/monaco/ThirdPartyNotices.txt @@ -33,32 +33,6 @@ USE OR OTHER DEALINGS IN THE SOFTWARE. END OF nodejs path library NOTICES AND INFORMATION -%% promise-polyfill version 8.1.0 (https://github.com/taylorhakes/promise-polyfill) -========================================= -Copyright (c) 2014 Taylor Hakes -Copyright (c) 2014 Forbes Lindesay - -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 winjs NOTICES AND INFORMATION - - %% string_scorer version 0.1.20 (https://github.com/joshaven/string_score) diff --git a/build/monaco/api.js b/build/monaco/api.js index bd656f2bcd1..1de24d4065c 100644 --- a/build/monaco/api.js +++ b/build/monaco/api.js @@ -4,6 +4,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); +exports.execute = exports.run3 = exports.DeclarationResolver = exports.FSProvider = exports.RECIPE_PATH = void 0; const fs = require("fs"); const ts = require("typescript"); const path = require("path"); diff --git a/build/npm/preinstall.js b/build/npm/preinstall.js index eca72654382..cb88d37adef 100644 --- a/build/npm/preinstall.js +++ b/build/npm/preinstall.js @@ -23,7 +23,7 @@ if (majorYarnVersion < 1 || minorYarnVersion < 10) { err = true; } -if (!/yarn\.js$|yarnpkg$/.test(process.env['npm_execpath'])) { +if (!/yarn[\w-.]*\.js$|yarnpkg$/.test(process.env['npm_execpath'])) { console.error('\033[1;31m*** Please use yarn to install dependencies.\033[0;0m'); err = true; } diff --git a/build/package.json b/build/package.json index d1cd1b063b7..9302d56f2f6 100644 --- a/build/package.json +++ b/build/package.json @@ -40,10 +40,10 @@ "iconv-lite": "0.4.23", "mime": "^1.3.4", "minimatch": "3.0.4", - "minimist": "^1.2.0", + "minimist": "^1.2.2", "request": "^2.85.0", "terser": "4.3.8", - "typescript": "3.8.2", + "typescript": "^3.9.0-dev.20200327", "vsce": "1.48.0", "vscode-telemetry-extractor": "^1.5.4", "xml2js": "^0.4.17" diff --git a/build/yarn.lock b/build/yarn.lock index 53bc78757a8..298e9596f12 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -1780,10 +1780,10 @@ minimatch@3.0.4, minimatch@^3.0.3, minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -minimist@^1.1.0, minimist@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= +minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.2.tgz#b00a00230a1108c48c169e69a291aafda3aacd63" + integrity sha512-rIqbOrKb8GJmx/5bc2M0QchhUouMXSpd1RTclXsB41JdL+VtnojfaJR+h7F9k18/4kHUsBFgk80Uk+q569vjPA== minimist@~0.0.1: version "0.0.10" @@ -2453,16 +2453,16 @@ typed-rest-client@^0.9.0: tunnel "0.0.4" underscore "1.8.3" -typescript@3.8.2: - version "3.8.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.2.tgz#91d6868aaead7da74f493c553aeff76c0c0b1d5a" - integrity sha512-EgOVgL/4xfVrCMbhYKUQTdF37SQn4Iw73H5BgCrF1Abdun7Kwy/QZsE/ssAy0y4LxBbvua3PIbFsbRczWWnDdQ== - typescript@^3.0.1: version "3.5.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g== +typescript@^3.9.0-dev.20200327: + version "3.9.0-dev.20200327" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.0-dev.20200327.tgz#52179aae816587f772a0526e91143760f2bee42f" + integrity sha512-/TWD/zPvhAcN2Toqx2NBQ+oDVGVj4iqupjWcUAwL45TfcODeHpzszneABR1b/EjHbtUObtLH40vy5Z6rdVvKzg== + typical@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" diff --git a/cglicenses.json b/cglicenses.json index 76e6fe49f00..0d588440ae2 100644 --- a/cglicenses.json +++ b/cglicenses.json @@ -89,5 +89,85 @@ "prependLicenseText": [ "Copyright (c) Microsoft Corporation. All rights reserved." ] + }, + { + // Reason: The license at https://github.com/reem/rust-unreachable/blob/master/LICENSE-MIT + // cannot be found by the OSS tool automatically. + "name": "reem/rust-unreachable", + "fullLicenseText": [ + "Copyright (c) 2015 The rust-unreachable Developers", + "", + "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: The license at https://github.com/reem/rust-void/blob/master/LICENSE-MIT + // cannot be found by the OSS tool automatically. + "name": "reem/rust-void", + "fullLicenseText": [ + "Copyright (c) 2015 The rust-void Developers", + "", + "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: The license at https://github.com/mrhooray/crc-rs/blob/master/LICENSE-MIT + // cannot be found by the OSS tool automatically. + "name": "mrhooray/crc-rs", + "fullLicenseText": [ + "MIT License", + "", + "Copyright (c) 2017 crc-rs Developers", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + "of this software and associated documentation files (the \"Software\"), to deal", + "in the Software without restriction, including without limitation the rights", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + "copies of the Software, and to permit persons to whom the Software is", + "furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all", + "copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", + "SOFTWARE." + ] } ] diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index 0defd3aee6f..f929c260fd5 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -96,6 +96,10 @@ "fileMatch": "%APP_SETTINGS_HOME%/snippets/*.json", "url": "vscode://schemas/snippets" }, + { + "fileMatch": "%APP_SETTINGS_HOME%/sync/snippets/preview/*.json", + "url": "vscode://schemas/snippets" + }, { "fileMatch": "**/*.code-snippets", "url": "vscode://schemas/global-snippets" diff --git a/extensions/cpp/cgmanifest.json b/extensions/cpp/cgmanifest.json index cdcea133032..1690f2220d1 100644 --- a/extensions/cpp/cgmanifest.json +++ b/extensions/cpp/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "jeff-hykin/cpp-textmate-grammar", "repositoryUrl": "https://github.com/jeff-hykin/cpp-textmate-grammar", - "commitHash": "5b6f67859e895da83dc242b849480ee04ce7ce38" + "commitHash": "666808cab3907fc91ed4d3901060ee6b045cca58" } }, "license": "MIT", - "version": "1.14.20", + "version": "1.14.15", "description": "The files syntaxes/c.json and syntaxes/c++.json were derived from https://github.com/atom/language-c which was originally converted from the C TextMate bundle https://github.com/textmate/c.tmbundle." }, { @@ -42,4 +42,4 @@ } ], "version": 1 -} \ No newline at end of file +} diff --git a/extensions/cpp/syntaxes/c.tmLanguage.json b/extensions/cpp/syntaxes/c.tmLanguage.json index acf62fab955..eef07eeb53d 100644 --- a/extensions/cpp/syntaxes/c.tmLanguage.json +++ b/extensions/cpp/syntaxes/c.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/cpp-textmate-grammar/commit/85b9008b406cc9d3b1c9e779e94cc071116c8426", + "version": "https://github.com/jeff-hykin/cpp-textmate-grammar/commit/72b309aabb63bf14a3cdf0280149121db005d616", "name": "C", "scopeName": "source.c", "patterns": [ @@ -464,7 +464,7 @@ ] }, "backslash_escapes": { - "match": "(?x)\\\\ (\n\\\\\t\t\t |\n[abefnprtv'\"?] |\n[0-3][0-7]{,2}\t |\n[4-7]\\d?\t\t|\nx[a-fA-F0-9]{,2} |\nu[a-fA-F0-9]{,4} |\nU[a-fA-F0-9]{,8} )", + "match": "(?x)\\\\ (\n\\\\\t\t\t |\n[abefnprtv'\"?] |\n[0-3]\\d{,2}\t |\n[4-7]\\d?\t\t|\nx[a-fA-F0-9]{,2} |\nu[a-fA-F0-9]{,4} |\nU[a-fA-F0-9]{,8} )", "name": "constant.character.escape.c" }, "block": { @@ -690,20 +690,12 @@ "name": "storage.type.class.doxygen.c" }, { - "match": "((?<=[\\s*!\\/])[\\\\@]param)(?:\\s*\\[((?:,?\\s*(?:in|out)\\s*)+)\\])?\\s+(\\b\\w+\\b)", + "match": "((?<=[\\s*!\\/])[\\\\@]param)\\s+(\\b\\w+\\b)", "captures": { "1": { "name": "storage.type.class.doxygen.c" }, "2": { - "patterns": [ - { - "match": "in|out", - "name": "keyword.other.parameter.direction.$0.c" - } - ] - }, - "3": { "name": "variable.parameter.c" } } @@ -776,20 +768,12 @@ "name": "storage.type.class.doxygen.c" }, { - "match": "((?<=[\\s*!\\/])[\\\\@]param)(?:\\s*\\[((?:,?\\s*(?:in|out)\\s*)+)\\])?\\s+(\\b\\w+\\b)", + "match": "((?<=[\\s*!\\/])[\\\\@]param)\\s+(\\b\\w+\\b)", "captures": { "1": { "name": "storage.type.class.doxygen.c" }, "2": { - "patterns": [ - { - "match": "in|out", - "name": "keyword.other.parameter.direction.$0.c" - } - ] - }, - "3": { "name": "variable.parameter.c" } } @@ -875,20 +859,12 @@ "name": "storage.type.class.doxygen.c" }, { - "match": "((?<=[\\s*!\\/])[\\\\@]param)(?:\\s*\\[((?:,?\\s*(?:in|out)\\s*)+)\\])?\\s+(\\b\\w+\\b)", + "match": "((?<=[\\s*!\\/])[\\\\@]param)\\s+(\\b\\w+\\b)", "captures": { "1": { "name": "storage.type.class.doxygen.c" }, "2": { - "patterns": [ - { - "match": "in|out", - "name": "keyword.other.parameter.direction.$0.c" - } - ] - }, - "3": { "name": "variable.parameter.c" } } @@ -2898,219 +2874,95 @@ }, { "name": "meta.asm.c", - "begin": "(\\b(?:__asm__|asm)\\b)\\s*((?:volatile)?)", + "begin": "(\\b(?:__asm__|asm)\\b)\\s*((?:volatile)?)\\s*(\\()", "beginCaptures": { "1": { "name": "storage.type.asm.c" }, "2": { "name": "storage.modifier.c" + }, + "3": { + "name": "punctuation.section.parens.begin.bracket.round.assembly.c" + } + }, + "end": "(\\))", + "endCaptures": { + "1": { + "name": "punctuation.section.parens.end.bracket.round.assembly.c" } }, - "end": "(?!\\G)", "patterns": [ { - "match": "(?:^)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:\\n|$)", - "captures": { + "name": "string.quoted.double.c", + "contentName": "meta.embedded.assembly.c", + "begin": "(R?)(\")", + "beginCaptures": { "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "meta.encoding.c" }, "2": { - "name": "comment.block.c punctuation.definition.comment.begin.c" - }, - "3": { - "name": "comment.block.c" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.c punctuation.definition.comment.end.c" - }, - { - "match": "\\*", - "name": "comment.block.c" - } - ] + "name": "punctuation.definition.string.begin.assembly.c" } - } + }, + "end": "(\")", + "endCaptures": { + "1": { + "name": "punctuation.definition.string.end.assembly.c" + } + }, + "patterns": [ + { + "include": "source.asm" + }, + { + "include": "source.x86" + }, + { + "include": "source.x86_64" + }, + { + "include": "source.arm" + }, + { + "include": "#backslash_escapes" + }, + { + "include": "#string_escaped_char" + }, + { + "match": "(?=not)possible" + } + ] + }, + { + "begin": "(\\()", + "beginCaptures": { + "1": { + "name": "punctuation.section.parens.begin.bracket.round.assembly.inner.c" + } + }, + "end": "(\\))", + "endCaptures": { + "1": { + "name": "punctuation.section.parens.end.bracket.round.assembly.inner.c" + } + }, + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + { + "match": ":", + "name": "punctuation.separator.delimiter.colon.assembly.c" }, { "include": "#comments_context" }, { "include": "#comments" - }, - { - "begin": "(((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))\\()", - "beginCaptures": { - "1": { - "name": "punctuation.section.parens.begin.bracket.round.assembly.c" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.c punctuation.definition.comment.begin.c" - }, - "4": { - "name": "comment.block.c" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.c punctuation.definition.comment.end.c" - }, - { - "match": "\\*", - "name": "comment.block.c" - } - ] - } - }, - "end": "(\\))", - "endCaptures": { - "1": { - "name": "punctuation.section.parens.end.bracket.round.assembly.c" - } - }, - "patterns": [ - { - "name": "string.quoted.double.c", - "contentName": "meta.embedded.assembly.c", - "begin": "(R?)(\")", - "beginCaptures": { - "1": { - "name": "meta.encoding.c" - }, - "2": { - "name": "punctuation.definition.string.begin.assembly.c" - } - }, - "end": "(\")", - "endCaptures": { - "1": { - "name": "punctuation.definition.string.end.assembly.c" - } - }, - "patterns": [ - { - "include": "source.asm" - }, - { - "include": "source.x86" - }, - { - "include": "source.x86_64" - }, - { - "include": "source.arm" - }, - { - "include": "#backslash_escapes" - }, - { - "include": "#string_escaped_char" - } - ] - }, - { - "begin": "(\\()", - "beginCaptures": { - "1": { - "name": "punctuation.section.parens.begin.bracket.round.assembly.inner.c" - } - }, - "end": "(\\))", - "endCaptures": { - "1": { - "name": "punctuation.section.parens.end.bracket.round.assembly.inner.c" - } - }, - "patterns": [ - { - "include": "#evaluation_context" - } - ] - }, - { - "match": "\\[((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))([a-zA-Z_]\\w*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))\\]", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.c punctuation.definition.comment.begin.c" - }, - "3": { - "name": "comment.block.c" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.c punctuation.definition.comment.end.c" - }, - { - "match": "\\*", - "name": "comment.block.c" - } - ] - }, - "5": { - "name": "variable.other.asm.label.c" - }, - "6": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "7": { - "name": "comment.block.c punctuation.definition.comment.begin.c" - }, - "8": { - "name": "comment.block.c" - }, - "9": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.c punctuation.definition.comment.end.c" - }, - { - "match": "\\*", - "name": "comment.block.c" - } - ] - } - } - }, - { - "match": ":", - "name": "punctuation.separator.delimiter.colon.assembly.c" - }, - { - "include": "#comments_context" - }, - { - "include": "#comments" - } - ] } ] } @@ -3342,4 +3194,4 @@ "name": "punctuation.vararg-ellipses.c" } } -} \ No newline at end of file +} diff --git a/extensions/cpp/syntaxes/cpp.embedded.macro.tmLanguage.json b/extensions/cpp/syntaxes/cpp.embedded.macro.tmLanguage.json index e1a423160dc..4c0e9a582cf 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/cpp-textmate-grammar/commit/5b6f67859e895da83dc242b849480ee04ce7ce38", + "version": "https://github.com/jeff-hykin/cpp-textmate-grammar/commit/666808cab3907fc91ed4d3901060ee6b045cca58", "name": "C++", "scopeName": "source.cpp.embedded.macro", "patterns": [ @@ -27,13 +27,13 @@ "include": "#using_namespace" }, { - "include": "source.cpp#type_alias" + "include": "#type_alias" }, { - "include": "source.cpp#using_name" + "include": "#using_name" }, { - "include": "source.cpp#namespace_alias" + "include": "#namespace_alias" }, { "include": "#namespace_block" @@ -51,10 +51,10 @@ "include": "#typedef_union" }, { - "include": "source.cpp#misc_keywords" + "include": "#typedef_keyword" }, { - "include": "source.cpp#standard_declares" + "include": "#standard_declares" }, { "include": "#class_block" @@ -69,13 +69,13 @@ "include": "#enum_block" }, { - "include": "source.cpp#template_isolated_definition" + "include": "#template_isolated_definition" }, { "include": "#template_definition" }, { - "include": "source.cpp#access_control_keywords" + "include": "#access_control_keywords" }, { "include": "#block" @@ -94,20 +94,56 @@ } ], "repository": { + "access_control_keywords": { + "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(((?:protected|private|public))\\s*(:))", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "5": { + "name": "storage.type.modifier.access.control.$6.cpp" + }, + "7": { + "name": "punctuation.separator.colon.access.control.cpp" + } + } + }, "alignas_attribute": { - "begin": "alignas\\(", - "end": "\\)|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", "beginCaptures": { "1": { "name": "keyword.operator.functionlike.cpp keyword.operator.alignas.cpp" @@ -168,7 +202,7 @@ "2": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -194,12 +228,12 @@ "name": "punctuation.section.arguments.begin.bracket.round.operator.alignas.cpp" } }, + "end": "(\\))|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", "beginCaptures": { "1": { "name": "keyword.operator.functionlike.cpp keyword.operator.alignof.cpp" @@ -216,7 +250,7 @@ "2": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -242,12 +276,12 @@ "name": "punctuation.section.arguments.begin.bracket.round.operator.alignof.cpp" } }, + "end": "(\\))|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:\\n)|$)", - "captures": { + "name": "string.quoted.double.cpp", + "contentName": "meta.embedded.assembly.cpp", + "begin": "(R?)(\")", + "beginCaptures": { "1": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] + "name": "meta.encoding.cpp" }, "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] + "name": "punctuation.definition.string.begin.assembly.cpp" } - } + }, + "end": "(\")|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\(", - "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}))*)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]", - "captures": { - "1": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "5": { - "name": "variable.other.asm.label.cpp" - }, - "6": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "7": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "8": { - "name": "comment.block.cpp" - }, - "9": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - } - }, - { - "match": ":", - "name": "punctuation.separator.delimiter.colon.assembly.cpp" - }, - { - "include": "#comments_context" - }, - { - "include": "#comments" - } - ] } ] }, + "assignment_operator": { + "match": "\\=", + "name": "keyword.operator.assignment.cpp" + }, "attributes_context": { "patterns": [ { @@ -489,20 +402,24 @@ } ] }, + "backslash_escapes": { + "match": "(?x)\\\\ (\n\\\\\t\t\t |\n[abefnprtv'\"?] |\n[0-3]\\d{,2}\t |\n[4-7]\\d?\t\t|\nx[a-fA-F0-9]{,2} |\nu[a-fA-F0-9]{,4} |\nU[a-fA-F0-9]{,8} )", + "name": "constant.character.escape.cpp" + }, "block": { - "begin": "{", - "end": "}|(?=\\s*#\\s*(?:elif|else|endif)\\b)|(?=(?(?:\\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)))(\\()", - "end": "\\)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", "beginCaptures": { "1": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -556,7 +472,7 @@ "5": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -584,7 +500,7 @@ "10": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -612,7 +528,7 @@ "15": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -640,7 +556,7 @@ "20": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -668,7 +584,7 @@ "25": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -694,8 +610,9 @@ "name": "punctuation.section.arguments.begin.bracket.round.initializer.cpp" } }, + "end": "(\\))|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(DLLEXPORT)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(final)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:))?", - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(DLLEXPORT)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(final)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)((?>[^{]*)))?))", "beginCaptures": { - "0": { + "1": { "name": "meta.head.class.cpp" }, + "3": { + "name": "storage.type.$3.cpp" + }, + "4": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "5": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "6": { + "name": "comment.block.cpp" + }, + "7": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "8": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "9": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "10": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "11": { + "name": "comment.block.cpp" + }, + "12": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "13": { + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + "14": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "15": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "16": { + "name": "comment.block.cpp" + }, + "17": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "18": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "21": { + "name": "comment.block.cpp" + }, + "22": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "23": { + "name": "entity.name.type.$3.cpp" + }, + "24": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "26": { + "name": "comment.block.cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "28": { + "name": "storage.type.modifier.final.cpp" + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "37": { + "name": "punctuation.separator.colon.inheritance.cpp" + }, + "38": { + "patterns": [ + { + "include": "#inheritance_context" + } + ] + } + }, + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>)|(?=(?|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\S)(?![:{])", + "captures": { + "1": { + "name": "storage.type.class.declare.cpp" }, "2": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -789,29 +997,65 @@ ] }, "6": { - "patterns": [ - { - "include": "#attributes_context" - }, - { - "include": "source.cpp#number_literal" - } - ] + "name": "entity.name.type.class.cpp" }, "7": { "patterns": [ { - "include": "source.cpp#inline_comment" + "match": "\\*", + "name": "storage.modifier.pointer.cpp" + }, + { + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cpp" } ] }, "8": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "9": { - "name": "comment.block.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "10": { + "name": "comment.block.cpp" + }, + "11": { "patterns": [ { "match": "\\*\\/", @@ -823,13 +1067,10 @@ } ] }, - "11": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, "12": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -854,55 +1095,45 @@ "16": { "patterns": [ { - "include": "#attributes_context" - }, - { - "include": "source.cpp#number_literal" + "include": "#inline_comment" } ] }, "17": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "18": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "comment.block.cpp" }, "19": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "20": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] + "name": "variable.other.object.declare.cpp" }, "21": { - "name": "entity.name.type.$1.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "22": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "23": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "comment.block.cpp" }, "24": { - "name": "comment.block.cpp" - }, - "25": { "patterns": [ { "match": "\\*\\/", @@ -913,160 +1144,38 @@ "name": "comment.block.cpp" } ] - }, - "26": { - "name": "storage.type.modifier.final.cpp" - }, - "27": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "28": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "29": { - "name": "comment.block.cpp" - }, - "30": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "31": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "32": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "33": { - "name": "comment.block.cpp" - }, - "34": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "35": { - "name": "punctuation.separator.colon.inheritance.cpp" } - }, - "endCaptures": { - "1": { - "name": "punctuation.terminator.statement.cpp" - }, - "2": { - "name": "punctuation.terminator.statement.cpp" - } - }, - "name": "meta.block.class.cpp", - "patterns": [ - { - "begin": "\\G ?", - "end": "(?:\\{|<%|\\?\\?<|(?=;))|(?=(?|\\?\\?>|(?=(?|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)|(?=(?\\s*)(\\/\\/[!\\/]+)", "beginCaptures": { "1": { "name": "punctuation.definition.comment.documentation.cpp" } }, - "endCaptures": {}, - "name": "comment.line.double-slash.documentation.cpp", + "end": "(?<=\\n)(?|%|\"|\\.|=|::|\\||\\-\\-|\\-\\-\\-)\\b(?:\\{[^}]*\\})?", "name": "storage.type.class.doxygen.cpp" }, { - "match": "((?<=[\\s*!\\/])[\\\\@](?:a|em|e))(?:\\s)+(\\S+)", + "match": "((?<=[\\s*!\\/])[\\\\@](?:a|em|e))\\s+(\\S+)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" @@ -1077,7 +1186,7 @@ } }, { - "match": "((?<=[\\s*!\\/])[\\\\@]b)(?:\\s)+(\\S+)", + "match": "((?<=[\\s*!\\/])[\\\\@]b)\\s+(\\S+)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" @@ -1088,7 +1197,7 @@ } }, { - "match": "((?<=[\\s*!\\/])[\\\\@](?:c|p))(?:\\s)+(\\S+)", + "match": "((?<=[\\s*!\\/])[\\\\@](?:c|p))\\s+(\\S+)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" @@ -1107,20 +1216,12 @@ "name": "storage.type.class.doxygen.cpp" }, { - "match": "((?<=[\\s*!\\/])[\\\\@]param)(?:\\s*\\[((?:,?(?:(?:\\s)+)?(?:in|out)(?:(?:\\s)+)?)+)\\])?(?:\\s)+(\\b\\w+\\b)", + "match": "((?<=[\\s*!\\/])[\\\\@]param)\\s+(\\b\\w+\\b)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" }, "2": { - "patterns": [ - { - "match": "in|out", - "name": "keyword.other.parameter.direction.$0.cpp" - } - ] - }, - "3": { "name": "variable.parameter.cpp" } } @@ -1152,7 +1253,7 @@ "name": "storage.type.class.doxygen.cpp" }, { - "match": "((?<=[\\s*!\\/])[\\\\@](?:a|em|e))(?:\\s)+(\\S+)", + "match": "((?<=[\\s*!\\/])[\\\\@](?:a|em|e))\\s+(\\S+)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" @@ -1163,7 +1264,7 @@ } }, { - "match": "((?<=[\\s*!\\/])[\\\\@]b)(?:\\s)+(\\S+)", + "match": "((?<=[\\s*!\\/])[\\\\@]b)\\s+(\\S+)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" @@ -1174,7 +1275,7 @@ } }, { - "match": "((?<=[\\s*!\\/])[\\\\@](?:c|p))(?:\\s)+(\\S+)", + "match": "((?<=[\\s*!\\/])[\\\\@](?:c|p))\\s+(\\S+)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" @@ -1193,20 +1294,12 @@ "name": "storage.type.class.doxygen.cpp" }, { - "match": "((?<=[\\s*!\\/])[\\\\@]param)(?:\\s*\\[((?:,?(?:(?:\\s)+)?(?:in|out)(?:(?:\\s)+)?)+)\\])?(?:\\s)+(\\b\\w+\\b)", + "match": "((?<=[\\s*!\\/])[\\\\@]param)\\s+(\\b\\w+\\b)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" }, "2": { - "patterns": [ - { - "match": "in|out", - "name": "keyword.other.parameter.direction.$0.cpp" - } - ] - }, - "3": { "name": "variable.parameter.cpp" } } @@ -1232,26 +1325,26 @@ "name": "comment.block.documentation.cpp" }, { - "begin": "(?:(?:\\s)+)?+\\/\\*[!*]+(?:(?:(?:\\n)|$)|(?=\\s))", - "end": "[!*]*\\*\\/|(?=(?\\s*)\\/\\*[!*]+(?:(?:\\n|$)|(?=\\s)))", "beginCaptures": { - "0": { + "1": { "name": "punctuation.definition.comment.begin.documentation.cpp" } }, + "end": "([!*]*\\*\\/)|(?=(?|%|\"|\\.|=|::|\\||\\-\\-|\\-\\-\\-)\\b(?:\\{[^}]*\\})?", "name": "storage.type.class.doxygen.cpp" }, { - "match": "((?<=[\\s*!\\/])[\\\\@](?:a|em|e))(?:\\s)+(\\S+)", + "match": "((?<=[\\s*!\\/])[\\\\@](?:a|em|e))\\s+(\\S+)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" @@ -1262,7 +1355,7 @@ } }, { - "match": "((?<=[\\s*!\\/])[\\\\@]b)(?:\\s)+(\\S+)", + "match": "((?<=[\\s*!\\/])[\\\\@]b)\\s+(\\S+)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" @@ -1273,7 +1366,7 @@ } }, { - "match": "((?<=[\\s*!\\/])[\\\\@](?:c|p))(?:\\s)+(\\S+)", + "match": "((?<=[\\s*!\\/])[\\\\@](?:c|p))\\s+(\\S+)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" @@ -1292,20 +1385,12 @@ "name": "storage.type.class.doxygen.cpp" }, { - "match": "((?<=[\\s*!\\/])[\\\\@]param)(?:\\s*\\[((?:,?(?:(?:\\s)+)?(?:in|out)(?:(?:\\s)+)?)+)\\])?(?:\\s)+(\\b\\w+\\b)", + "match": "((?<=[\\s*!\\/])[\\\\@]param)\\s+(\\b\\w+\\b)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" }, "2": { - "patterns": [ - { - "match": "in|out", - "name": "keyword.other.parameter.direction.$0.cpp" - } - ] - }, - "3": { "name": "variable.parameter.cpp" } } @@ -1325,7 +1410,7 @@ ] }, { - "include": "source.cpp#emacs_file_banner" + "include": "#emacs_file_banner" }, { "include": "#block_comment" @@ -1334,31 +1419,31 @@ "include": "#line_comment" }, { - "include": "source.cpp#invalid_comment_end" + "include": "#invalid_comment_end" } ] }, "constructor_inline": { - "begin": "^((?:(?:(?:(?>(?:\\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)))((?|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?:constexpr|explicit|mutable|virtual|inline|friend)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(default)|(delete))", - "captures": { - "1": { - "name": "keyword.operator.assignment.cpp" - }, - "2": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + "patterns": [ + { + "match": "(\\=)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(default)|(delete))", + "captures": { + "1": { + "name": "keyword.operator.assignment.cpp" }, - { - "match": "\\*", + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "keyword.other.default.constructor.cpp" + }, + "7": { + "name": "keyword.other.delete.constructor.cpp" } - ] - }, - "6": { - "name": "keyword.other.default.constructor.cpp" - }, - "7": { - "name": "keyword.other.delete.constructor.cpp" + } } - } + ] }, { - "include": "source.cpp#functional_specifiers_pre_parameters" + "include": "#functional_specifiers_pre_parameters" }, { - "begin": ":", - "end": "(?=\\{)|(?=(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:(?:\\s)+)?)?(\\()", - "end": "\\)|(?=(?(?:(?>[^<>]*)\\g<3>?)+)>)\\s*)?(\\()", "beginCaptures": { "1": { "name": "entity.name.function.call.initializer.cpp" @@ -1543,17 +1629,16 @@ } ] }, - "3": {}, "4": { "name": "punctuation.section.arguments.begin.bracket.round.function.call.initializer.cpp" } }, + "end": "(\\))|(?=(?|\\?\\?>|(?=(?|\\?\\?>)|(?=(?|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)|(?=(?(?:\\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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:(?:\\s)+)?)?::)*)(((?>(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))::((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\14((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\())", - "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<14>?)+)>)\\s*)?::)*)(((?>(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))::((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\16((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\()))", "beginCaptures": { - "0": { + "1": { "name": "meta.head.function.definition.special.constructor.cpp" }, - "1": { + "2": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "5": { - "name": "storage.type.modifier.calling-convention.cpp" - }, - "6": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "7": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "8": { + "4": { "name": "comment.block.cpp" }, - "9": { + "5": { "patterns": [ { "match": "\\*\\/", @@ -1710,7 +1763,35 @@ } ] }, + "6": { + "name": "storage.type.modifier.calling-convention.cpp" + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "9": { + "name": "comment.block.cpp" + }, "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "11": { "patterns": [ { "match": "::", @@ -1725,7 +1806,7 @@ } ] }, - "11": { + "13": { "name": "meta.template.call.cpp", "patterns": [ { @@ -1733,8 +1814,7 @@ } ] }, - "12": {}, - "13": { + "15": { "patterns": [ { "match": "(?:[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}))*(?=:)", @@ -1750,46 +1830,20 @@ } ] }, - "14": {}, - "15": { + "17": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, - "16": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "17": { - "name": "comment.block.cpp" - }, "18": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "19": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "20": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "21": { "name": "comment.block.cpp" }, - "22": { + "20": { "patterns": [ { "match": "\\*\\/", @@ -1801,20 +1855,45 @@ } ] }, - "23": { + "21": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, - "24": { + "22": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "25": { + "23": { "name": "comment.block.cpp" }, + "24": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "25": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, "26": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "27": { + "name": "comment.block.cpp" + }, + "28": { "patterns": [ { "match": "\\*\\/", @@ -1827,78 +1906,79 @@ ] } }, - "endCaptures": {}, - "name": "meta.function.definition.special.constructor.cpp", + "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(default)|(delete))", - "captures": { - "1": { - "name": "keyword.operator.assignment.cpp" - }, - "2": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + "patterns": [ + { + "match": "(\\=)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(default)|(delete))", + "captures": { + "1": { + "name": "keyword.operator.assignment.cpp" }, - { - "match": "\\*", + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "keyword.other.default.constructor.cpp" + }, + "7": { + "name": "keyword.other.delete.constructor.cpp" } - ] - }, - "6": { - "name": "keyword.other.default.constructor.cpp" - }, - "7": { - "name": "keyword.other.delete.constructor.cpp" + } } - } + ] }, { - "include": "source.cpp#functional_specifiers_pre_parameters" + "include": "#functional_specifiers_pre_parameters" }, { - "begin": ":", - "end": "(?=\\{)|(?=(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:(?:\\s)+)?)?(\\()", - "end": "\\)|(?=(?(?:(?>[^<>]*)\\g<3>?)+)>)\\s*)?(\\()", "beginCaptures": { "1": { "name": "entity.name.function.call.initializer.cpp" @@ -1911,17 +1991,16 @@ } ] }, - "3": {}, "4": { "name": "punctuation.section.arguments.begin.bracket.round.function.call.initializer.cpp" } }, + "end": "(\\))|(?=(?|\\?\\?>|(?=(?|\\?\\?>)|(?=(?|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<25>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<25>?)+>)(?:(?:\\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<25>?)+>)(?:(?:\\s)+)?)?(?![\\w<:.]))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\{)", - "end": "\\}|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|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<27>?)+)>)\\s*)?(?![\\w<:.]))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\{)", "beginCaptures": { "1": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", "beginCaptures": { "1": { "name": "keyword.operator.functionlike.cpp keyword.other.decltype.cpp storage.type.decltype.cpp" @@ -2325,7 +2411,7 @@ "2": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -2351,12 +2437,12 @@ "name": "punctuation.section.arguments.begin.bracket.round.decltype.cpp" } }, + "end": "(\\))|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", "beginCaptures": { "1": { "name": "keyword.operator.functionlike.cpp keyword.other.decltype.cpp storage.type.decltype.cpp" @@ -2373,7 +2459,7 @@ "2": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -2399,12 +2485,12 @@ "name": "punctuation.section.arguments.begin.bracket.round.decltype.cpp" } }, + "end": "(\\))|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\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)|(?:explicit)|(?:mutable)|(?:virtual)|(?:inline)|(?:friend))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*)(~(?|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?:constexpr|explicit|mutable|virtual|inline|friend)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*)(~(?|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(default)|(delete))", - "captures": { - "1": { - "name": "keyword.operator.assignment.cpp" - }, - "2": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + "patterns": [ + { + "match": "(\\=)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(default)|(delete))", + "captures": { + "1": { + "name": "keyword.operator.assignment.cpp" }, - { - "match": "\\*", + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "keyword.other.default.constructor.cpp" + }, + "7": { + "name": "keyword.other.delete.constructor.cpp" } - ] - }, - "6": { - "name": "keyword.other.default.constructor.cpp" - }, - "7": { - "name": "keyword.other.delete.constructor.cpp" + } } - } + ] }, { - "begin": "\\(", - "end": "\\)|(?=(?|\\?\\?>|(?=(?|\\?\\?>)|(?=(?|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)|(?=(?(?:\\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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:(?:\\s)+)?)?::)*)(((?>(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))::((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))~\\14((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\())", - "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<14>?)+)>)\\s*)?::)*)(((?>(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))::((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))~\\16((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\()))", "beginCaptures": { - "0": { + "1": { "name": "meta.head.function.definition.special.member.destructor.cpp" }, - "1": { + "2": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "5": { - "name": "storage.type.modifier.calling-convention.cpp" - }, - "6": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "7": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "8": { + "4": { "name": "comment.block.cpp" }, - "9": { + "5": { "patterns": [ { "match": "\\*\\/", @@ -2747,7 +2803,35 @@ } ] }, + "6": { + "name": "storage.type.modifier.calling-convention.cpp" + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "9": { + "name": "comment.block.cpp" + }, "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "11": { "patterns": [ { "match": "::", @@ -2762,7 +2846,7 @@ } ] }, - "11": { + "13": { "name": "meta.template.call.cpp", "patterns": [ { @@ -2770,8 +2854,7 @@ } ] }, - "12": {}, - "13": { + "15": { "patterns": [ { "match": "(?:[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}))*(?=:)", @@ -2787,46 +2870,20 @@ } ] }, - "14": {}, - "15": { + "17": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, - "16": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "17": { - "name": "comment.block.cpp" - }, "18": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "19": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "20": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "21": { "name": "comment.block.cpp" }, - "22": { + "20": { "patterns": [ { "match": "\\*\\/", @@ -2838,20 +2895,45 @@ } ] }, - "23": { + "21": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, - "24": { + "22": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "25": { + "23": { "name": "comment.block.cpp" }, + "24": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "25": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, "26": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "27": { + "name": "comment.block.cpp" + }, + "28": { "patterns": [ { "match": "\\*\\/", @@ -2864,77 +2946,78 @@ ] } }, - "endCaptures": {}, - "name": "meta.function.definition.special.member.destructor.cpp", + "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(default)|(delete))", - "captures": { - "1": { - "name": "keyword.operator.assignment.cpp" - }, - "2": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + "patterns": [ + { + "match": "(\\=)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(default)|(delete))", + "captures": { + "1": { + "name": "keyword.operator.assignment.cpp" }, - { - "match": "\\*", + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "keyword.other.default.constructor.cpp" + }, + "7": { + "name": "keyword.other.delete.constructor.cpp" } - ] - }, - "6": { - "name": "keyword.other.default.constructor.cpp" - }, - "7": { - "name": "keyword.other.delete.constructor.cpp" + } } - } + ] }, { - "begin": "\\(", - "end": "\\)|(?=(?|\\?\\?>|(?=(?|\\?\\?>)|(?=(?|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?((?:error|warning)))\\b(?:(?:\\s)+)?", - "end": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*((?:error|warning)))\\b\\s*", "beginCaptures": { "1": { "name": "keyword.control.directive.diagnostic.$7.cpp" @@ -2981,7 +3061,7 @@ "2": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -3005,61 +3085,57 @@ }, "6": { "name": "punctuation.definition.directive.cpp" - }, - "7": {} + } }, - "endCaptures": {}, - "name": "meta.preprocessor.diagnostic.$reference(directive).cpp", + "end": "(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:(?:\\s)+)?)?(::))?(?:(?:\\s)+)?((?|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))|(?=(?[#;\\/=*C~]+)(?![#;\\/=*C~]))\\s*.+\\s*\\8\\s*(?:\\n|$)))|(^\\s*((\\/\\*)\\s*?((?>[#;\\/=*C~]+)(?![#;\\/=*C~]))\\s*.+\\s*\\8\\s*\\*\\/)))", + "captures": { "1": { - "name": "storage.type.enum.cpp" + "name": "meta.toc-list.banner.double-slash.cpp" }, "2": { - "name": "storage.type.enum.enum-key.$2.cpp" + "name": "comment.line.double-slash.cpp" }, "3": { + "name": "punctuation.definition.comment.cpp" + }, + "4": { + "name": "meta.banner.character.cpp" + }, + "5": { + "name": "meta.toc-list.banner.block.cpp" + }, + "6": { + "name": "comment.line.banner.cpp" + }, + "7": { + "name": "punctuation.definition.comment.cpp" + }, + "8": { + "name": "meta.banner.character.cpp" + } + } + }, + "empty_square_brackets": { + "name": "storage.modifier.array.bracket.square.cpp", + "match": "(?-mix:(?-mix:(?(?:(?>[^<>]*)\\g<15>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<15>?)+)>)\\s*)?(::))?\\s*((?|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>|(?=(?|\\?\\?>)|(?=(?|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\S)(?![:{])", + "captures": { + "1": { + "name": "storage.type.enum.declare.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "entity.name.type.enum.cpp" + }, + "7": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cpp" + }, + { + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cpp" + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "10": { + "name": "comment.block.cpp" + }, + "11": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "12": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "13": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "14": { + "name": "comment.block.cpp" + }, + "15": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "16": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "17": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "18": { + "name": "comment.block.cpp" + }, + "19": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "20": { + "name": "variable.other.object.declare.cpp" + }, + "21": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "22": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "23": { + "name": "comment.block.cpp" + }, + "24": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + "enumerator_list": { + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(extern)(?=\\s*\\\")", - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(extern)(?=\\s*\\\"))", + "beginCaptures": { + "1": { + "name": "meta.head.extern.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { "name": "storage.type.extern.cpp" } }, + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>|(?=(?|\\?\\?>)|(?=(?|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)|(?=(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\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": "\\)|(?=(?(?:(?>[^<>]*)\\g<12>?)+)>)\\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(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(((?(?:(?>[^<>]*)\\g<12>?)+)>)\\s*)?(\\()", "beginCaptures": { "1": { "patterns": [ { - "include": "source.cpp#scope_resolution_function_call_inner_generated" + "include": "#scope_resolution_function_call_inner_generated" } ] }, "2": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.call.cpp" }, - "3": { + "4": { "name": "meta.template.call.cpp", "patterns": [ { @@ -3521,24 +3879,23 @@ } ] }, - "4": {}, - "5": { + "6": { "name": "entity.name.function.call.cpp" }, - "6": { + "7": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, - "7": { + "8": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "8": { + "9": { "name": "comment.block.cpp" }, - "9": { + "10": { "patterns": [ { "match": "\\*\\/", @@ -3550,7 +3907,7 @@ } ] }, - "10": { + "11": { "name": "meta.template.call.cpp", "patterns": [ { @@ -3558,13 +3915,13 @@ } ] }, - "11": {}, - "12": { + "13": { "name": "punctuation.section.arguments.begin.bracket.round.function.call.cpp" } }, + "end": "(\\))|(?=(?))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*)(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<66>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<66>?)+>)(?:(?:\\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<66>?)+>)(?:(?:\\s)+)?)?(?![\\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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<66>?)+>)(?:(?:\\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": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*)(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<70>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<70>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|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<70>?)+)>)\\s*)?(?![\\w<:.]))(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<70>?)+)>)\\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(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\())", "beginCaptures": { - "0": { + "1": { "name": "meta.head.function.definition.cpp" }, - "1": { + "2": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "5": { - "name": "storage.type.template.cpp" - }, - "6": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "7": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "8": { + "4": { "name": "comment.block.cpp" }, - "9": { + "5": { "patterns": [ { "match": "\\*\\/", @@ -3634,10 +3963,38 @@ } ] }, + "6": { + "name": "storage.type.template.cpp" + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "9": { + "name": "comment.block.cpp" + }, "10": { "patterns": [ { - "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))", + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "11": { + "patterns": [ + { + "match": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))", "captures": { "1": { "name": "storage.modifier.$1.cpp" @@ -3645,7 +4002,7 @@ "2": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -3671,23 +4028,23 @@ } ] }, - "11": { - "name": "storage.modifier.$11.cpp" - }, "12": { + "name": "storage.modifier.$1.cpp" + }, + "13": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, - "13": { + "14": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "14": { + "15": { "name": "comment.block.cpp" }, - "15": { + "16": { "patterns": [ { "match": "\\*\\/", @@ -3699,11 +4056,11 @@ } ] }, - "16": { + "17": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -3927,123 +4264,95 @@ } ] }, - "42": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "43": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "44": { - "name": "comment.block.cpp" - }, "45": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "46": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "47": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "48": { + "47": { "name": "comment.block.cpp" }, + "48": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "49": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "50": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "51": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "52": { + "51": { "name": "comment.block.cpp" }, + "52": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "53": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "54": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "55": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "56": { + "55": { "name": "comment.block.cpp" }, + "56": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "57": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "58": { - "name": "storage.type.modifier.calling-convention.cpp" - }, - "59": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "60": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "61": { + "59": { "name": "comment.block.cpp" }, - "62": { + "60": { "patterns": [ { "match": "\\*\\/", @@ -4055,17 +4364,45 @@ } ] }, - "63": { + "61": { + "name": "storage.type.modifier.calling-convention.cpp" + }, + "62": { "patterns": [ { - "include": "source.cpp#scope_resolution_function_definition_inner_generated" + "include": "#inline_comment" } ] }, + "63": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "64": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.cpp" + "name": "comment.block.cpp" }, "65": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "66": { + "patterns": [ + { + "include": "#scope_resolution_function_definition_inner_generated" + } + ] + }, + "67": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.cpp" + }, + "69": { "name": "meta.template.call.cpp", "patterns": [ { @@ -4073,24 +4410,23 @@ } ] }, - "66": {}, - "67": { + "71": { "name": "entity.name.function.definition.cpp" }, - "68": { + "72": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, - "69": { + "73": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "70": { + "74": { "name": "comment.block.cpp" }, - "71": { + "75": { "patterns": [ { "match": "\\*\\/", @@ -4103,37 +4439,35 @@ ] } }, - "endCaptures": {}, - "name": "meta.function.definition.cpp", + "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>|(?=(?|\\?\\?>)|(?=(?|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<25>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<25>?)+>)(?:(?:\\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<25>?)+>)(?:(?:\\s)+)?)?(?![\\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)(?!\\()|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|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<27>?)+)>)\\s*)?(?![\\w<:.]))(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\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*(\\()", "beginCaptures": { "1": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -4429,70 +4739,45 @@ } ] }, - "27": { + "29": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, - "28": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "29": { - "name": "comment.block.cpp" - }, "30": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "31": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] + "name": "comment.block.cpp" }, "32": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "33": { - "name": "comment.block.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "34": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "35": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "36": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "37": { + "35": { "name": "comment.block.cpp" }, - "38": { + "36": { "patterns": [ { "match": "\\*\\/", @@ -4504,35 +4789,61 @@ } ] }, + "37": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "38": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "39": { - "name": "punctuation.section.parens.begin.bracket.round.function.pointer.cpp" + "name": "comment.block.cpp" }, "40": { - "name": "punctuation.definition.function.pointer.dereference.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "41": { - "name": "variable.other.definition.pointer.function.cpp" + "name": "punctuation.section.parens.begin.bracket.round.function.pointer.cpp" }, "42": { - "name": "punctuation.definition.begin.bracket.square.cpp" + "name": "punctuation.definition.function.pointer.dereference.cpp" }, "43": { + "name": "variable.other.definition.pointer.function.cpp" + }, + "44": { + "name": "punctuation.definition.begin.bracket.square.cpp" + }, + "45": { "patterns": [ { "include": "#evaluation_context" } ] }, - "44": { + "46": { "name": "punctuation.definition.end.bracket.square.cpp" }, - "45": { + "47": { "name": "punctuation.section.parens.end.bracket.round.function.pointer.cpp" }, - "46": { + "48": { "name": "punctuation.section.parameters.begin.bracket.round.function.pointer.cpp" } }, + "end": "(\\))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=[{=,);]|\\n)(?!\\()|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<25>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<25>?)+>)(?:(?:\\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<25>?)+>)(?:(?:\\s)+)?)?(?![\\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)(?!\\()|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|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<27>?)+)>)\\s*)?(?![\\w<:.]))(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\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*(\\()", "beginCaptures": { "1": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -4801,70 +5091,45 @@ } ] }, - "27": { + "29": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, - "28": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "29": { - "name": "comment.block.cpp" - }, "30": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "31": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] + "name": "comment.block.cpp" }, "32": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "33": { - "name": "comment.block.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "34": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "35": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "36": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "37": { + "35": { "name": "comment.block.cpp" }, - "38": { + "36": { "patterns": [ { "match": "\\*\\/", @@ -4876,35 +5141,61 @@ } ] }, + "37": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "38": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "39": { - "name": "punctuation.section.parens.begin.bracket.round.function.pointer.cpp" + "name": "comment.block.cpp" }, "40": { - "name": "punctuation.definition.function.pointer.dereference.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "41": { - "name": "variable.parameter.pointer.function.cpp" + "name": "punctuation.section.parens.begin.bracket.round.function.pointer.cpp" }, "42": { - "name": "punctuation.definition.begin.bracket.square.cpp" + "name": "punctuation.definition.function.pointer.dereference.cpp" }, "43": { + "name": "variable.parameter.pointer.function.cpp" + }, + "44": { + "name": "punctuation.definition.begin.bracket.square.cpp" + }, + "45": { "patterns": [ { "include": "#evaluation_context" } ] }, - "44": { + "46": { "name": "punctuation.definition.end.bracket.square.cpp" }, - "45": { + "47": { "name": "punctuation.section.parens.end.bracket.round.function.pointer.cpp" }, - "46": { + "48": { "name": "punctuation.section.parameters.begin.bracket.round.function.pointer.cpp" } }, + "end": "(\\))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=[{=,);]|\\n)(?!\\()|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\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": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "entity.name.label.call.cpp" + } + } + }, + "include": { + "match": "(?:^)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((#)\\s*((?:include|include_next))\\b)\\s*(?:(?:(?:((<)[^>]*(>?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=\\/\\/)))|((\\\")[^\\\"]*(\\\"?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=\\/\\/))))|(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\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}))*(?:\\.(?:[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|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=(?:\\/\\/|;)))))|((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=(?:\\/\\/|;))))", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "5": { + "name": "keyword.control.directive.$7.cpp" + }, + "6": { + "name": "punctuation.definition.directive.cpp" + }, + "8": { + "name": "string.quoted.other.lt-gt.include.cpp" + }, + "9": { + "name": "punctuation.definition.string.begin.cpp" + }, + "10": { + "name": "punctuation.definition.string.end.cpp" + }, + "11": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "12": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "13": { + "name": "comment.block.cpp" + }, + "14": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "15": { + "name": "string.quoted.double.include.cpp" + }, + "16": { + "name": "punctuation.definition.string.begin.cpp" + }, + "17": { + "name": "punctuation.definition.string.end.cpp" + }, + "18": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "19": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "20": { + "name": "comment.block.cpp" + }, + "21": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "22": { + "name": "entity.name.other.preprocessor.macro.include.cpp" + }, + "23": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "24": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "25": { + "name": "comment.block.cpp" + }, + "26": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "27": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "28": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "29": { + "name": "comment.block.cpp" + }, + "30": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "31": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "32": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "33": { + "name": "comment.block.cpp" + }, + "34": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + }, + "name": "meta.preprocessor.include.cpp" + }, "inheritance_context": { "patterns": [ { @@ -5015,7 +5527,7 @@ "name": "punctuation.separator.delimiter.comma.inheritance.cpp" }, { - "match": "(?(?:\\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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<19>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<19>?)+>)(?:(?:\\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<19>?)+>)(?:(?:\\s)+)?)?(?![\\w<:.]))", + "match": "(?<=protected|virtual|private|public|,|:)\\s*(?!(?:(?:protected|private|public)|virtual))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|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<27>?)+)>)\\s*)?(?![\\w<:.]))", "captures": { "1": { "name": "meta.qualified_type.cpp", "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" - } - ] - } - } - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "5": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] + "name": "comment.block.cpp" }, "6": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "7": { "patterns": [ { - "include": "source.cpp#scope_resolution_inner_generated" + "include": "#inline_comment" } ] }, "8": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "9": { + "name": "comment.block.cpp" + }, + "10": { "patterns": [ { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "10": {}, - "11": { - "name": "entity.name.scope-resolution.cpp" - }, "12": { + "patterns": [ + { + "include": "#scope_resolution_inner_generated" + } + ] + }, + "13": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + }, + "15": { "name": "meta.template.call.cpp", "patterns": [ { @@ -5197,46 +5652,8 @@ } ] }, - "13": {}, - "14": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" - }, - "15": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "16": { - "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" - } - ] - } - } - } - ] - }, "17": { - "name": "entity.name.type.cpp" + "name": "entity.name.scope-resolution.cpp" }, "18": { "name": "meta.template.call.cpp", @@ -5246,26 +5663,151 @@ } ] }, - "19": {} + "20": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + }, + "21": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "22": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "23": { + "name": "comment.block.cpp" + }, + "24": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "25": { + "name": "entity.name.type.cpp" + }, + "26": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + } } } ] }, - "lambdas": { - "begin": "(?:(?<=[^\\s]|^)(?])|(?<=\\Wreturn|^return))(?:(?:\\s)+)?(\\[(?!\\[| *+\"| *+\\d))((?:[^\\[\\]]|((??)++\\]))*+)(\\](?!((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))[\\[\\];]))", - "end": "(?<=[;}])|(?=(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/))", + "captures": { "1": { - "name": "punctuation.definition.capture.begin.lambda.cpp" + "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" + } + ] + } + } + }, + "invalid_comment_end": { + "match": "\\*\\/", + "name": "invalid.illegal.unexpected.punctuation.definition.comment.end.cpp" + }, + "label": { + "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "5": { + "name": "entity.name.label.cpp" + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "8": { + "name": "comment.block.cpp" + }, + "9": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "10": { + "name": "punctuation.separator.label.cpp" + } + } + }, + "lambdas": { + "begin": "((?:(?<=[^\\s]|^)(?])|(?<=\\Wreturn|^return))\\s*(\\[(?!\\[| *+\"| *+\\d))((?>(?:[^\\[\\]]|((?(?:(?>[^\\[\\]]*)\\g<4>?)+)\\]))*))(\\](?!\\[)))", + "beginCaptures": { + "2": { + "name": "punctuation.definition.capture.begin.lambda.cpp" + }, + "3": { "name": "meta.lambda.capture.cpp", "patterns": [ { - "include": "source.cpp#the_this_keyword" + "include": "#the_this_keyword" }, { - "match": "((?:[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)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?=\\]|\\z|$)|(,))|(\\=))", + "match": "((?:[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|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?=\\]|\\z|$)|(,))|(\\=))", "captures": { "1": { "name": "variable.parameter.capture.cpp" @@ -5273,7 +5815,7 @@ "2": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -5308,52 +5850,26 @@ } ] }, - "3": {}, - "4": { - "name": "punctuation.definition.capture.end.lambda.cpp" - }, "5": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "6": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "7": { - "name": "comment.block.cpp" - }, - "8": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] + "name": "punctuation.definition.capture.end.lambda.cpp" } }, - "endCaptures": {}, + "end": "(?<=})|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?line\\b", - "end": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*line\\b)", "beginCaptures": { - "0": { + "1": { "name": "keyword.control.directive.line.cpp" }, - "1": { + "2": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, - "2": { + "3": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "3": { + "4": { "name": "comment.block.cpp" }, - "4": { + "5": { "patterns": [ { "match": "\\*\\/", @@ -5429,12 +5949,11 @@ } ] }, - "5": { + "6": { "name": "punctuation.definition.directive.cpp" } }, - "endCaptures": {}, - "name": "meta.preprocessor.line.cpp", + "end": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?define\\b)(?:(?:\\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)+)?(~?(?:[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)+)?(\\()", - "end": "\\)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\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_least64_t[^(?-mix:\\w)]|uint_least16_t[^(?-mix:\\w)]|uint_least32_t[^(?-mix:\\w)]|int_least16_t[^(?-mix:\\w)]|uint_fast64_t[^(?-mix:\\w)]|uint_fast32_t[^(?-mix:\\w)]|uint_fast16_t[^(?-mix:\\w)]|uint_least8_t[^(?-mix:\\w)]|int_least64_t[^(?-mix:\\w)]|int_least32_t[^(?-mix:\\w)]|int_fast32_t[^(?-mix:\\w)]|int_fast16_t[^(?-mix:\\w)]|int_least8_t[^(?-mix:\\w)]|uint_fast8_t[^(?-mix:\\w)]|int_fast64_t[^(?-mix:\\w)]|int_fast8_t[^(?-mix:\\w)]|suseconds_t[^(?-mix:\\w)]|useconds_t[^(?-mix:\\w)]|in_addr_t[^(?-mix:\\w)]|uintmax_t[^(?-mix:\\w)]|uintmax_t[^(?-mix:\\w)]|uintptr_t[^(?-mix:\\w)]|blksize_t[^(?-mix:\\w)]|in_port_t[^(?-mix:\\w)]|intmax_t[^(?-mix:\\w)]|unsigned[^(?-mix:\\w)]|blkcnt_t[^(?-mix:\\w)]|uint32_t[^(?-mix:\\w)]|u_quad_t[^(?-mix:\\w)]|uint16_t[^(?-mix:\\w)]|intmax_t[^(?-mix:\\w)]|uint64_t[^(?-mix:\\w)]|intptr_t[^(?-mix:\\w)]|swblk_t[^(?-mix:\\w)]|wchar_t[^(?-mix:\\w)]|u_short[^(?-mix:\\w)]|qaddr_t[^(?-mix:\\w)]|caddr_t[^(?-mix:\\w)]|daddr_t[^(?-mix:\\w)]|fixpt_t[^(?-mix:\\w)]|nlink_t[^(?-mix:\\w)]|segsz_t[^(?-mix:\\w)]|clock_t[^(?-mix:\\w)]|ssize_t[^(?-mix:\\w)]|int16_t[^(?-mix:\\w)]|int32_t[^(?-mix:\\w)]|int64_t[^(?-mix:\\w)]|uint8_t[^(?-mix:\\w)]|int8_t[^(?-mix:\\w)]|mode_t[^(?-mix:\\w)]|quad_t[^(?-mix:\\w)]|ushort[^(?-mix:\\w)]|u_long[^(?-mix:\\w)]|u_char[^(?-mix:\\w)]|double[^(?-mix:\\w)]|size_t[^(?-mix:\\w)]|signed[^(?-mix:\\w)]|time_t[^(?-mix:\\w)]|key_t[^(?-mix:\\w)]|ino_t[^(?-mix:\\w)]|gid_t[^(?-mix:\\w)]|dev_t[^(?-mix:\\w)]|div_t[^(?-mix:\\w)]|float[^(?-mix:\\w)]|u_int[^(?-mix:\\w)]|uid_t[^(?-mix:\\w)]|short[^(?-mix:\\w)]|off_t[^(?-mix:\\w)]|pid_t[^(?-mix:\\w)]|id_t[^(?-mix:\\w)]|bool[^(?-mix:\\w)]|char[^(?-mix:\\w)]|id_t[^(?-mix:\\w)]|uint[^(?-mix:\\w)]|void[^(?-mix:\\w)]|long[^(?-mix:\\w)]|auto[^(?-mix:\\w)]|int[^(?-mix:\\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": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -5596,12 +6124,12 @@ "9": { "patterns": [ { - "match": "(?<=(?:\\.\\*|\\.|->|->\\*))(?:(?:\\s)+)?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?\\*|->)))", + "match": "(?<=(?:\\.\\*|\\.|->|->\\*))\\s*(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?\\*|->)))", "captures": { "1": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -5638,12 +6166,12 @@ } }, { - "match": "(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?\\*|->)))", + "match": "(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?\\*|->)))", "captures": { "1": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -5680,7 +6208,191 @@ } }, { - "include": "source.cpp#member_access" + "include": "#member_access" + }, + { + "include": "#method_access" + } + ] + }, + "10": { + "name": "variable.other.property.cpp" + } + } + }, + "memory_operators": { + "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?:(delete)\\s*(\\[\\])|(delete))|(new))(?!\\w))", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "5": { + "name": "keyword.operator.wordlike.cpp" + }, + "6": { + "name": "keyword.operator.delete.array.cpp" + }, + "7": { + "name": "keyword.operator.delete.array.bracket.cpp" + }, + "8": { + "name": "keyword.operator.delete.cpp" + }, + "9": { + "name": "keyword.operator.new.cpp" + } + } + }, + "method_access": { + "begin": "(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\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*(~?(?:[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*(\\()", + "beginCaptures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "5": { + "name": "variable.language.this.cpp" + }, + "6": { + "name": "variable.other.object.access.cpp" + }, + "7": { + "name": "punctuation.separator.dot-access.cpp" + }, + "8": { + "name": "punctuation.separator.pointer-access.cpp" + }, + "9": { + "patterns": [ + { + "match": "(?<=(?:\\.\\*|\\.|->|->\\*))\\s*(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?\\*|->)))", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "5": { + "name": "variable.language.this.cpp" + }, + "6": { + "name": "variable.other.object.property.cpp" + }, + "7": { + "name": "punctuation.separator.dot-access.cpp" + }, + "8": { + "name": "punctuation.separator.pointer-access.cpp" + } + } + }, + { + "match": "(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?\\*|->)))", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "5": { + "name": "variable.language.this.cpp" + }, + "6": { + "name": "variable.other.object.access.cpp" + }, + "7": { + "name": "punctuation.separator.dot-access.cpp" + }, + "8": { + "name": "punctuation.separator.pointer-access.cpp" + } + } + }, + { + "include": "#member_access" }, { "include": "#method_access" @@ -5694,8 +6406,9 @@ "name": "punctuation.section.arguments.begin.bracket.round.function.member.cpp" } }, + "end": "(\\))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((import))\\s*(?:(?:(?:((<)[^>]*(>?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=\\/\\/)))|((\\\")[^\\\"]*(\\\"?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=\\/\\/))))|(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\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}))*(?:\\.(?:[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|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=(?:\\/\\/|;)))))|((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=(?:\\/\\/|;))))\\s*(;?)", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "5": { + "name": "keyword.control.directive.import.cpp" + }, + "7": { + "name": "string.quoted.other.lt-gt.include.cpp" + }, + "8": { + "name": "punctuation.definition.string.begin.cpp" + }, + "9": { + "name": "punctuation.definition.string.end.cpp" + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "12": { + "name": "comment.block.cpp" + }, + "13": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "14": { + "name": "string.quoted.double.include.cpp" + }, + "15": { + "name": "punctuation.definition.string.begin.cpp" + }, + "16": { + "name": "punctuation.definition.string.end.cpp" + }, + "17": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "18": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "19": { + "name": "comment.block.cpp" + }, + "20": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "21": { + "name": "entity.name.other.preprocessor.macro.include.cpp" + }, + "22": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "23": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "24": { + "name": "comment.block.cpp" + }, + "25": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "26": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "27": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "28": { + "name": "comment.block.cpp" + }, + "29": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "30": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "31": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "32": { + "name": "comment.block.cpp" + }, + "33": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "34": { + "name": "punctuation.terminator.statement.cpp" + } + }, + "name": "meta.preprocessor.import.cpp" + }, "ms_attributes": { - "begin": "__declspec\\(", - "end": "\\)|(?=(?(?:(?>[^<>]*)\\g<9>?)+)>)\\s*)?::)*\\s*+)\\s*((?|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<4>?)+>)(?:(?:\\s)+)?)?::)*\\s*+)(?:(?:\\s)+)?((?(?:(?>[^<>]*)\\g<5>?)+)>)\\s*)?::)*\\s*+)\\s*((?|\\?\\?>|(?=(?|\\?\\?>)|(?=(?|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", "beginCaptures": { "1": { "name": "keyword.operator.functionlike.cpp keyword.operator.noexcept.cpp" @@ -5875,7 +6809,7 @@ "2": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -5901,30 +6835,334 @@ "name": "punctuation.section.arguments.begin.bracket.round.operator.noexcept.cpp" } }, + "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<62>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<62>?)+>)(?:(?:\\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<62>?)+>)(?:(?:\\s)+)?)?(?![\\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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<62>?)+>)(?:(?:\\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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<62>?)+>)(?:(?:\\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": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|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<67>?)+)>)\\s*)?(?![\\w<:.]))(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\s*)?::)*)(operator)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\s*)?::)*)(?:(?:((?:delete\\[\\]|delete|new\\[\\]|<=>|<<=|new|>>=|\\->\\*|\\/=|%=|&=|>=|\\|=|\\+\\+|\\-\\-|\\(\\)|\\[\\]|\\->|\\+\\+|<<|>>|\\-\\-|<=|\\^=|==|!=|&&|\\|\\||\\+=|\\-=|\\*=|,|\\+|\\-|!|~|\\*|&|\\*|\\/|%|\\+|\\-|<|>|&|\\^|\\||=))|((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:\\[\\])?)))|(\"\")((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\<|\\())", + "beginCaptures": { + "1": { "name": "meta.head.function.definition.special.operator-overload.cpp" }, - "1": { + "2": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -6148,148 +7366,123 @@ } ] }, - "27": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "28": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "29": { - "name": "comment.block.cpp" - }, "30": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "31": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "32": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "33": { + "32": { "name": "comment.block.cpp" }, + "33": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "34": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "35": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "36": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "37": { + "36": { "name": "comment.block.cpp" }, + "37": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "38": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "39": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "40": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "41": { + "40": { "name": "comment.block.cpp" }, + "41": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "42": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "43": { - "name": "storage.type.modifier.calling-convention.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "44": { + "name": "comment.block.cpp" + }, + "45": { "patterns": [ { - "include": "source.cpp#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "45": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, "46": { - "name": "comment.block.cpp" + "name": "storage.type.modifier.calling-convention.cpp" }, "47": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "48": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "49": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "50": { + "49": { "name": "comment.block.cpp" }, - "51": { + "50": { "patterns": [ { "match": "\\*\\/", @@ -6301,7 +7494,32 @@ } ] }, + "51": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, "52": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "53": { + "name": "comment.block.cpp" + }, + "54": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "55": { "patterns": [ { "match": "::", @@ -6316,7 +7534,7 @@ } ] }, - "53": { + "57": { "name": "meta.template.call.cpp", "patterns": [ { @@ -6324,24 +7542,23 @@ } ] }, - "54": {}, - "55": { + "59": { "name": "keyword.other.operator.overload.cpp" }, - "56": { + "60": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, - "57": { + "61": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "58": { + "62": { "name": "comment.block.cpp" }, - "59": { + "63": { "patterns": [ { "match": "\\*\\/", @@ -6353,7 +7570,7 @@ } ] }, - "60": { + "64": { "patterns": [ { "match": "::", @@ -6368,7 +7585,7 @@ } ] }, - "61": { + "66": { "name": "meta.template.call.cpp", "patterns": [ { @@ -6376,26 +7593,25 @@ } ] }, - "62": {}, - "63": { + "68": { "name": "entity.name.operator.cpp" }, - "64": { + "69": { "name": "entity.name.operator.type.cpp" }, - "65": { + "70": { "patterns": [ { "match": "\\*", "name": "entity.name.operator.type.pointer.cpp" }, { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -6426,101 +7642,45 @@ } ] }, - "66": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "67": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "68": { - "name": "comment.block.cpp" - }, - "69": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "70": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, "71": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "72": { - "name": "comment.block.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "73": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] + "name": "comment.block.cpp" }, "74": { "patterns": [ { - "include": "source.cpp#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "75": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "76": { - "name": "comment.block.cpp" - }, - "77": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "78": { - "name": "entity.name.operator.type.array.cpp" - }, - "79": { - "name": "entity.name.operator.custom-literal.cpp" - }, - "80": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "81": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "82": { + "77": { "name": "comment.block.cpp" }, - "83": { + "78": { "patterns": [ { "match": "\\*\\/", @@ -6532,13 +7692,41 @@ } ] }, + "79": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "80": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "81": { + "name": "comment.block.cpp" + }, + "82": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "83": { + "name": "entity.name.operator.type.array.cpp" + }, "84": { "name": "entity.name.operator.custom-literal.cpp" }, "85": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -6559,21 +7747,47 @@ "name": "comment.block.cpp" } ] + }, + "89": { + "name": "entity.name.operator.custom-literal.cpp" + }, + "90": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "91": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "92": { + "name": "comment.block.cpp" + }, + "93": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] } }, - "endCaptures": {}, - "name": "meta.function.definition.special.operator-overload.cpp", + "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>|(?=(?|\\?\\?>)|(?=(?|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\w)", - "end": "(?:(?=\\))|(,))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\w)", "beginCaptures": { "1": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7006,12 +7963,12 @@ ] } }, + "end": "(?:(?=\\))|(,))|(?=(?(?:\\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)))(?=,|\\)|=)", + "match": "((?:((?:volatile|register|restrict|static|extern|const))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))+)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=,|\\)|=)", "captures": { "1": { "patterns": [ @@ -7041,7 +7998,7 @@ "3": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7066,7 +8023,7 @@ "7": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7091,7 +8048,7 @@ "11": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7116,7 +8073,7 @@ "15": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7144,7 +8101,7 @@ "20": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7172,7 +8129,7 @@ "25": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7200,7 +8157,7 @@ "30": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7231,7 +8188,7 @@ "36": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7259,16 +8216,15 @@ "include": "#storage_types" }, { - "include": "source.cpp#scope_resolution_parameter_inner_generated" + "include": "#scope_resolution_parameter_inner_generated" }, { - "match": "(?:(?:struct)|(?:class)|(?:union)|(?:enum))", + "match": "(?:struct|class|union|enum)", "name": "storage.type.$0.cpp" }, { "begin": "(?<==)", "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}))*)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\)|,|\\[|=|\\n)", + "match": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\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}))*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\)|,|\\[|=|\\n)", "captures": { "1": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7318,7 +8273,7 @@ "6": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7346,19 +8301,19 @@ "include": "#attributes_context" }, { - "begin": "\\[", - "end": "\\]|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*))", + "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*)", "captures": { "0": { "patterns": [ @@ -7382,12 +8337,12 @@ "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7421,7 +8376,7 @@ "1": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7446,7 +8401,7 @@ "5": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7472,14 +8427,528 @@ } ] }, + "parameter_class": { + "match": "(class)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:\\[((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\]((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?=,|\\)|\\n)", + "captures": { + "1": { + "name": "storage.type.class.parameter.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "entity.name.type.class.parameter.cpp" + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "9": { + "name": "comment.block.cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "11": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cpp" + }, + { + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cpp" + } + ] + }, + "12": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "13": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "14": { + "name": "comment.block.cpp" + }, + "15": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "16": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "17": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "18": { + "name": "comment.block.cpp" + }, + "19": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "20": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "21": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "22": { + "name": "comment.block.cpp" + }, + "23": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "24": { + "name": "variable.other.object.declare.cpp" + }, + "25": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "26": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "27": { + "name": "comment.block.cpp" + }, + "28": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + "parameter_enum": { + "match": "(enum)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:\\[((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\]((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?=,|\\)|\\n)", + "captures": { + "1": { + "name": "storage.type.enum.parameter.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "entity.name.type.enum.parameter.cpp" + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "9": { + "name": "comment.block.cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "11": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cpp" + }, + { + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cpp" + } + ] + }, + "12": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "13": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "14": { + "name": "comment.block.cpp" + }, + "15": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "16": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "17": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "18": { + "name": "comment.block.cpp" + }, + "19": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "20": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "21": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "22": { + "name": "comment.block.cpp" + }, + "23": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "24": { + "name": "variable.other.object.declare.cpp" + }, + "25": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "26": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "27": { + "name": "comment.block.cpp" + }, + "28": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, "parameter_or_maybe_value": { - "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\w)", - "end": "(?:(?=\\))|(,))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\w)", "beginCaptures": { "1": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7502,12 +8971,12 @@ ] } }, + "end": "(?:(?=\\))|(,))|(?=(?(?:\\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)))(?=,|\\)|=)", + "match": "((?:((?:volatile|register|restrict|static|extern|const))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))+)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=,|\\)|=)", "captures": { "1": { "patterns": [ @@ -7546,7 +9015,7 @@ "3": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7571,7 +9040,7 @@ "7": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7596,7 +9065,7 @@ "11": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7621,7 +9090,7 @@ "15": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7649,7 +9118,7 @@ "20": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7677,7 +9146,7 @@ "25": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7705,7 +9174,7 @@ "30": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7736,7 +9205,7 @@ "36": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7767,16 +9236,15 @@ "include": "#function_call" }, { - "include": "source.cpp#scope_resolution_parameter_inner_generated" + "include": "#scope_resolution_parameter_inner_generated" }, { - "match": "(?:(?:struct)|(?:class)|(?:union)|(?:enum))", + "match": "(?:struct|class|union|enum)", "name": "storage.type.$0.cpp" }, { "begin": "(?<==)", "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}))*)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=(?:\\)|,|\\[|=|\\/\\/|(?:(?:\\n)|$)))", + "match": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\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}))*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=(?:\\)|,|\\[|=|\\/\\/|(?:\\n|$)))", "captures": { "1": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7822,7 +9290,7 @@ "6": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7850,19 +9318,19 @@ "include": "#attributes_context" }, { - "begin": "\\[", - "end": "\\]|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*))", + "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*)", "captures": { "0": { "patterns": [ @@ -7886,12 +9354,12 @@ "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7925,7 +9393,7 @@ "1": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7950,7 +9418,7 @@ "5": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -7979,23 +9447,537 @@ } ] }, + "parameter_struct": { + "match": "(struct)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:\\[((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\]((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?=,|\\)|\\n)", + "captures": { + "1": { + "name": "storage.type.struct.parameter.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "entity.name.type.struct.parameter.cpp" + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "9": { + "name": "comment.block.cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "11": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cpp" + }, + { + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cpp" + } + ] + }, + "12": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "13": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "14": { + "name": "comment.block.cpp" + }, + "15": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "16": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "17": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "18": { + "name": "comment.block.cpp" + }, + "19": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "20": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "21": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "22": { + "name": "comment.block.cpp" + }, + "23": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "24": { + "name": "variable.other.object.declare.cpp" + }, + "25": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "26": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "27": { + "name": "comment.block.cpp" + }, + "28": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + "parameter_union": { + "match": "(union)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:\\[((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\]((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?=,|\\)|\\n)", + "captures": { + "1": { + "name": "storage.type.union.parameter.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "entity.name.type.union.parameter.cpp" + }, + "7": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "8": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "9": { + "name": "comment.block.cpp" + }, + "10": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "11": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cpp" + }, + { + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cpp" + } + ] + }, + "12": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "13": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "14": { + "name": "comment.block.cpp" + }, + "15": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "16": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "17": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "18": { + "name": "comment.block.cpp" + }, + "19": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "20": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "21": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "22": { + "name": "comment.block.cpp" + }, + "23": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "24": { + "name": "variable.other.object.declare.cpp" + }, + "25": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "26": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "27": { + "name": "comment.block.cpp" + }, + "28": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, "parentheses": { - "begin": "\\(", - "end": "\\)|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?pragma\\b", - "end": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*pragma\\b)", + "beginCaptures": { + "1": { + "name": "keyword.control.directive.pragma.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { "name": "punctuation.definition.directive.cpp" } }, - "endCaptures": {}, - "name": "meta.preprocessor.pragma.cpp", + "end": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*pragma\\s+mark)\\s+(.*)", + "captures": { + "1": { + "name": "keyword.control.directive.pragma.pragma-mark.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "punctuation.definition.directive.cpp" + }, + "7": { + "name": "entity.name.tag.pragma-mark.cpp" + } + }, + "name": "meta.preprocessor.pragma.cpp" + }, + "predefined_macros": { + "patterns": [ + { + "match": "\\b(__cplusplus|__DATE__|__FILE__|__LINE__|__STDC__|__STDC_HOSTED__|__STDC_NO_COMPLEX__|__STDC_VERSION__|__STDCPP_THREADS__|__TIME__|NDEBUG|__OBJC__|__ASSEMBLER__|__ATOM__|__AVX__|__AVX2__|_CHAR_UNSIGNED|__CLR_VER|_CONTROL_FLOW_GUARD|__COUNTER__|__cplusplus_cli|__cplusplus_winrt|_CPPRTTI|_CPPUNWIND|_DEBUG|_DLL|__FUNCDNAME__|__FUNCSIG__|__FUNCTION__|_INTEGRAL_MAX_BITS|__INTELLISENSE__|_ISO_VOLATILE|_KERNEL_MODE|_M_AMD64|_M_ARM|_M_ARM_ARMV7VE|_M_ARM_FP|_M_ARM64|_M_CEE|_M_CEE_PURE|_M_CEE_SAFE|_M_FP_EXCEPT|_M_FP_FAST|_M_FP_PRECISE|_M_FP_STRICT|_M_IX86|_M_IX86_FP|_M_X64|_MANAGED|_MSC_BUILD|_MSC_EXTENSIONS|_MSC_FULL_VER|_MSC_VER|_MSVC_LANG|__MSVC_RUNTIME_CHECKS|_MT|_NATIVE_WCHAR_T_DEFINED|_OPENMP|_PREFAST|__TIMESTAMP__|_VC_NO_DEFAULTLIB|_WCHAR_T_DEFINED|_WIN32|_WIN64|_WINRT_DLL|_ATL_VER|_MFC_VER|__GFORTRAN__|__GNUC__|__GNUC_MINOR__|__GNUC_PATCHLEVEL__|__GNUG__|__STRICT_ANSI__|__BASE_FILE__|__INCLUDE_LEVEL__|__ELF__|__VERSION__|__OPTIMIZE__|__OPTIMIZE_SIZE__|__NO_INLINE__|__GNUC_STDC_INLINE__|__CHAR_UNSIGNED__|__WCHAR_UNSIGNED__|__REGISTER_PREFIX__|__REGISTER_PREFIX__|__SIZE_TYPE__|__PTRDIFF_TYPE__|__WCHAR_TYPE__|__WINT_TYPE__|__INTMAX_TYPE__|__UINTMAX_TYPE__|__SIG_ATOMIC_TYPE__|__INT8_TYPE__|__INT16_TYPE__|__INT32_TYPE__|__INT64_TYPE__|__UINT8_TYPE__|__UINT16_TYPE__|__UINT32_TYPE__|__UINT64_TYPE__|__INT_LEAST8_TYPE__|__INT_LEAST16_TYPE__|__INT_LEAST32_TYPE__|__INT_LEAST64_TYPE__|__UINT_LEAST8_TYPE__|__UINT_LEAST16_TYPE__|__UINT_LEAST32_TYPE__|__UINT_LEAST64_TYPE__|__INT_FAST8_TYPE__|__INT_FAST16_TYPE__|__INT_FAST32_TYPE__|__INT_FAST64_TYPE__|__UINT_FAST8_TYPE__|__UINT_FAST16_TYPE__|__UINT_FAST32_TYPE__|__UINT_FAST64_TYPE__|__INTPTR_TYPE__|__UINTPTR_TYPE__|__CHAR_BIT__|__SCHAR_MAX__|__WCHAR_MAX__|__SHRT_MAX__|__INT_MAX__|__LONG_MAX__|__LONG_LONG_MAX__|__WINT_MAX__|__SIZE_MAX__|__PTRDIFF_MAX__|__INTMAX_MAX__|__UINTMAX_MAX__|__SIG_ATOMIC_MAX__|__INT8_MAX__|__INT16_MAX__|__INT32_MAX__|__INT64_MAX__|__UINT8_MAX__|__UINT16_MAX__|__UINT32_MAX__|__UINT64_MAX__|__INT_LEAST8_MAX__|__INT_LEAST16_MAX__|__INT_LEAST32_MAX__|__INT_LEAST64_MAX__|__UINT_LEAST8_MAX__|__UINT_LEAST16_MAX__|__UINT_LEAST32_MAX__|__UINT_LEAST64_MAX__|__INT_FAST8_MAX__|__INT_FAST16_MAX__|__INT_FAST32_MAX__|__INT_FAST64_MAX__|__UINT_FAST8_MAX__|__UINT_FAST16_MAX__|__UINT_FAST32_MAX__|__UINT_FAST64_MAX__|__INTPTR_MAX__|__UINTPTR_MAX__|__WCHAR_MIN__|__WINT_MIN__|__SIG_ATOMIC_MIN__|__SCHAR_WIDTH__|__SHRT_WIDTH__|__INT_WIDTH__|__LONG_WIDTH__|__LONG_LONG_WIDTH__|__PTRDIFF_WIDTH__|__SIG_ATOMIC_WIDTH__|__SIZE_WIDTH__|__WCHAR_WIDTH__|__WINT_WIDTH__|__INT_LEAST8_WIDTH__|__INT_LEAST16_WIDTH__|__INT_LEAST32_WIDTH__|__INT_LEAST64_WIDTH__|__INT_FAST8_WIDTH__|__INT_FAST16_WIDTH__|__INT_FAST32_WIDTH__|__INT_FAST64_WIDTH__|__INTPTR_WIDTH__|__INTMAX_WIDTH__|__SIZEOF_INT__|__SIZEOF_LONG__|__SIZEOF_LONG_LONG__|__SIZEOF_SHORT__|__SIZEOF_POINTER__|__SIZEOF_FLOAT__|__SIZEOF_DOUBLE__|__SIZEOF_LONG_DOUBLE__|__SIZEOF_SIZE_T__|__SIZEOF_WCHAR_T__|__SIZEOF_WINT_T__|__SIZEOF_PTRDIFF_T__|__BYTE_ORDER__|__ORDER_LITTLE_ENDIAN__|__ORDER_BIG_ENDIAN__|__ORDER_PDP_ENDIAN__|__FLOAT_WORD_ORDER__|__DEPRECATED|__EXCEPTIONS|__GXX_RTTI|__USING_SJLJ_EXCEPTIONS__|__GXX_EXPERIMENTAL_CXX0X__|__GXX_WEAK__|__NEXT_RUNTIME__|__LP64__|_LP64|__SSP__|__SSP_ALL__|__SSP_STRONG__|__SSP_EXPLICIT__|__SANITIZE_ADDRESS__|__SANITIZE_THREAD__|__GCC_HAVE_SYNC_COMPARE_AND_SWAP_1|__GCC_HAVE_SYNC_COMPARE_AND_SWAP_2|__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4|__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8|__GCC_HAVE_SYNC_COMPARE_AND_SWAP_16|__HAVE_SPECULATION_SAFE_VALUE|__GCC_HAVE_DWARF2_CFI_ASM|__FP_FAST_FMA|__FP_FAST_FMAF|__FP_FAST_FMAL|__FP_FAST_FMAF16|__FP_FAST_FMAF32|__FP_FAST_FMAF64|__FP_FAST_FMAF128|__FP_FAST_FMAF32X|__FP_FAST_FMAF64X|__FP_FAST_FMAF128X|__GCC_IEC_559|__GCC_IEC_559_COMPLEX|__NO_MATH_ERRNO__|__has_builtin|__has_feature|__has_extension|__has_cpp_attribute|__has_c_attribute|__has_attribute|__has_declspec_attribute|__is_identifier|__has_include|__has_include_next|__has_warning|__BASE_FILE__|__FILE_NAME__|__clang__|__clang_major__|__clang_minor__|__clang_patchlevel__|__clang_version__|__fp16|_Float16)\\b", + "captures": { + "1": { + "name": "entity.name.other.preprocessor.macro.predefined.$1.cpp" + } + } + }, + { + "match": "\\b__([A-Z_]+)__\\b", + "name": "entity.name.other.preprocessor.macro.predefined.probably.$1.cpp" } ] }, @@ -8072,31 +10142,30 @@ "include": "#comments" }, { - "include": "source.cpp#language_constants" + "include": "#language_constants" }, { - "include": "#d9bc4796b0b_string_context_c" + "include": "#string_context_c" }, { - "include": "source.cpp#d9bc4796b0b_preprocessor_number_literal" + "include": "#preprocessor_number_literal" }, { "include": "#operators" }, { - "include": "source.cpp#predefined_macros" + "include": "#predefined_macros" }, { - "include": "source.cpp#macro_name" + "include": "#macro_name" }, { - "include": "source.cpp#line_continuation_character" + "include": "#line_continuation_character" } ] }, "preprocessor_conditional_defined": { "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?((?:(?:ifndef|ifdef)|if))", - "end": "^(?!\\s*+#\\s*(?:else|endif))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*((?:(?:ifndef|ifdef)|if)))", "beginCaptures": { - "0": { - "name": "keyword.control.directive.conditional.$6.cpp" + "1": { + "name": "keyword.control.directive.conditional.$7.cpp" }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "punctuation.definition.directive.cpp" + } + }, + "end": "(?:^)(?!\\s*+#\\s*(?:else|endif))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<26>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<26>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|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<26>?)+)>)\\s*)?(?![\\w<:.])", + "captures": { + "0": { + "name": "meta.qualified_type.cpp", + "patterns": [ + { + "match": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", + "captures": { + "0": { + "patterns": [ + { + "include": "#scope_resolution_inner_generated" + } + ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + }, + "3": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + } + } + }, + "scope_resolution_function_call": { + "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", + "captures": { + "0": { + "patterns": [ + { + "include": "#scope_resolution_function_call_inner_generated" + } + ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.call.cpp" + }, + "3": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + } + } + }, + "scope_resolution_function_call_inner_generated": { + "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", + "captures": { + "1": { + "patterns": [ + { + "include": "#scope_resolution_function_call_inner_generated" + } + ] + }, + "2": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.call.cpp" + }, + "4": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "6": { + "name": "entity.name.scope-resolution.function.call.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.call.cpp" + } + } + }, + "scope_resolution_function_definition": { + "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", + "captures": { + "0": { + "patterns": [ + { + "include": "#scope_resolution_function_definition_inner_generated" + } + ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.cpp" + }, + "3": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + } + } + }, + "scope_resolution_function_definition_inner_generated": { + "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", + "captures": { + "1": { + "patterns": [ + { + "include": "#scope_resolution_function_definition_inner_generated" + } + ] + }, + "2": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.cpp" + }, + "4": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "6": { + "name": "entity.name.scope-resolution.function.definition.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.cpp" + } + } + }, + "scope_resolution_function_definition_operator_overload": { + "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", + "captures": { + "0": { + "patterns": [ + { + "include": "#scope_resolution_function_definition_operator_overload_inner_generated" + } + ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.operator-overload.cpp" + }, + "3": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + } + } + }, + "scope_resolution_function_definition_operator_overload_inner_generated": { + "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", + "captures": { + "1": { + "patterns": [ + { + "include": "#scope_resolution_function_definition_operator_overload_inner_generated" + } + ] + }, + "2": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.operator-overload.cpp" + }, + "4": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "6": { + "name": "entity.name.scope-resolution.function.definition.operator-overload.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.operator-overload.cpp" + } + } + }, + "scope_resolution_inner_generated": { + "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", + "captures": { + "1": { + "patterns": [ + { + "include": "#scope_resolution_inner_generated" + } + ] + }, + "2": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + }, + "4": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "6": { + "name": "entity.name.scope-resolution.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + } + } + }, + "scope_resolution_namespace_alias": { + "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", + "captures": { + "0": { + "patterns": [ + { + "include": "#scope_resolution_namespace_alias_inner_generated" + } + ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.alias.cpp" + }, + "3": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + } + } + }, + "scope_resolution_namespace_alias_inner_generated": { + "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", + "captures": { + "1": { + "patterns": [ + { + "include": "#scope_resolution_namespace_alias_inner_generated" + } + ] + }, + "2": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.alias.cpp" + }, + "4": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "6": { + "name": "entity.name.scope-resolution.namespace.alias.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.alias.cpp" + } + } + }, + "scope_resolution_namespace_block": { + "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", + "captures": { + "0": { + "patterns": [ + { + "include": "#scope_resolution_namespace_block_inner_generated" + } + ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.block.cpp" + }, + "3": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + } + } + }, + "scope_resolution_namespace_block_inner_generated": { + "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", + "captures": { + "1": { + "patterns": [ + { + "include": "#scope_resolution_namespace_block_inner_generated" + } + ] + }, + "2": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.block.cpp" + }, + "4": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "6": { + "name": "entity.name.scope-resolution.namespace.block.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.block.cpp" + } + } + }, + "scope_resolution_namespace_using": { + "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", + "captures": { + "0": { + "patterns": [ + { + "include": "#scope_resolution_namespace_using_inner_generated" + } + ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.using.cpp" + }, + "3": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + } + } + }, + "scope_resolution_namespace_using_inner_generated": { + "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", + "captures": { + "1": { + "patterns": [ + { + "include": "#scope_resolution_namespace_using_inner_generated" + } + ] + }, + "2": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.using.cpp" + }, + "4": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "6": { + "name": "entity.name.scope-resolution.namespace.using.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.using.cpp" + } + } + }, + "scope_resolution_parameter": { + "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", + "captures": { + "0": { + "patterns": [ + { + "include": "#scope_resolution_parameter_inner_generated" + } + ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.parameter.cpp" + }, + "3": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + } + } + }, + "scope_resolution_parameter_inner_generated": { + "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", + "captures": { + "1": { + "patterns": [ + { + "include": "#scope_resolution_parameter_inner_generated" + } + ] + }, + "2": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.parameter.cpp" + }, + "4": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "6": { + "name": "entity.name.scope-resolution.parameter.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.parameter.cpp" + } + } + }, + "scope_resolution_template_call": { + "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", + "captures": { + "0": { + "patterns": [ + { + "include": "#scope_resolution_template_call_inner_generated" + } + ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp" + }, + "3": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + } + } + }, + "scope_resolution_template_call_inner_generated": { + "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", + "captures": { + "1": { + "patterns": [ + { + "include": "#scope_resolution_template_call_inner_generated" + } + ] + }, + "2": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp" + }, + "4": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "6": { + "name": "entity.name.scope-resolution.template.call.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp" + } + } + }, + "scope_resolution_template_definition": { + "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", + "captures": { + "0": { + "patterns": [ + { + "include": "#scope_resolution_template_definition_inner_generated" + } + ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.definition.cpp" + }, + "3": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + } + } + }, + "scope_resolution_template_definition_inner_generated": { + "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", + "captures": { + "1": { + "patterns": [ + { + "include": "#scope_resolution_template_definition_inner_generated" + } + ] + }, + "2": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.definition.cpp" + }, + "4": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "6": { + "name": "entity.name.scope-resolution.template.definition.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.definition.cpp" + } + } + }, + "semicolon": { + "match": ";", + "name": "punctuation.terminator.statement.cpp" + }, + "simple_type": { + "match": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|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<27>?)+)>)\\s*)?(?![\\w<:.]))(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?", + "captures": { + "1": { + "name": "meta.qualified_type.cpp", + "patterns": [ + { + "match": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cpp" + } + ] + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + "single_line_macro": { + "match": "^((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))#define.*(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", "beginCaptures": { "1": { "name": "keyword.operator.functionlike.cpp keyword.operator.sizeof.cpp" @@ -8234,7 +11726,7 @@ "2": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -8260,12 +11752,12 @@ "name": "punctuation.section.arguments.begin.bracket.round.operator.sizeof.cpp" } }, + "end": "(\\))|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", "beginCaptures": { "1": { "name": "keyword.operator.functionlike.cpp keyword.operator.sizeof.variadic.cpp" @@ -8282,7 +11774,7 @@ "2": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -8308,12 +11800,12 @@ "name": "punctuation.section.arguments.begin.bracket.round.operator.sizeof.variadic.cpp" } }, + "end": "(\\))|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", "beginCaptures": { "1": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -8378,7 +11885,7 @@ "6": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -8404,22 +11911,22 @@ "name": "punctuation.section.arguments.begin.bracket.round.static_assert.cpp" } }, + "end": "(\\))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(DLLEXPORT)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(final)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:))?", - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(DLLEXPORT)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(final)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)((?>[^{]*)))?))", "beginCaptures": { - "0": { + "1": { "name": "meta.head.struct.cpp" }, + "3": { + "name": "storage.type.$3.cpp" + }, + "4": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "5": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "6": { + "name": "comment.block.cpp" + }, + "7": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "8": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "9": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "10": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "11": { + "name": "comment.block.cpp" + }, + "12": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "13": { + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + "14": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "15": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "16": { + "name": "comment.block.cpp" + }, + "17": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "18": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "21": { + "name": "comment.block.cpp" + }, + "22": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "23": { + "name": "entity.name.type.$3.cpp" + }, + "24": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "26": { + "name": "comment.block.cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "28": { + "name": "storage.type.modifier.final.cpp" + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "37": { + "name": "punctuation.separator.colon.inheritance.cpp" + }, + "38": { + "patterns": [ + { + "include": "#inheritance_context" + } + ] + } + }, + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>)|(?=(?|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\S)(?![:{])", + "captures": { + "1": { + "name": "storage.type.struct.declare.cpp" }, "2": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -8631,29 +12481,65 @@ ] }, "6": { - "patterns": [ - { - "include": "#attributes_context" - }, - { - "include": "source.cpp#number_literal" - } - ] + "name": "entity.name.type.struct.cpp" }, "7": { "patterns": [ { - "include": "source.cpp#inline_comment" + "match": "\\*", + "name": "storage.modifier.pointer.cpp" + }, + { + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cpp" } ] }, "8": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "9": { - "name": "comment.block.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "10": { + "name": "comment.block.cpp" + }, + "11": { "patterns": [ { "match": "\\*\\/", @@ -8665,13 +12551,10 @@ } ] }, - "11": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, "12": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -8696,55 +12579,45 @@ "16": { "patterns": [ { - "include": "#attributes_context" - }, - { - "include": "source.cpp#number_literal" + "include": "#inline_comment" } ] }, "17": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "18": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "comment.block.cpp" }, "19": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "20": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] + "name": "variable.other.object.declare.cpp" }, "21": { - "name": "entity.name.type.$1.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "22": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "23": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "comment.block.cpp" }, "24": { - "name": "comment.block.cpp" - }, - "25": { "patterns": [ { "match": "\\*\\/", @@ -8755,146 +12628,17 @@ "name": "comment.block.cpp" } ] - }, - "26": { - "name": "storage.type.modifier.final.cpp" - }, - "27": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "28": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "29": { - "name": "comment.block.cpp" - }, - "30": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "31": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "32": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "33": { - "name": "comment.block.cpp" - }, - "34": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "35": { - "name": "punctuation.separator.colon.inheritance.cpp" } - }, - "endCaptures": { - "1": { - "name": "punctuation.terminator.statement.cpp" - }, - "2": { - "name": "punctuation.terminator.statement.cpp" - } - }, - "name": "meta.block.struct.cpp", - "patterns": [ - { - "begin": "\\G ?", - "end": "(?:\\{|<%|\\?\\?<|(?=;))|(?=(?|\\?\\?>|(?=(?|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", "beginCaptures": { "1": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -8920,12 +12664,12 @@ "name": "punctuation.section.parens.begin.bracket.round.conditional.switch.cpp" } }, + "end": "(\\))|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>)|(?=(?|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)|(?=(?(?:(?>[^<>]*)\\g<1>?)+)>)\\s*", + "captures": { + "0": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + } + } + }, + "template_call_range": { + "name": "meta.template.call.cpp", + "begin": "(<)", + "beginCaptures": { + "1": { + "name": "punctuation.section.angle-brackets.begin.template.call.cpp" + } + }, + "end": "(>)|(?=(?)|(?=(?)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\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}))*)|((?:(?:[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+)+)((?:[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}))*))|((?:[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*((?:[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*(?:(,)|(?=>|$))", + "captures": { "1": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -8968,195 +12923,85 @@ ] }, "5": { - "name": "keyword.control.switch.cpp" - } - }, - "endCaptures": {}, - "name": "meta.block.switch.cpp", - "patterns": [ - { - "begin": "\\G ?", - "end": "(?:\\{|<%|\\?\\?<|(?=;))|(?=(?|\\?\\?>|(?=(?|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)|(?=(?|(?=(?|(?=(?|(?=(?\\s*$)", + "captures": { + "1": { + "name": "storage.type.template.cpp" + }, + "2": { + "name": "punctuation.section.angle-brackets.start.template.definition.cpp" + }, + "3": { + "name": "meta.template.definition.cpp", + "patterns": [ + { + "include": "#template_definition_context" + } + ] + }, + "4": { + "name": "punctuation.section.angle-brackets.end.template.definition.cpp" + } + } + }, "ternary_operator": { - "begin": "\\?", - "end": ":|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<58>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<58>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|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<58>?)+)>)\\s*)?(?![\\w<:.]))\\s*(\\=)\\s*((?:typename)?)\\s*((?:(?:(?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<58>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<58>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|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<58>?)+)>)\\s*)?(?![\\w<:.]))|(.*(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:(\\[)(\\w*)(\\])\\s*)?\\s*(?:(;)|\\n)", + "captures": { + "1": { + "name": "keyword.other.using.directive.cpp" + }, + "2": { + "name": "meta.qualified_type.cpp", + "patterns": [ + { + "match": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cpp" + } + ] + }, + "61": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "62": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "63": { + "name": "comment.block.cpp" + }, + "64": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "65": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "66": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "67": { + "name": "comment.block.cpp" + }, + "68": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "69": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "70": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "71": { + "name": "comment.block.cpp" + }, + "72": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "73": { + "name": "punctuation.definition.begin.bracket.square.cpp" + }, + "74": { + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + "75": { + "name": "punctuation.definition.end.bracket.square.cpp" + }, + "76": { + "name": "punctuation.terminator.statement.cpp" + } + }, + "name": "meta.declaration.type.alias.cpp" + }, + "type_casting_operators": { + "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(DLLEXPORT)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(final)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:))?", - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(DLLEXPORT)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(final)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)((?>[^{]*)))?))", "beginCaptures": { - "0": { + "1": { "name": "meta.head.class.cpp" }, - "1": { - "name": "storage.type.$1.cpp" - }, - "2": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "storage.type.$3.cpp" }, "4": { - "name": "comment.block.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "6": { - "patterns": [ - { - "include": "#attributes_context" - }, - { - "include": "source.cpp#number_literal" - } - ] + "name": "comment.block.cpp" }, "7": { "patterns": [ { - "include": "source.cpp#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "8": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "9": { - "name": "comment.block.cpp" - }, - "10": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "11": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "12": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "13": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "14": { - "name": "comment.block.cpp" - }, - "15": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "16": { "patterns": [ { "include": "#attributes_context" }, { - "include": "source.cpp#number_literal" + "include": "#number_literal" } ] }, + "9": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "10": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "11": { + "name": "comment.block.cpp" + }, + "12": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "13": { + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + "14": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "15": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "16": { + "name": "comment.block.cpp" + }, "17": { "patterns": [ { - "include": "source.cpp#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "18": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "19": { - "name": "comment.block.cpp" - }, - "20": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + "include": "#attributes_context" }, { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#number_literal" } ] }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "21": { - "name": "entity.name.type.$1.cpp" + "name": "comment.block.cpp" }, "22": { "patterns": [ { - "include": "source.cpp#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "23": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "entity.name.type.$3.cpp" }, "24": { - "name": "comment.block.cpp" - }, - "25": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "26": { - "name": "storage.type.modifier.final.cpp" + "name": "comment.block.cpp" }, "27": { "patterns": [ { - "include": "source.cpp#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "28": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "storage.type.modifier.final.cpp" }, "29": { - "name": "comment.block.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "30": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "31": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "32": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "33": { + "31": { "name": "comment.block.cpp" }, - "34": { + "32": { "patterns": [ { "match": "\\*\\/", @@ -9468,10 +13850,43 @@ } ] }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "37": { "name": "punctuation.separator.colon.inheritance.cpp" + }, + "38": { + "patterns": [ + { + "include": "#inheritance_context" + } + ] } }, + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>|(?=(?|\\?\\?>)|(?=(?|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -9588,7 +13998,7 @@ "2": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -9613,7 +14023,7 @@ "6": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -9638,7 +14048,7 @@ "10": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -9675,24 +14085,22 @@ ] }, "typedef_function_pointer": { - "begin": "((?(?:\\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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<25>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<25>?)+>)(?:(?:\\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<25>?)+>)(?:(?:\\s)+)?)?(?![\\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)(?!\\()|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|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<27>?)+)>)\\s*)?(?![\\w<:.]))(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\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*(\\()", "beginCaptures": { "1": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -9916,70 +14304,45 @@ } ] }, - "27": { + "29": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, - "28": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "29": { - "name": "comment.block.cpp" - }, "30": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "31": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] + "name": "comment.block.cpp" }, "32": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "33": { - "name": "comment.block.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "34": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "35": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "36": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "37": { + "35": { "name": "comment.block.cpp" }, - "38": { + "36": { "patterns": [ { "match": "\\*\\/", @@ -9991,35 +14354,61 @@ } ] }, + "37": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "38": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "39": { - "name": "punctuation.section.parens.begin.bracket.round.function.pointer.cpp" + "name": "comment.block.cpp" }, "40": { - "name": "punctuation.definition.function.pointer.dereference.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "41": { - "name": "entity.name.type.alias.cpp entity.name.type.pointer.function.cpp" + "name": "punctuation.section.parens.begin.bracket.round.function.pointer.cpp" }, "42": { - "name": "punctuation.definition.begin.bracket.square.cpp" + "name": "punctuation.definition.function.pointer.dereference.cpp" }, "43": { + "name": "entity.name.type.alias.cpp entity.name.type.pointer.function.cpp" + }, + "44": { + "name": "punctuation.definition.begin.bracket.square.cpp" + }, + "45": { "patterns": [ { "include": "#evaluation_context" } ] }, - "44": { + "46": { "name": "punctuation.definition.end.bracket.square.cpp" }, - "45": { + "47": { "name": "punctuation.section.parens.end.bracket.round.function.pointer.cpp" }, - "46": { + "48": { "name": "punctuation.section.parameters.begin.bracket.round.function.pointer.cpp" } }, + "end": "(\\))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=[{=,);]|\\n)(?!\\()|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(DLLEXPORT)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(final)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:))?", - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(DLLEXPORT)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(final)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)((?>[^{]*)))?))", "beginCaptures": { - "0": { + "1": { "name": "meta.head.struct.cpp" }, - "1": { - "name": "storage.type.$1.cpp" - }, - "2": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "storage.type.$3.cpp" }, "4": { - "name": "comment.block.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "6": { - "patterns": [ - { - "include": "#attributes_context" - }, - { - "include": "source.cpp#number_literal" - } - ] + "name": "comment.block.cpp" }, "7": { "patterns": [ { - "include": "source.cpp#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "8": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "9": { - "name": "comment.block.cpp" - }, - "10": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "11": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "12": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "13": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "14": { - "name": "comment.block.cpp" - }, - "15": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "16": { "patterns": [ { "include": "#attributes_context" }, { - "include": "source.cpp#number_literal" + "include": "#number_literal" } ] }, + "9": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "10": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "11": { + "name": "comment.block.cpp" + }, + "12": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "13": { + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + "14": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "15": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "16": { + "name": "comment.block.cpp" + }, "17": { "patterns": [ { - "include": "source.cpp#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "18": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "19": { - "name": "comment.block.cpp" - }, - "20": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + "include": "#attributes_context" }, { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#number_literal" } ] }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "21": { - "name": "entity.name.type.$1.cpp" + "name": "comment.block.cpp" }, "22": { "patterns": [ { - "include": "source.cpp#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "23": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "entity.name.type.$3.cpp" }, "24": { - "name": "comment.block.cpp" - }, - "25": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "26": { - "name": "storage.type.modifier.final.cpp" + "name": "comment.block.cpp" }, "27": { "patterns": [ { - "include": "source.cpp#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "28": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "storage.type.modifier.final.cpp" }, "29": { - "name": "comment.block.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "30": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "31": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "32": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "33": { + "31": { "name": "comment.block.cpp" }, - "34": { + "32": { "patterns": [ { "match": "\\*\\/", @@ -10282,10 +14678,43 @@ } ] }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "37": { "name": "punctuation.separator.colon.inheritance.cpp" + }, + "38": { + "patterns": [ + { + "include": "#inheritance_context" + } + ] } }, + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>|(?=(?|\\?\\?>)|(?=(?|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -10402,7 +14826,7 @@ "2": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -10427,7 +14851,7 @@ "6": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -10452,7 +14876,7 @@ "10": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -10489,218 +14913,192 @@ ] }, "typedef_union": { - "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(DLLEXPORT)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(final)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:))?", - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(DLLEXPORT)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(final)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)((?>[^{]*)))?))", "beginCaptures": { - "0": { + "1": { "name": "meta.head.union.cpp" }, - "1": { - "name": "storage.type.$1.cpp" - }, - "2": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "storage.type.$3.cpp" }, "4": { - "name": "comment.block.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "6": { - "patterns": [ - { - "include": "#attributes_context" - }, - { - "include": "source.cpp#number_literal" - } - ] + "name": "comment.block.cpp" }, "7": { "patterns": [ { - "include": "source.cpp#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "8": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "9": { - "name": "comment.block.cpp" - }, - "10": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "11": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "12": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "13": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "14": { - "name": "comment.block.cpp" - }, - "15": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "16": { "patterns": [ { "include": "#attributes_context" }, { - "include": "source.cpp#number_literal" + "include": "#number_literal" } ] }, + "9": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "10": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "11": { + "name": "comment.block.cpp" + }, + "12": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "13": { + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + "14": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "15": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "16": { + "name": "comment.block.cpp" + }, "17": { "patterns": [ { - "include": "source.cpp#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "18": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "19": { - "name": "comment.block.cpp" - }, - "20": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + "include": "#attributes_context" }, { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#number_literal" } ] }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "21": { - "name": "entity.name.type.$1.cpp" + "name": "comment.block.cpp" }, "22": { "patterns": [ { - "include": "source.cpp#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "23": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "entity.name.type.$3.cpp" }, "24": { - "name": "comment.block.cpp" - }, - "25": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "26": { - "name": "storage.type.modifier.final.cpp" + "name": "comment.block.cpp" }, "27": { "patterns": [ { - "include": "source.cpp#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "28": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "storage.type.modifier.final.cpp" }, "29": { - "name": "comment.block.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "30": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "31": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "32": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "33": { + "31": { "name": "comment.block.cpp" }, - "34": { + "32": { "patterns": [ { "match": "\\*\\/", @@ -10712,10 +15110,43 @@ } ] }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "37": { "name": "punctuation.separator.colon.inheritance.cpp" + }, + "38": { + "patterns": [ + { + "include": "#inheritance_context" + } + ] } }, + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>|(?=(?|\\?\\?>)|(?=(?|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -10832,7 +15258,7 @@ "2": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -10857,7 +15283,7 @@ "6": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -10882,7 +15308,7 @@ "10": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -10919,8 +15345,8 @@ ] }, "typeid_operator": { - "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", "beginCaptures": { "1": { "name": "keyword.operator.functionlike.cpp keyword.operator.typeid.cpp" @@ -10928,7 +15354,7 @@ "2": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -10954,32 +15380,28 @@ "name": "punctuation.section.arguments.begin.bracket.round.operator.typeid.cpp" } }, + "end": "(\\))|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(DLLEXPORT)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(final)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:))?", - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<36>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<36>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|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<36>?)+)>)\\s*)?(?![\\w<:.]))", + "captures": { "1": { - "name": "storage.type.$1.cpp" + "name": "storage.modifier.cpp" }, "2": { "patterns": [ { - "include": "source.cpp#inline_comment" + "include": "#inline_comment" } ] }, @@ -11004,27 +15426,17 @@ "6": { "patterns": [ { - "include": "#attributes_context" - }, - { - "include": "source.cpp#number_literal" + "include": "#inline_comment" } ] }, "7": { - "patterns": [ - { - "include": "source.cpp#inline_comment" - } - ] - }, - "8": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "9": { + "8": { "name": "comment.block.cpp" }, - "10": { + "9": { "patterns": [ { "match": "\\*\\/", @@ -11036,13 +15448,57 @@ } ] }, + "10": { + "name": "meta.qualified_type.cpp", + "patterns": [ + { + "match": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*undef\\b)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(DLLEXPORT)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(final)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)((?>[^{]*)))?))", + "beginCaptures": { + "1": { + "name": "meta.head.union.cpp" + }, + "3": { + "name": "storage.type.$3.cpp" + }, + "4": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "5": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "6": { + "name": "comment.block.cpp" + }, + "7": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "8": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "9": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "10": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "11": { + "name": "comment.block.cpp" + }, + "12": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "13": { + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + "14": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "15": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "16": { + "name": "comment.block.cpp" + }, + "17": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "18": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "21": { + "name": "comment.block.cpp" + }, + "22": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "23": { + "name": "entity.name.type.$3.cpp" + }, + "24": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "26": { + "name": "comment.block.cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "28": { + "name": "storage.type.modifier.final.cpp" + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "37": { + "name": "punctuation.separator.colon.inheritance.cpp" + }, + "38": { + "patterns": [ + { + "include": "#inheritance_context" + } + ] + } + }, + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))|(?=(?|\\?\\?>|(?=(?|\\?\\?>)|(?=(?|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)|(?=(?|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)|(?=(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\S)(?![:{])", + "captures": { + "1": { + "name": "storage.type.union.declare.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "entity.name.type.union.cpp" + }, + "7": { + "patterns": [ + { + "match": "\\*", + "name": "storage.modifier.pointer.cpp" + }, + { + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cpp" + } + ] + }, + "8": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "9": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "10": { + "name": "comment.block.cpp" + }, + "11": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "12": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "13": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "14": { + "name": "comment.block.cpp" + }, + "15": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "16": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "17": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "18": { + "name": "comment.block.cpp" + }, + "19": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "20": { + "name": "variable.other.object.declare.cpp" + }, + "21": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "22": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "23": { + "name": "comment.block.cpp" + }, + "24": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + }, + "using_name": { + "match": "(using)\\s+(?!namespace\\b)", + "captures": { + "1": { + "name": "keyword.other.using.directive.cpp" + } + } + }, "using_namespace": { - "begin": "(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<6>?)+>)(?:(?:\\s)+)?)?::)*\\s*+)?((?(?:(?>[^<>]*)\\g<7>?)+)>)\\s*)?::)*\\s*+)?((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(((?:(?:protected)|(?:private)|(?:public)))(?:(?:\\s)+)?(:))", + "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(((?:protected|private|public))\\s*(:))", "captures": { "1": { "patterns": [ @@ -105,55 +105,45 @@ ] }, "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "3": { - "name": "storage.type.modifier.access.control.$4.cpp" - }, - "4": {}, "5": { + "name": "storage.type.modifier.access.control.$6.cpp" + }, + "7": { "name": "punctuation.separator.colon.access.control.cpp" } } }, "alignas_attribute": { - "begin": "alignas\\(", - "end": "\\)", + "name": "support.other.attribute.cpp", + "begin": "(alignas\\()", "beginCaptures": { - "0": { + "1": { "name": "punctuation.section.attribute.begin.cpp" } }, + "end": "(\\))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.attribute.end.cpp" } }, - "name": "support.other.attribute.cpp", "patterns": [ { "include": "#attributes_context" @@ -161,8 +151,6 @@ { "begin": "\\(", "end": "\\)", - "beginCaptures": {}, - "endCaptures": {}, "patterns": [ { "include": "#attributes_context" @@ -173,7 +161,7 @@ ] }, { - "match": "(using)(?:\\s)+((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)", + "contentName": "meta.arguments.operator.alignas.cpp", + "begin": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", "beginCaptures": { "1": { "name": "keyword.operator.functionlike.cpp keyword.operator.alignas.cpp" @@ -240,12 +228,12 @@ "name": "punctuation.section.arguments.begin.bracket.round.operator.alignas.cpp" } }, + "end": "(\\))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.arguments.end.bracket.round.operator.alignas.cpp" } }, - "contentName": "meta.arguments.operator.alignas", "patterns": [ { "include": "#evaluation_context" @@ -253,8 +241,8 @@ ] }, "alignof_operator": { - "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)", + "contentName": "meta.arguments.operator.alignof.cpp", + "begin": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", "beginCaptures": { "1": { "name": "keyword.operator.functionlike.cpp keyword.operator.alignof.cpp" @@ -288,12 +276,12 @@ "name": "punctuation.section.arguments.begin.bracket.round.operator.alignof.cpp" } }, + "end": "(\\))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.arguments.end.bracket.round.operator.alignof.cpp" } }, - "contentName": "meta.arguments.operator.alignof", "patterns": [ { "include": "#evaluation_context" @@ -301,221 +289,96 @@ ] }, "assembly": { - "begin": "(\\b(?:__asm__|asm)\\b)(?:(?:\\s)+)?((?:volatile)?)", - "end": "(?!\\G)", + "name": "meta.asm.cpp", + "begin": "(\\b(?:__asm__|asm)\\b)\\s*((?:volatile)?)\\s*(\\()", "beginCaptures": { "1": { "name": "storage.type.asm.cpp" }, "2": { "name": "storage.modifier.cpp" + }, + "3": { + "name": "punctuation.section.parens.begin.bracket.round.assembly.cpp" + } + }, + "end": "(\\))", + "endCaptures": { + "1": { + "name": "punctuation.section.parens.end.bracket.round.assembly.cpp" } }, - "endCaptures": {}, - "name": "meta.asm.cpp", "patterns": [ { - "match": "^((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:\\n)|$)", - "captures": { + "name": "string.quoted.double.cpp", + "contentName": "meta.embedded.assembly.cpp", + "begin": "(R?)(\")", + "beginCaptures": { "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "meta.encoding.cpp" }, "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] + "name": "punctuation.definition.string.begin.assembly.cpp" } - } + }, + "end": "(\")", + "endCaptures": { + "1": { + "name": "punctuation.definition.string.end.assembly.cpp" + } + }, + "patterns": [ + { + "include": "source.asm" + }, + { + "include": "source.x86" + }, + { + "include": "source.x86_64" + }, + { + "include": "source.arm" + }, + { + "include": "#backslash_escapes" + }, + { + "include": "#string_escaped_char" + }, + { + "match": "(?=not)possible" + } + ] + }, + { + "begin": "(\\()", + "beginCaptures": { + "1": { + "name": "punctuation.section.parens.begin.bracket.round.assembly.inner.cpp" + } + }, + "end": "(\\))", + "endCaptures": { + "1": { + "name": "punctuation.section.parens.end.bracket.round.assembly.inner.cpp" + } + }, + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + { + "match": ":", + "name": "punctuation.separator.delimiter.colon.assembly.cpp" }, { "include": "#comments_context" }, { "include": "#comments" - }, - { - "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\(", - "end": "\\)", - "beginCaptures": { - "0": { - "name": "punctuation.section.parens.begin.bracket.round.assembly.cpp" - }, - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - }, - "endCaptures": { - "0": { - "name": "punctuation.section.parens.end.bracket.round.assembly.cpp" - } - }, - "patterns": [ - { - "begin": "(R?)(\")", - "end": "\"", - "beginCaptures": { - "1": { - "name": "meta.encoding.cpp" - }, - "2": { - "name": "punctuation.definition.string.begin.assembly.cpp" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.assembly.cpp" - } - }, - "name": "string.quoted.double.cpp", - "contentName": "meta.embedded.assembly", - "patterns": [ - { - "include": "source.asm" - }, - { - "include": "source.x86" - }, - { - "include": "source.x86_64" - }, - { - "include": "source.arm" - }, - { - "include": "#backslash_escapes" - }, - { - "include": "#string_escaped_char" - } - ] - }, - { - "begin": "\\(", - "end": "\\)", - "beginCaptures": { - "0": { - "name": "punctuation.section.parens.begin.bracket.round.assembly.inner.cpp" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.section.parens.end.bracket.round.assembly.inner.cpp" - } - }, - "patterns": [ - { - "include": "#evaluation_context" - } - ] - }, - { - "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)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "5": { - "name": "variable.other.asm.label.cpp" - }, - "6": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "7": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "8": { - "name": "comment.block.cpp" - }, - "9": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - } - }, - { - "match": ":", - "name": "punctuation.separator.delimiter.colon.assembly.cpp" - }, - { - "include": "#comments_context" - }, - { - "include": "#comments" - } - ] } ] }, @@ -540,23 +403,23 @@ ] }, "backslash_escapes": { - "match": "(?x)\\\\ (\n\\\\\t\t\t |\n[abefnprtv'\"?] |\n[0-3][0-7]{,2}\t |\n[4-7]\\d?\t\t|\nx[a-fA-F0-9]{,2} |\nu[a-fA-F0-9]{,4} |\nU[a-fA-F0-9]{,8} )", - "name": "constant.character.escape" + "match": "(?x)\\\\ (\n\\\\\t\t\t |\n[abefnprtv'\"?] |\n[0-3]\\d{,2}\t |\n[4-7]\\d?\t\t|\nx[a-fA-F0-9]{,2} |\nu[a-fA-F0-9]{,4} |\nU[a-fA-F0-9]{,8} )", + "name": "constant.character.escape.cpp" }, "block": { - "begin": "{", - "end": "}|(?=\\s*#\\s*(?:elif|else|endif)\\b)", + "name": "meta.block.cpp", + "begin": "({)", "beginCaptures": { - "0": { + "1": { "name": "punctuation.section.block.begin.bracket.curly.cpp" } }, + "end": "(}|(?=\\s*#\\s*(?:elif|else|endif)\\b))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.end.bracket.curly.cpp" } }, - "name": "meta.block.cpp", "patterns": [ { "include": "#function_body_context" @@ -564,23 +427,22 @@ ] }, "block_comment": { + "name": "comment.block.cpp", "begin": "\\s*+(\\/\\*)", - "end": "\\*\\/", "beginCaptures": { "1": { "name": "punctuation.definition.comment.begin.cpp" } }, + "end": "(\\*\\/)", "endCaptures": { - "0": { + "1": { "name": "punctuation.definition.comment.end.cpp" } - }, - "name": "comment.block.cpp" + } }, "builtin_storage_type_initilizer": { - "begin": "((?:(?:(?:(?>(?:\\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)))(\\()", - "end": "\\)", + "begin": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", "beginCaptures": { "1": { "patterns": [ @@ -748,8 +610,9 @@ "name": "punctuation.section.arguments.begin.bracket.round.initializer.cpp" } }, + "end": "(\\))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.arguments.end.bracket.round.initializer.cpp" } }, @@ -760,8 +623,8 @@ ] }, "case_statement": { - "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(DLLEXPORT)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(final)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:))?", - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))", + "name": "meta.block.class.cpp", + "begin": "((((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(DLLEXPORT)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(final)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)((?>[^{]*)))?))", "beginCaptures": { - "0": { + "1": { "name": "meta.head.class.cpp" }, + "3": { + "name": "storage.type.$3.cpp" + }, + "4": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "5": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "6": { + "name": "comment.block.cpp" + }, + "7": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "8": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "9": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "10": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "11": { + "name": "comment.block.cpp" + }, + "12": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "13": { + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + "14": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "15": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "16": { + "name": "comment.block.cpp" + }, + "17": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "18": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "21": { + "name": "comment.block.cpp" + }, + "22": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "23": { + "name": "entity.name.type.$3.cpp" + }, + "24": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "26": { + "name": "comment.block.cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "28": { + "name": "storage.type.modifier.final.cpp" + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "37": { + "name": "punctuation.separator.colon.inheritance.cpp" + }, + "38": { + "patterns": [ + { + "include": "#inheritance_context" + } + ] + } + }, + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))", + "endCaptures": { "1": { - "name": "storage.type.$1.cpp" + "name": "punctuation.terminator.statement.cpp" + }, + "2": { + "name": "punctuation.terminator.statement.cpp" + } + }, + "patterns": [ + { + "name": "meta.head.class.cpp", + "begin": "\\G ?", + "end": "((?:\\{|<%|\\?\\?<|(?=;)))", + "endCaptures": { + "1": { + "name": "punctuation.section.block.begin.bracket.curly.class.cpp" + } + }, + "patterns": [ + { + "include": "#ever_present_context" + }, + { + "include": "#inheritance_context" + }, + { + "include": "#template_call_range" + } + ] + }, + { + "name": "meta.body.class.cpp", + "begin": "(?<=\\{|<%|\\?\\?<)", + "end": "(\\}|%>|\\?\\?>)", + "endCaptures": { + "1": { + "name": "punctuation.section.block.end.bracket.curly.class.cpp" + } + }, + "patterns": [ + { + "include": "#function_pointer" + }, + { + "include": "#static_assert" + }, + { + "include": "#constructor_inline" + }, + { + "include": "#destructor_inline" + }, + { + "include": "$self" + } + ] + }, + { + "name": "meta.tail.class.cpp", + "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)", + "patterns": [ + { + "include": "$self" + } + ] + } + ] + }, + "class_declare": { + "match": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\S)(?![:{])", + "captures": { + "1": { + "name": "storage.type.class.declare.cpp" }, "2": { "patterns": [ @@ -843,313 +997,16 @@ ] }, "6": { - "patterns": [ - { - "include": "#attributes_context" - }, - { - "include": "#number_literal" - } - ] - }, - "7": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "8": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "9": { - "name": "comment.block.cpp" - }, - "10": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "11": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "12": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "13": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "14": { - "name": "comment.block.cpp" - }, - "15": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "16": { - "patterns": [ - { - "include": "#attributes_context" - }, - { - "include": "#number_literal" - } - ] - }, - "17": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "18": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "19": { - "name": "comment.block.cpp" - }, - "20": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "21": { - "name": "entity.name.type.$1.cpp" - }, - "22": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "23": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "24": { - "name": "comment.block.cpp" - }, - "25": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "26": { - "name": "storage.type.modifier.final.cpp" - }, - "27": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "28": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "29": { - "name": "comment.block.cpp" - }, - "30": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "31": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "32": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "33": { - "name": "comment.block.cpp" - }, - "34": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "35": { - "name": "punctuation.separator.colon.inheritance.cpp" - } - }, - "endCaptures": { - "1": { - "name": "punctuation.terminator.statement.cpp" - }, - "2": { - "name": "punctuation.terminator.statement.cpp" - } - }, - "name": "meta.block.class.cpp", - "patterns": [ - { - "begin": "\\G ?", - "end": "(?:\\{|<%|\\?\\?<|(?=;))", - "beginCaptures": {}, - "endCaptures": { - "0": { - "name": "punctuation.section.block.begin.bracket.curly.class.cpp" - } - }, - "name": "meta.head.class.cpp", - "patterns": [ - { - "include": "#ever_present_context" - }, - { - "include": "#inheritance_context" - }, - { - "include": "#template_call_range" - } - ] - }, - { - "begin": "(?<=\\{|<%|\\?\\?<)", - "end": "\\}|%>|\\?\\?>", - "beginCaptures": {}, - "endCaptures": { - "0": { - "name": "punctuation.section.block.end.bracket.curly.class.cpp" - } - }, - "name": "meta.body.class.cpp", - "patterns": [ - { - "include": "#function_pointer" - }, - { - "include": "#static_assert" - }, - { - "include": "#constructor_inline" - }, - { - "include": "#destructor_inline" - }, - { - "include": "$self" - } - ] - }, - { - "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)", - "beginCaptures": {}, - "endCaptures": {}, - "name": "meta.tail.class.cpp", - "patterns": [ - { - "include": "$self" - } - ] - } - ] - }, - "class_declare": { - "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\S)(?![:{])", - "captures": { - "1": { - "name": "storage.type.class.declare.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "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" - } - ] - } - } - } - ] - }, - "4": { "name": "entity.name.type.class.cpp" }, - "5": { + "7": { "patterns": [ { "match": "\\*", "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ @@ -1185,40 +1042,6 @@ } ] }, - "6": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "7": { - "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" - } - ] - } - } - } - ] - }, "8": { "patterns": [ { @@ -1227,100 +1050,98 @@ ] }, "9": { - "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" - } - ] - } - } - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp" }, "11": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "12": { - "name": "variable.other.object.declare.cpp" - }, - "13": { "patterns": [ { "include": "#inline_comment" } ] }, + "13": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "14": { + "name": "comment.block.cpp" + }, + "15": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "16": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "17": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "18": { + "name": "comment.block.cpp" + }, + "19": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "20": { + "name": "variable.other.object.declare.cpp" + }, + "21": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "22": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "23": { + "name": "comment.block.cpp" + }, + "24": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] } @@ -1337,15 +1158,14 @@ "comments": { "patterns": [ { - "begin": "^(?:(?:\\s)+)?+(\\/\\/[!\\/]+)", - "end": "(?<=\\n)(?\\s*)(\\/\\/[!\\/]+)", "beginCaptures": { "1": { "name": "punctuation.definition.comment.documentation.cpp" } }, - "endCaptures": {}, - "name": "comment.line.double-slash.documentation.cpp", + "end": "(?<=\\n)(?\\s*)\\/\\*[!*]+(?:(?:\\n|$)|(?=\\s)))", "beginCaptures": { - "0": { + "1": { "name": "punctuation.definition.comment.begin.documentation.cpp" } }, + "end": "([!*]*\\*\\/)", "endCaptures": { - "0": { + "1": { "name": "punctuation.definition.comment.end.documentation.cpp" } }, - "name": "comment.block.documentation.cpp", "patterns": [ { "match": "(?<=[\\s*!\\/])[\\\\@](?:callergraph|callgraph|else|endif|f\\$|f\\[|f\\]|hidecallergraph|hidecallgraph|hiderefby|hiderefs|hideinitializer|htmlinclude|n|nosubgrouping|private|privatesection|protected|protectedsection|public|publicsection|pure|showinitializer|showrefby|showrefs|tableofcontents|\\$|\\#|<|>|%|\"|\\.|=|::|\\||\\-\\-|\\-\\-\\-)\\b(?:\\{[^}]*\\})?", "name": "storage.type.class.doxygen.cpp" }, { - "match": "((?<=[\\s*!\\/])[\\\\@](?:a|em|e))(?:\\s)+(\\S+)", + "match": "((?<=[\\s*!\\/])[\\\\@](?:a|em|e))\\s+(\\S+)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" @@ -1551,7 +1355,7 @@ } }, { - "match": "((?<=[\\s*!\\/])[\\\\@]b)(?:\\s)+(\\S+)", + "match": "((?<=[\\s*!\\/])[\\\\@]b)\\s+(\\S+)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" @@ -1562,7 +1366,7 @@ } }, { - "match": "((?<=[\\s*!\\/])[\\\\@](?:c|p))(?:\\s)+(\\S+)", + "match": "((?<=[\\s*!\\/])[\\\\@](?:c|p))\\s+(\\S+)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" @@ -1581,20 +1385,12 @@ "name": "storage.type.class.doxygen.cpp" }, { - "match": "((?<=[\\s*!\\/])[\\\\@]param)(?:\\s*\\[((?:,?(?:(?:\\s)+)?(?:in|out)(?:(?:\\s)+)?)+)\\])?(?:\\s)+(\\b\\w+\\b)", + "match": "((?<=[\\s*!\\/])[\\\\@]param)\\s+(\\b\\w+\\b)", "captures": { "1": { "name": "storage.type.class.doxygen.cpp" }, "2": { - "patterns": [ - { - "match": "in|out", - "name": "keyword.other.parameter.direction.$0.cpp" - } - ] - }, - "3": { "name": "variable.parameter.cpp" } } @@ -1628,26 +1424,26 @@ ] }, "constructor_inline": { - "begin": "^((?:(?:(?:(?>(?:\\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)))((?|\\?\\?>)|(?=[;>\\[\\]=]))", + "name": "meta.function.definition.special.constructor.cpp", + "begin": "(^((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?:constexpr|explicit|mutable|virtual|inline|friend)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?|\\?\\?>)|(?=[;>\\[\\]=]))", "patterns": [ { + "name": "meta.head.function.definition.special.constructor.cpp", "begin": "\\G ?", - "end": "(?:\\{|<%|\\?\\?<|(?=;))", - "beginCaptures": {}, + "end": "((?:\\{|<%|\\?\\?<|(?=;)))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.begin.bracket.curly.function.definition.special.constructor.cpp" } }, - "name": "meta.head.function.definition.special.constructor.cpp", "patterns": [ { "include": "#ever_present_context" }, { - "match": "(\\=)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(default)|(delete))", - "captures": { - "1": { - "name": "keyword.operator.assignment.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + "patterns": [ + { + "match": "(\\=)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(default)|(delete))", + "captures": { + "1": { + "name": "keyword.operator.assignment.cpp" }, - { - "match": "\\*", + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "keyword.other.default.constructor.cpp" + }, + "7": { + "name": "keyword.other.delete.constructor.cpp" } - ] - }, - "6": { - "name": "keyword.other.default.constructor.cpp" - }, - "7": { - "name": "keyword.other.delete.constructor.cpp" + } } - } + ] }, { "include": "#functional_specifiers_pre_parameters" }, { - "begin": ":", - "end": "(?=\\{)", + "begin": "(:)", "beginCaptures": { - "0": { + "1": { "name": "punctuation.separator.initializers.cpp" } }, - "endCaptures": {}, + "end": "(?=\\{)", "patterns": [ { - "begin": "((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:(?:\\s)+)?)?(\\()", - "end": "\\)", + "contentName": "meta.parameter.initialization.cpp", + "begin": "((?(?:(?>[^<>]*)\\g<3>?)+)>)\\s*)?(\\()", "beginCaptures": { "1": { "name": "entity.name.function.call.initializer.cpp" @@ -1832,17 +1629,16 @@ } ] }, - "3": {}, "4": { "name": "punctuation.section.arguments.begin.bracket.round.function.call.initializer.cpp" } }, + "end": "(\\))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.arguments.end.bracket.round.function.call.initializer.cpp" } }, - "contentName": "meta.parameter.initialization", "patterns": [ { "include": "#evaluation_context" @@ -1850,8 +1646,8 @@ ] }, { + "contentName": "meta.parameter.initialization.cpp", "begin": "((?|\\?\\?>", - "beginCaptures": {}, + "end": "(\\}|%>|\\?\\?>)", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.end.bracket.curly.function.definition.special.constructor.cpp" } }, - "name": "meta.body.function.definition.special.constructor.cpp", "patterns": [ { "include": "#function_body_context" @@ -1926,11 +1720,9 @@ ] }, { - "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)", - "beginCaptures": {}, - "endCaptures": {}, "name": "meta.tail.function.definition.special.constructor.cpp", + "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)", "patterns": [ { "include": "$self" @@ -1940,26 +1732,26 @@ ] }, "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:(?:\\s)+)?)?::)*)(((?>(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))::((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\14((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\())", - "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", + "name": "meta.function.definition.special.constructor.cpp", + "begin": "(\\s*+((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<14>?)+)>)\\s*)?::)*)(((?>(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))::((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\16((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\()))", "beginCaptures": { - "0": { + "1": { "name": "meta.head.function.definition.special.constructor.cpp" }, - "1": { + "2": { "patterns": [ { "include": "#inline_comment" } ] }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, "3": { - "name": "comment.block.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "4": { + "name": "comment.block.cpp" + }, + "5": { "patterns": [ { "match": "\\*\\/", @@ -1971,23 +1763,23 @@ } ] }, - "5": { + "6": { "name": "storage.type.modifier.calling-convention.cpp" }, - "6": { + "7": { "patterns": [ { "include": "#inline_comment" } ] }, - "7": { + "8": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "8": { + "9": { "name": "comment.block.cpp" }, - "9": { + "10": { "patterns": [ { "match": "\\*\\/", @@ -1999,7 +1791,7 @@ } ] }, - "10": { + "11": { "patterns": [ { "match": "::", @@ -2014,7 +1806,7 @@ } ] }, - "11": { + "13": { "name": "meta.template.call.cpp", "patterns": [ { @@ -2022,8 +1814,7 @@ } ] }, - "12": {}, - "13": { + "15": { "patterns": [ { "match": "(?:[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}))*(?=:)", @@ -2039,46 +1830,20 @@ } ] }, - "14": {}, - "15": { + "17": { "patterns": [ { "include": "#inline_comment" } ] }, - "16": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "17": { - "name": "comment.block.cpp" - }, "18": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "19": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "20": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "21": { "name": "comment.block.cpp" }, - "22": { + "20": { "patterns": [ { "match": "\\*\\/", @@ -2090,20 +1855,45 @@ } ] }, - "23": { + "21": { "patterns": [ { "include": "#inline_comment" } ] }, - "24": { + "22": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "25": { + "23": { "name": "comment.block.cpp" }, + "24": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "25": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, "26": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "27": { + "name": "comment.block.cpp" + }, + "28": { "patterns": [ { "match": "\\*\\/", @@ -2116,78 +1906,79 @@ ] } }, - "endCaptures": {}, - "name": "meta.function.definition.special.constructor.cpp", + "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", "patterns": [ { + "name": "meta.head.function.definition.special.constructor.cpp", "begin": "\\G ?", - "end": "(?:\\{|<%|\\?\\?<|(?=;))", - "beginCaptures": {}, + "end": "((?:\\{|<%|\\?\\?<|(?=;)))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.begin.bracket.curly.function.definition.special.constructor.cpp" } }, - "name": "meta.head.function.definition.special.constructor.cpp", "patterns": [ { "include": "#ever_present_context" }, { - "match": "(\\=)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(default)|(delete))", - "captures": { - "1": { - "name": "keyword.operator.assignment.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + "patterns": [ + { + "match": "(\\=)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(default)|(delete))", + "captures": { + "1": { + "name": "keyword.operator.assignment.cpp" }, - { - "match": "\\*", + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "keyword.other.default.constructor.cpp" + }, + "7": { + "name": "keyword.other.delete.constructor.cpp" } - ] - }, - "6": { - "name": "keyword.other.default.constructor.cpp" - }, - "7": { - "name": "keyword.other.delete.constructor.cpp" + } } - } + ] }, { "include": "#functional_specifiers_pre_parameters" }, { - "begin": ":", - "end": "(?=\\{)", + "begin": "(:)", "beginCaptures": { - "0": { + "1": { "name": "punctuation.separator.initializers.cpp" } }, - "endCaptures": {}, + "end": "(?=\\{)", "patterns": [ { - "begin": "((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:(?:\\s)+)?)?(\\()", - "end": "\\)", + "contentName": "meta.parameter.initialization.cpp", + "begin": "((?(?:(?>[^<>]*)\\g<3>?)+)>)\\s*)?(\\()", "beginCaptures": { "1": { "name": "entity.name.function.call.initializer.cpp" @@ -2200,17 +1991,16 @@ } ] }, - "3": {}, "4": { "name": "punctuation.section.arguments.begin.bracket.round.function.call.initializer.cpp" } }, + "end": "(\\))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.arguments.end.bracket.round.function.call.initializer.cpp" } }, - "contentName": "meta.parameter.initialization", "patterns": [ { "include": "#evaluation_context" @@ -2218,8 +2008,8 @@ ] }, { + "contentName": "meta.parameter.initialization.cpp", "begin": "((?|\\?\\?>", - "beginCaptures": {}, + "end": "(\\}|%>|\\?\\?>)", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.end.bracket.curly.function.definition.special.constructor.cpp" } }, - "name": "meta.body.function.definition.special.constructor.cpp", "patterns": [ { "include": "#function_body_context" @@ -2294,11 +2082,9 @@ ] }, { - "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)", - "beginCaptures": {}, - "endCaptures": {}, "name": "meta.tail.function.definition.special.constructor.cpp", + "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)", "patterns": [ { "include": "$self" @@ -2308,7 +2094,7 @@ ] }, "control_flow_keywords": { - "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "3": { - "name": "keyword.control.$3.cpp" + "5": { + "name": "keyword.control.$5.cpp" } } }, "cpp_attributes": { - "begin": "\\[\\[", - "end": "\\]\\]", + "name": "support.other.attribute.cpp", + "begin": "(\\[\\[)", "beginCaptures": { - "0": { + "1": { "name": "punctuation.section.attribute.begin.cpp" } }, + "end": "(\\]\\])", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.attribute.end.cpp" } }, - "name": "support.other.attribute.cpp", "patterns": [ { "include": "#attributes_context" @@ -2370,8 +2147,6 @@ { "begin": "\\(", "end": "\\)", - "beginCaptures": {}, - "endCaptures": {}, "patterns": [ { "include": "#attributes_context" @@ -2382,7 +2157,7 @@ ] }, { - "match": "(using)(?:\\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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<25>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<25>?)+>)(?:(?:\\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<25>?)+>)(?:(?:\\s)+)?)?(?![\\w<:.]))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\{)", - "end": "\\}", + "name": "meta.initialization.cpp", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|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<27>?)+)>)\\s*)?(?![\\w<:.]))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\{)", "beginCaptures": { "1": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?", - "beginCaptures": { - "0": { - "name": "punctuation.section.angle-brackets.begin.template.call.cpp" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.section.angle-brackets.end.template.call.cpp" - } - }, - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_context" - } - ] + "include": "#template_call_range" }, { "match": "(?:[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}))*", @@ -2531,17 +2289,17 @@ } ] }, - "11": { + "12": { "patterns": [ { "include": "#scope_resolution_inner_generated" } ] }, - "12": { + "13": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, - "13": { + "15": { "name": "meta.template.call.cpp", "patterns": [ { @@ -2549,11 +2307,10 @@ } ] }, - "14": {}, - "15": { + "17": { "name": "entity.name.scope-resolution.cpp" }, - "16": { + "18": { "name": "meta.template.call.cpp", "patterns": [ { @@ -2561,24 +2318,23 @@ } ] }, - "17": {}, - "18": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" - }, - "19": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, "20": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, "21": { - "name": "comment.block.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "22": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "23": { + "name": "comment.block.cpp" + }, + "24": { "patterns": [ { "match": "\\*\\/", @@ -2590,10 +2346,10 @@ } ] }, - "23": { + "25": { "name": "entity.name.type.cpp" }, - "24": { + "26": { "name": "meta.template.call.cpp", "patterns": [ { @@ -2601,21 +2357,20 @@ } ] }, - "25": {}, - "26": { + "28": { "patterns": [ { "include": "#inline_comment" } ] }, - "27": { + "29": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "28": { + "30": { "name": "comment.block.cpp" }, - "29": { + "31": { "patterns": [ { "match": "\\*\\/", @@ -2627,16 +2382,16 @@ } ] }, - "30": { + "32": { "name": "punctuation.section.arguments.begin.bracket.curly.initializer.cpp" } }, + "end": "(\\})", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.arguments.end.bracket.curly.initializer.cpp" } }, - "name": "meta.initialization.cpp", "patterns": [ { "include": "#evaluation_context" @@ -2646,501 +2401,9 @@ } ] }, - "d9bc4796b0b_module_import": { - "match": "^((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((import))(?:(?:\\s)+)?(?:(?:(?:((<)[^>]*(>?)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:\\n)|$)|(?=\\/\\/)))|((\\\")[^\\\"]*((?:\\\")?)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:\\n)|$)|(?=\\/\\/))))|(((?:((?:(?>(?:\\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}))*(?:\\.(?:[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)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:\\n)|$)|(?=(?:\\/\\/|;)))))|((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:\\n)|$)|(?=(?:\\/\\/|;))))(?:(?:\\s)+)?(;?)", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "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" - } - ] - } - } - } - ] - }, - "3": { - "name": "keyword.control.directive.import.cpp" - }, - "5": { - "name": "string.quoted.other.lt-gt.include.cpp" - }, - "6": { - "name": "punctuation.definition.string.begin.cpp" - }, - "7": { - "name": "punctuation.definition.string.end.cpp" - }, - "8": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "9": { - "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" - } - ] - } - } - } - ] - }, - "10": { - "name": "string.quoted.double.include.cpp" - }, - "11": { - "name": "punctuation.definition.string.begin.cpp" - }, - "12": { - "name": "punctuation.definition.string.end.cpp" - }, - "13": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "14": { - "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" - } - ] - } - } - } - ] - }, - "15": { - "name": "entity.name.other.preprocessor.macro.include.cpp" - }, - "16": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "17": { - "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" - } - ] - } - } - } - ] - }, - "18": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "19": { - "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" - } - ] - } - } - } - ] - }, - "20": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "21": { - "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" - } - ] - } - } - } - ] - }, - "22": { - "name": "punctuation.terminator.statement.cpp" - } - }, - "name": "meta.preprocessor.import.cpp" - }, - "d9bc4796b0b_preprocessor_number_literal": { - "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)", + "contentName": "meta.arguments.decltype.cpp", + "begin": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", "beginCaptures": { "1": { "name": "keyword.operator.functionlike.cpp keyword.other.decltype.cpp storage.type.decltype.cpp" @@ -3174,12 +2437,12 @@ "name": "punctuation.section.arguments.begin.bracket.round.decltype.cpp" } }, + "end": "(\\))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.arguments.end.bracket.round.decltype.cpp" } }, - "contentName": "meta.arguments.decltype", "patterns": [ { "include": "#evaluation_context" @@ -3187,8 +2450,8 @@ ] }, "decltype_specifier": { - "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)", + "contentName": "meta.arguments.decltype.cpp", + "begin": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", "beginCaptures": { "1": { "name": "keyword.operator.functionlike.cpp keyword.other.decltype.cpp storage.type.decltype.cpp" @@ -3222,12 +2485,12 @@ "name": "punctuation.section.arguments.begin.bracket.round.decltype.cpp" } }, + "end": "(\\))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.arguments.end.bracket.round.decltype.cpp" } }, - "contentName": "meta.arguments.decltype", "patterns": [ { "include": "#evaluation_context" @@ -3235,8 +2498,8 @@ ] }, "default_statement": { - "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\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)|(?:explicit)|(?:mutable)|(?:virtual)|(?:inline)|(?:friend))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*)(~(?|\\?\\?>)|(?=[;>\\[\\]=]))", + "name": "meta.function.definition.special.member.destructor.cpp", + "begin": "(^((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?:constexpr|explicit|mutable|virtual|inline|friend)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*)(~(?|\\?\\?>)|(?=[;>\\[\\]=]))", "patterns": [ { + "name": "meta.head.function.definition.special.member.destructor.cpp", "begin": "\\G ?", - "end": "(?:\\{|<%|\\?\\?<|(?=;))", - "beginCaptures": {}, + "end": "((?:\\{|<%|\\?\\?<|(?=;)))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.begin.bracket.curly.function.definition.special.member.destructor.cpp" } }, - "name": "meta.head.function.definition.special.member.destructor.cpp", "patterns": [ { "include": "#ever_present_context" }, { - "match": "(\\=)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(default)|(delete))", - "captures": { - "1": { - "name": "keyword.operator.assignment.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + "patterns": [ + { + "match": "(\\=)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(default)|(delete))", + "captures": { + "1": { + "name": "keyword.operator.assignment.cpp" }, - { - "match": "\\*", + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "keyword.other.default.constructor.cpp" + }, + "7": { + "name": "keyword.other.delete.constructor.cpp" } - ] - }, - "6": { - "name": "keyword.other.default.constructor.cpp" - }, - "7": { - "name": "keyword.other.delete.constructor.cpp" + } } - } + ] }, { - "begin": "\\(", - "end": "\\)", + "contentName": "meta.function.definition.parameters.special.member.destructor.cpp", + "begin": "(\\()", "beginCaptures": { - "0": { + "1": { "name": "punctuation.section.parameters.begin.bracket.round.special.member.destructor.cpp" } }, + "end": "(\\))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.parameters.end.bracket.round.special.member.destructor.cpp" } - }, - "contentName": "meta.function.definition.parameters.special.member.destructor", - "patterns": [] + } }, { "include": "$self" @@ -3481,15 +2745,14 @@ ] }, { + "name": "meta.body.function.definition.special.member.destructor.cpp", "begin": "(?<=\\{|<%|\\?\\?<)", - "end": "\\}|%>|\\?\\?>", - "beginCaptures": {}, + "end": "(\\}|%>|\\?\\?>)", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.end.bracket.curly.function.definition.special.member.destructor.cpp" } }, - "name": "meta.body.function.definition.special.member.destructor.cpp", "patterns": [ { "include": "#function_body_context" @@ -3497,11 +2760,9 @@ ] }, { - "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)", - "beginCaptures": {}, - "endCaptures": {}, "name": "meta.tail.function.definition.special.member.destructor.cpp", + "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)", "patterns": [ { "include": "$self" @@ -3511,26 +2772,26 @@ ] }, "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:(?:\\s)+)?)?::)*)(((?>(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))::((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))~\\14((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\())", - "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", + "name": "meta.function.definition.special.member.destructor.cpp", + "begin": "(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<14>?)+)>)\\s*)?::)*)(((?>(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))::((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))~\\16((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\()))", "beginCaptures": { - "0": { + "1": { "name": "meta.head.function.definition.special.member.destructor.cpp" }, - "1": { + "2": { "patterns": [ { "include": "#inline_comment" } ] }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, "3": { - "name": "comment.block.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "4": { + "name": "comment.block.cpp" + }, + "5": { "patterns": [ { "match": "\\*\\/", @@ -3542,23 +2803,23 @@ } ] }, - "5": { + "6": { "name": "storage.type.modifier.calling-convention.cpp" }, - "6": { + "7": { "patterns": [ { "include": "#inline_comment" } ] }, - "7": { + "8": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "8": { + "9": { "name": "comment.block.cpp" }, - "9": { + "10": { "patterns": [ { "match": "\\*\\/", @@ -3570,7 +2831,7 @@ } ] }, - "10": { + "11": { "patterns": [ { "match": "::", @@ -3585,7 +2846,7 @@ } ] }, - "11": { + "13": { "name": "meta.template.call.cpp", "patterns": [ { @@ -3593,8 +2854,7 @@ } ] }, - "12": {}, - "13": { + "15": { "patterns": [ { "match": "(?:[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}))*(?=:)", @@ -3610,46 +2870,20 @@ } ] }, - "14": {}, - "15": { + "17": { "patterns": [ { "include": "#inline_comment" } ] }, - "16": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "17": { - "name": "comment.block.cpp" - }, "18": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "19": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "20": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "21": { "name": "comment.block.cpp" }, - "22": { + "20": { "patterns": [ { "match": "\\*\\/", @@ -3661,20 +2895,45 @@ } ] }, - "23": { + "21": { "patterns": [ { "include": "#inline_comment" } ] }, - "24": { + "22": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "25": { + "23": { "name": "comment.block.cpp" }, + "24": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "25": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, "26": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "27": { + "name": "comment.block.cpp" + }, + "28": { "patterns": [ { "match": "\\*\\/", @@ -3687,77 +2946,78 @@ ] } }, - "endCaptures": {}, - "name": "meta.function.definition.special.member.destructor.cpp", + "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", "patterns": [ { + "name": "meta.head.function.definition.special.member.destructor.cpp", "begin": "\\G ?", - "end": "(?:\\{|<%|\\?\\?<|(?=;))", - "beginCaptures": {}, + "end": "((?:\\{|<%|\\?\\?<|(?=;)))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.begin.bracket.curly.function.definition.special.member.destructor.cpp" } }, - "name": "meta.head.function.definition.special.member.destructor.cpp", "patterns": [ { "include": "#ever_present_context" }, { - "match": "(\\=)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(default)|(delete))", - "captures": { - "1": { - "name": "keyword.operator.assignment.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + "patterns": [ + { + "match": "(\\=)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(default)|(delete))", + "captures": { + "1": { + "name": "keyword.operator.assignment.cpp" }, - { - "match": "\\*", + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "keyword.other.default.constructor.cpp" + }, + "7": { + "name": "keyword.other.delete.constructor.cpp" } - ] - }, - "6": { - "name": "keyword.other.default.constructor.cpp" - }, - "7": { - "name": "keyword.other.delete.constructor.cpp" + } } - } + ] }, { - "begin": "\\(", - "end": "\\)", + "contentName": "meta.function.definition.parameters.special.member.destructor.cpp", + "begin": "(\\()", "beginCaptures": { - "0": { + "1": { "name": "punctuation.section.parameters.begin.bracket.round.special.member.destructor.cpp" } }, + "end": "(\\))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.parameters.end.bracket.round.special.member.destructor.cpp" } - }, - "contentName": "meta.function.definition.parameters.special.member.destructor", - "patterns": [] + } }, { "include": "$self" @@ -3765,15 +3025,14 @@ ] }, { + "name": "meta.body.function.definition.special.member.destructor.cpp", "begin": "(?<=\\{|<%|\\?\\?<)", - "end": "\\}|%>|\\?\\?>", - "beginCaptures": {}, + "end": "(\\}|%>|\\?\\?>)", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.end.bracket.curly.function.definition.special.member.destructor.cpp" } }, - "name": "meta.body.function.definition.special.member.destructor.cpp", "patterns": [ { "include": "#function_body_context" @@ -3781,11 +3040,9 @@ ] }, { - "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)", - "beginCaptures": {}, - "endCaptures": {}, "name": "meta.tail.function.definition.special.member.destructor.cpp", + "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)", "patterns": [ { "include": "$self" @@ -3795,8 +3052,8 @@ ] }, "diagnostic": { - "begin": "(^((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?((?:error|warning)))\\b(?:(?:\\s)+)?", - "end": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*((?:error|warning)))\\b\\s*", "beginCaptures": { "1": { "name": "keyword.control.directive.diagnostic.$7.cpp" @@ -3828,46 +3085,24 @@ }, "6": { "name": "punctuation.definition.directive.cpp" - }, - "7": {} + } }, - "endCaptures": {}, - "name": "meta.preprocessor.diagnostic.$reference(directive).cpp", + "end": "(?[#;\\/=*C~]+)(?![#;\\/=*C~]))\\s*.+\\s*\\8\\s*(?:\\n|$)))|(^\\s*((\\/\\*)\\s*?((?>[#;\\/=*C~]+)(?![#;\\/=*C~]))\\s*.+\\s*\\8\\s*\\*\\/)))", "captures": { "1": { "name": "meta.toc-list.banner.double-slash.cpp" @@ -3921,23 +3174,23 @@ } }, "empty_square_brackets": { - "name": "storage.modifier.array.bracket.square", - "match": "(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:(?:\\s)+)?)?(::))?(?:(?:\\s)+)?((?|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))", + "name": "meta.block.enum.cpp", + "begin": "(((?(?:(?>[^<>]*)\\g<15>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<15>?)+)>)\\s*)?(::))?\\s*((?|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))", "endCaptures": { "1": { "name": "punctuation.terminator.statement.cpp" @@ -3999,18 +3251,16 @@ "name": "punctuation.terminator.statement.cpp" } }, - "name": "meta.block.enum.cpp", "patterns": [ { + "name": "meta.head.enum.cpp", "begin": "\\G ?", - "end": "(?:\\{|<%|\\?\\?<|(?=;))", - "beginCaptures": {}, + "end": "((?:\\{|<%|\\?\\?<|(?=;)))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.begin.bracket.curly.enum.cpp" } }, - "name": "meta.head.enum.cpp", "patterns": [ { "include": "$self" @@ -4018,15 +3268,14 @@ ] }, { + "name": "meta.body.enum.cpp", "begin": "(?<=\\{|<%|\\?\\?<)", - "end": "\\}|%>|\\?\\?>", - "beginCaptures": {}, + "end": "(\\}|%>|\\?\\?>)", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.end.bracket.curly.enum.cpp" } }, - "name": "meta.body.enum.cpp", "patterns": [ { "include": "#ever_present_context" @@ -4046,11 +3295,9 @@ ] }, { - "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)", - "beginCaptures": {}, - "endCaptures": {}, "name": "meta.tail.enum.cpp", + "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)", "patterns": [ { "include": "$self" @@ -4060,7 +3307,7 @@ ] }, "enum_declare": { - "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\S)(?![:{])", + "match": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\S)(?![:{])", "captures": { "1": { "name": "storage.type.enum.declare.cpp" @@ -4073,43 +3320,34 @@ ] }, "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "4": { + "6": { "name": "entity.name.type.enum.cpp" }, - "5": { + "7": { "patterns": [ { "match": "\\*", "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ @@ -4145,40 +3383,6 @@ } ] }, - "6": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "7": { - "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" - } - ] - } - } - } - ] - }, "8": { "patterns": [ { @@ -4187,107 +3391,105 @@ ] }, "9": { - "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" - } - ] - } - } - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp" }, "11": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "12": { - "name": "variable.other.object.declare.cpp" - }, - "13": { "patterns": [ { "include": "#inline_comment" } ] }, + "13": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "14": { + "name": "comment.block.cpp" + }, + "15": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "16": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "17": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "18": { + "name": "comment.block.cpp" + }, + "19": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "20": { + "name": "variable.other.object.declare.cpp" + }, + "21": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "22": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "23": { + "name": "comment.block.cpp" + }, + "24": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] } } }, "enumerator_list": { - "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\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" - } - ] - } - } - } - ] - }, - "3": { - "name": "keyword.control.exception.$3.cpp" - } - } - }, - "extern_block": { - "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(extern)(?=\\s*\\\")", - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))", - "beginCaptures": { - "0": { - "name": "meta.head.extern.cpp" - }, "1": { "patterns": [ { @@ -4528,9 +3690,47 @@ ] }, "5": { + "name": "keyword.control.exception.$5.cpp" + } + } + }, + "extern_block": { + "name": "meta.block.extern.cpp", + "begin": "(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(extern)(?=\\s*\\\"))", + "beginCaptures": { + "1": { + "name": "meta.head.extern.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { "name": "storage.type.extern.cpp" } }, + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))", "endCaptures": { "1": { "name": "punctuation.terminator.statement.cpp" @@ -4539,18 +3739,16 @@ "name": "punctuation.terminator.statement.cpp" } }, - "name": "meta.block.extern.cpp", "patterns": [ { + "name": "meta.head.extern.cpp", "begin": "\\G ?", - "end": "(?:\\{|<%|\\?\\?<|(?=;))", - "beginCaptures": {}, + "end": "((?:\\{|<%|\\?\\?<|(?=;)))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.begin.bracket.curly.extern.cpp" } }, - "name": "meta.head.extern.cpp", "patterns": [ { "include": "$self" @@ -4558,15 +3756,14 @@ ] }, { + "name": "meta.body.extern.cpp", "begin": "(?<=\\{|<%|\\?\\?<)", - "end": "\\}|%>|\\?\\?>", - "beginCaptures": {}, + "end": "(\\}|%>|\\?\\?>)", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.end.bracket.curly.extern.cpp" } }, - "name": "meta.body.extern.cpp", "patterns": [ { "include": "$self" @@ -4574,11 +3771,9 @@ ] }, { - "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)", - "beginCaptures": {}, - "endCaptures": {}, "name": "meta.tail.extern.cpp", + "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)", "patterns": [ { "include": "$self" @@ -4617,7 +3812,7 @@ "include": "#typedef_union" }, { - "include": "#misc_keywords" + "include": "#typedef_keyword" }, { "include": "#standard_declares" @@ -4664,8 +3859,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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\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": "\\)", + "begin": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<12>?)+)>)\\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(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(((?(?:(?>[^<>]*)\\g<12>?)+)>)\\s*)?(\\()", "beginCaptures": { "1": { "patterns": [ @@ -4677,7 +3871,7 @@ "2": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.call.cpp" }, - "3": { + "4": { "name": "meta.template.call.cpp", "patterns": [ { @@ -4685,24 +3879,23 @@ } ] }, - "4": {}, - "5": { + "6": { "name": "entity.name.function.call.cpp" }, - "6": { + "7": { "patterns": [ { "include": "#inline_comment" } ] }, - "7": { + "8": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "8": { + "9": { "name": "comment.block.cpp" }, - "9": { + "10": { "patterns": [ { "match": "\\*\\/", @@ -4714,7 +3907,7 @@ } ] }, - "10": { + "11": { "name": "meta.template.call.cpp", "patterns": [ { @@ -4722,13 +3915,13 @@ } ] }, - "11": {}, - "12": { + "13": { "name": "punctuation.section.arguments.begin.bracket.round.function.call.cpp" } }, + "end": "(\\))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.arguments.end.bracket.round.function.call.cpp" } }, @@ -4739,26 +3932,26 @@ ] }, "function_definition": { - "begin": "(?:(?:^|\\G|(?<=;|\\}))|(?<=>))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*)(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<66>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<66>?)+>)(?:(?:\\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<66>?)+>)(?:(?:\\s)+)?)?(?![\\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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<66>?)+>)(?:(?:\\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": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", + "name": "meta.function.definition.cpp", + "begin": "((?:(?:^|\\G|(?<=;|\\}))|(?<=>))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*)(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<70>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<70>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|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<70>?)+)>)\\s*)?(?![\\w<:.]))(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<70>?)+)>)\\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(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\())", "beginCaptures": { - "0": { + "1": { "name": "meta.head.function.definition.cpp" }, - "1": { + "2": { "patterns": [ { "include": "#inline_comment" } ] }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, "3": { - "name": "comment.block.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "4": { + "name": "comment.block.cpp" + }, + "5": { "patterns": [ { "match": "\\*\\/", @@ -4770,38 +3963,38 @@ } ] }, - "5": { + "6": { "name": "storage.type.template.cpp" }, - "6": { + "7": { "patterns": [ { "include": "#inline_comment" } ] }, - "7": { + "8": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "8": { - "name": "comment.block.cpp" - }, "9": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] + "name": "comment.block.cpp" }, "10": { "patterns": [ { - "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))", + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "11": { + "patterns": [ + { + "match": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))", "captures": { "1": { "name": "storage.modifier.$1.cpp" @@ -4835,23 +4028,23 @@ } ] }, - "11": { - "name": "storage.modifier.$11.cpp" - }, "12": { + "name": "storage.modifier.$1.cpp" + }, + "13": { "patterns": [ { "include": "#inline_comment" } ] }, - "13": { + "14": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "14": { + "15": { "name": "comment.block.cpp" }, - "15": { + "16": { "patterns": [ { "match": "\\*\\/", @@ -4863,11 +4056,11 @@ } ] }, - "16": { + "17": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?", - "beginCaptures": { - "0": { - "name": "punctuation.section.angle-brackets.begin.template.call.cpp" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.section.angle-brackets.end.template.call.cpp" - } - }, - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_context" - } - ] + "include": "#template_call_range" }, { "match": "(?:[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}))*", @@ -4917,7 +4093,7 @@ } ] }, - "17": { + "18": { "patterns": [ { "include": "#attributes_context" @@ -4927,45 +4103,45 @@ } ] }, - "18": { + "19": { "patterns": [ { "include": "#inline_comment" } ] }, - "19": { + "20": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "20": { - "name": "comment.block.cpp" - }, "21": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] + "name": "comment.block.cpp" }, "22": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "23": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "24": { - "name": "comment.block.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "25": { + "name": "comment.block.cpp" + }, + "26": { "patterns": [ { "match": "\\*\\/", @@ -4977,28 +4153,16 @@ } ] }, - "26": { + "28": { "patterns": [ { "include": "#scope_resolution_inner_generated" } ] }, - "27": { + "29": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, - "28": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "29": {}, - "30": { - "name": "entity.name.scope-resolution.cpp" - }, "31": { "name": "meta.template.call.cpp", "patterns": [ @@ -5007,24 +4171,34 @@ } ] }, - "32": {}, "33": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + "name": "entity.name.scope-resolution.cpp" }, "34": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "36": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + }, + "37": { "patterns": [ { "include": "#inline_comment" } ] }, - "35": { + "38": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "36": { + "39": { "name": "comment.block.cpp" }, - "37": { + "40": { "patterns": [ { "match": "\\*\\/", @@ -5036,10 +4210,10 @@ } ] }, - "38": { + "41": { "name": "entity.name.type.cpp" }, - "39": { + "42": { "name": "meta.template.call.cpp", "patterns": [ { @@ -5047,15 +4221,14 @@ } ] }, - "40": {}, - "41": { + "44": { "patterns": [ { "match": "\\*", "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ @@ -5091,95 +4264,95 @@ } ] }, - "42": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "43": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "44": { - "name": "comment.block.cpp" - }, "45": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "46": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "47": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "48": { + "47": { "name": "comment.block.cpp" }, + "48": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "49": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "50": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "51": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "52": { + "51": { "name": "comment.block.cpp" }, + "52": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "53": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "54": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "55": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "56": { + "55": { "name": "comment.block.cpp" }, + "56": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "57": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "58": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "59": { + "name": "comment.block.cpp" + }, + "60": { "patterns": [ { "match": "\\*\\/", @@ -5191,23 +4364,23 @@ } ] }, - "58": { + "61": { "name": "storage.type.modifier.calling-convention.cpp" }, - "59": { + "62": { "patterns": [ { "include": "#inline_comment" } ] }, - "60": { + "63": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "61": { + "64": { "name": "comment.block.cpp" }, - "62": { + "65": { "patterns": [ { "match": "\\*\\/", @@ -5219,17 +4392,17 @@ } ] }, - "63": { + "66": { "patterns": [ { "include": "#scope_resolution_function_definition_inner_generated" } ] }, - "64": { + "67": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.cpp" }, - "65": { + "69": { "name": "meta.template.call.cpp", "patterns": [ { @@ -5237,24 +4410,23 @@ } ] }, - "66": {}, - "67": { + "71": { "name": "entity.name.function.definition.cpp" }, - "68": { + "72": { "patterns": [ { "include": "#inline_comment" } ] }, - "69": { + "73": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "70": { + "74": { "name": "comment.block.cpp" }, - "71": { + "75": { "patterns": [ { "match": "\\*\\/", @@ -5267,37 +4439,35 @@ ] } }, - "endCaptures": {}, - "name": "meta.function.definition.cpp", + "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", "patterns": [ { + "name": "meta.head.function.definition.cpp", "begin": "\\G ?", - "end": "(?:\\{|<%|\\?\\?<|(?=;))", - "beginCaptures": {}, + "end": "((?:\\{|<%|\\?\\?<|(?=;)))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.begin.bracket.curly.function.definition.cpp" } }, - "name": "meta.head.function.definition.cpp", "patterns": [ { "include": "#ever_present_context" }, { - "begin": "\\(", - "end": "\\)", + "contentName": "meta.function.definition.parameters.cpp", + "begin": "(\\()", "beginCaptures": { - "0": { + "1": { "name": "punctuation.section.parameters.begin.bracket.round.cpp" } }, + "end": "(\\))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.parameters.end.bracket.round.cpp" } }, - "contentName": "meta.function.definition.parameters", "patterns": [ { "include": "#ever_present_context" @@ -5319,15 +4489,14 @@ ] }, { + "name": "meta.body.function.definition.cpp", "begin": "(?<=\\{|<%|\\?\\?<)", - "end": "\\}|%>|\\?\\?>", - "beginCaptures": {}, + "end": "(\\}|%>|\\?\\?>)", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.end.bracket.curly.function.definition.cpp" } }, - "name": "meta.body.function.definition.cpp", "patterns": [ { "include": "#function_body_context" @@ -5335,11 +4504,9 @@ ] }, { - "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)", - "beginCaptures": {}, - "endCaptures": {}, "name": "meta.tail.function.definition.cpp", + "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)", "patterns": [ { "include": "$self" @@ -5362,14 +4529,13 @@ ] }, "function_pointer": { - "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<25>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<25>?)+>)(?:(?:\\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<25>?)+>)(?:(?:\\s)+)?)?(?![\\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)(?!\\()", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|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<27>?)+)>)\\s*)?(?![\\w<:.]))(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\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*(\\()", "beginCaptures": { "1": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?", - "beginCaptures": { - "0": { - "name": "punctuation.section.angle-brackets.begin.template.call.cpp" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.section.angle-brackets.end.template.call.cpp" - } - }, - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_context" - } - ] + "include": "#template_call_range" }, { "match": "(?:[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}))*", @@ -5479,17 +4628,17 @@ } ] }, - "11": { + "12": { "patterns": [ { "include": "#scope_resolution_inner_generated" } ] }, - "12": { + "13": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, - "13": { + "15": { "name": "meta.template.call.cpp", "patterns": [ { @@ -5497,11 +4646,10 @@ } ] }, - "14": {}, - "15": { + "17": { "name": "entity.name.scope-resolution.cpp" }, - "16": { + "18": { "name": "meta.template.call.cpp", "patterns": [ { @@ -5509,24 +4657,23 @@ } ] }, - "17": {}, - "18": { + "20": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, - "19": { + "21": { "patterns": [ { "include": "#inline_comment" } ] }, - "20": { + "22": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "21": { + "23": { "name": "comment.block.cpp" }, - "22": { + "24": { "patterns": [ { "match": "\\*\\/", @@ -5538,10 +4685,10 @@ } ] }, - "23": { + "25": { "name": "entity.name.type.cpp" }, - "24": { + "26": { "name": "meta.template.call.cpp", "patterns": [ { @@ -5549,15 +4696,14 @@ } ] }, - "25": {}, - "26": { + "28": { "patterns": [ { "match": "\\*", "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ @@ -5593,45 +4739,20 @@ } ] }, - "27": { + "29": { "patterns": [ { "include": "#inline_comment" } ] }, - "28": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "29": { - "name": "comment.block.cpp" - }, "30": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "31": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp" }, "32": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "33": { - "name": "comment.block.cpp" - }, - "34": { "patterns": [ { "match": "\\*\\/", @@ -5643,20 +4764,20 @@ } ] }, - "35": { + "33": { "patterns": [ { "include": "#inline_comment" } ] }, - "36": { + "34": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "37": { + "35": { "name": "comment.block.cpp" }, - "38": { + "36": { "patterns": [ { "match": "\\*\\/", @@ -5668,35 +4789,61 @@ } ] }, + "37": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "38": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "39": { - "name": "punctuation.section.parens.begin.bracket.round.function.pointer.cpp" + "name": "comment.block.cpp" }, "40": { - "name": "punctuation.definition.function.pointer.dereference.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "41": { - "name": "variable.other.definition.pointer.function.cpp" + "name": "punctuation.section.parens.begin.bracket.round.function.pointer.cpp" }, "42": { - "name": "punctuation.definition.begin.bracket.square.cpp" + "name": "punctuation.definition.function.pointer.dereference.cpp" }, "43": { + "name": "variable.other.definition.pointer.function.cpp" + }, + "44": { + "name": "punctuation.definition.begin.bracket.square.cpp" + }, + "45": { "patterns": [ { "include": "#evaluation_context" } ] }, - "44": { + "46": { "name": "punctuation.definition.end.bracket.square.cpp" }, - "45": { + "47": { "name": "punctuation.section.parens.end.bracket.round.function.pointer.cpp" }, - "46": { + "48": { "name": "punctuation.section.parameters.begin.bracket.round.function.pointer.cpp" } }, + "end": "(\\))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=[{=,);]|\\n)(?!\\()", "endCaptures": { "1": { "name": "punctuation.section.parameters.end.bracket.round.function.pointer.cpp" @@ -5734,14 +4881,13 @@ ] }, "function_pointer_parameter": { - "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<25>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<25>?)+>)(?:(?:\\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<25>?)+>)(?:(?:\\s)+)?)?(?![\\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)(?!\\()", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|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<27>?)+)>)\\s*)?(?![\\w<:.]))(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\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*(\\()", "beginCaptures": { "1": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?", - "beginCaptures": { - "0": { - "name": "punctuation.section.angle-brackets.begin.template.call.cpp" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.section.angle-brackets.end.template.call.cpp" - } - }, - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_context" - } - ] + "include": "#template_call_range" }, { "match": "(?:[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}))*", @@ -5851,17 +4980,17 @@ } ] }, - "11": { + "12": { "patterns": [ { "include": "#scope_resolution_inner_generated" } ] }, - "12": { + "13": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, - "13": { + "15": { "name": "meta.template.call.cpp", "patterns": [ { @@ -5869,11 +4998,10 @@ } ] }, - "14": {}, - "15": { + "17": { "name": "entity.name.scope-resolution.cpp" }, - "16": { + "18": { "name": "meta.template.call.cpp", "patterns": [ { @@ -5881,24 +5009,23 @@ } ] }, - "17": {}, - "18": { + "20": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, - "19": { + "21": { "patterns": [ { "include": "#inline_comment" } ] }, - "20": { + "22": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "21": { + "23": { "name": "comment.block.cpp" }, - "22": { + "24": { "patterns": [ { "match": "\\*\\/", @@ -5910,10 +5037,10 @@ } ] }, - "23": { + "25": { "name": "entity.name.type.cpp" }, - "24": { + "26": { "name": "meta.template.call.cpp", "patterns": [ { @@ -5921,15 +5048,14 @@ } ] }, - "25": {}, - "26": { + "28": { "patterns": [ { "match": "\\*", "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ @@ -5965,6 +5091,379 @@ } ] }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "37": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "38": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "39": { + "name": "comment.block.cpp" + }, + "40": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "41": { + "name": "punctuation.section.parens.begin.bracket.round.function.pointer.cpp" + }, + "42": { + "name": "punctuation.definition.function.pointer.dereference.cpp" + }, + "43": { + "name": "variable.parameter.pointer.function.cpp" + }, + "44": { + "name": "punctuation.definition.begin.bracket.square.cpp" + }, + "45": { + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + "46": { + "name": "punctuation.definition.end.bracket.square.cpp" + }, + "47": { + "name": "punctuation.section.parens.end.bracket.round.function.pointer.cpp" + }, + "48": { + "name": "punctuation.section.parameters.begin.bracket.round.function.pointer.cpp" + } + }, + "end": "(\\))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=[{=,);]|\\n)(?!\\()", + "endCaptures": { + "1": { + "name": "punctuation.section.parameters.end.bracket.round.function.pointer.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + }, + "patterns": [ + { + "include": "#function_parameter_context" + } + ] + }, + "functional_specifiers_pre_parameters": { + "match": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\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": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "entity.name.label.call.cpp" + } + } + }, + "include": { + "match": "(?:^)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((#)\\s*((?:include|include_next))\\b)\\s*(?:(?:(?:((<)[^>]*(>?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=\\/\\/)))|((\\\")[^\\\"]*(\\\"?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=\\/\\/))))|(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\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}))*(?:\\.(?:[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|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=(?:\\/\\/|;)))))|((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=(?:\\/\\/|;))))", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "5": { + "name": "keyword.control.directive.$7.cpp" + }, + "6": { + "name": "punctuation.definition.directive.cpp" + }, + "8": { + "name": "string.quoted.other.lt-gt.include.cpp" + }, + "9": { + "name": "punctuation.definition.string.begin.cpp" + }, + "10": { + "name": "punctuation.definition.string.end.cpp" + }, + "11": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "12": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "13": { + "name": "comment.block.cpp" + }, + "14": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "15": { + "name": "string.quoted.double.include.cpp" + }, + "16": { + "name": "punctuation.definition.string.begin.cpp" + }, + "17": { + "name": "punctuation.definition.string.end.cpp" + }, + "18": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "19": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "20": { + "name": "comment.block.cpp" + }, + "21": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "22": { + "name": "entity.name.other.preprocessor.macro.include.cpp" + }, + "23": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "24": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "25": { + "name": "comment.block.cpp" + }, + "26": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "27": { "patterns": [ { @@ -6014,446 +5513,6 @@ "name": "comment.block.cpp" } ] - }, - "35": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "36": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "37": { - "name": "comment.block.cpp" - }, - "38": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "39": { - "name": "punctuation.section.parens.begin.bracket.round.function.pointer.cpp" - }, - "40": { - "name": "punctuation.definition.function.pointer.dereference.cpp" - }, - "41": { - "name": "variable.parameter.pointer.function.cpp" - }, - "42": { - "name": "punctuation.definition.begin.bracket.square.cpp" - }, - "43": { - "patterns": [ - { - "include": "#evaluation_context" - } - ] - }, - "44": { - "name": "punctuation.definition.end.bracket.square.cpp" - }, - "45": { - "name": "punctuation.section.parens.end.bracket.round.function.pointer.cpp" - }, - "46": { - "name": "punctuation.section.parameters.begin.bracket.round.function.pointer.cpp" - } - }, - "endCaptures": { - "1": { - "name": "punctuation.section.parameters.end.bracket.round.function.pointer.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - }, - "patterns": [ - { - "include": "#function_parameter_context" - } - ] - }, - "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}))*)", - "captures": { - "1": { - "name": "keyword.control.goto.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "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" - } - ] - } - } - } - ] - }, - "4": { - "name": "entity.name.label.call.cpp" - } - } - }, - "identifier": { - "match": "(?:[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}))*" - }, - "include": { - "match": "^((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((#)(?:(?:\\s)+)?((?:include|include_next))\\b)(?:(?:\\s)+)?(?:(?:(?:((<)[^>]*(>?)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:\\n)|$)|(?=\\/\\/)))|((\\\")[^\\\"]*((?:\\\")?)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:\\n)|$)|(?=\\/\\/))))|(((?:((?:(?>(?:\\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}))*(?:\\.(?:[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)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:\\n)|$)|(?=(?:\\/\\/|;)))))|((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:\\n)|$)|(?=(?:\\/\\/|;))))", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "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" - } - ] - } - } - } - ] - }, - "3": { - "name": "keyword.control.directive.$5.cpp" - }, - "4": { - "name": "punctuation.definition.directive.cpp" - }, - "6": { - "name": "string.quoted.other.lt-gt.include.cpp" - }, - "7": { - "name": "punctuation.definition.string.begin.cpp" - }, - "8": { - "name": "punctuation.definition.string.end.cpp" - }, - "9": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "10": { - "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" - } - ] - } - } - } - ] - }, - "11": { - "name": "string.quoted.double.include.cpp" - }, - "12": { - "name": "punctuation.definition.string.begin.cpp" - }, - "13": { - "name": "punctuation.definition.string.end.cpp" - }, - "14": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "15": { - "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" - } - ] - } - } - } - ] - }, - "16": { - "name": "entity.name.other.preprocessor.macro.include.cpp" - }, - "17": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "18": { - "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" - } - ] - } - } - } - ] - }, - "19": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "20": { - "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" - } - ] - } - } - } - ] - }, - "21": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "22": { - "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" - } - ] - } - } - } - ] } }, "name": "meta.preprocessor.include.cpp" @@ -6468,7 +5527,7 @@ "name": "punctuation.separator.delimiter.comma.inheritance.cpp" }, { - "match": "(?(?:\\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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<19>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<19>?)+>)(?:(?:\\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<19>?)+>)(?:(?:\\s)+)?)?(?![\\w<:.]))", + "match": "(?<=protected|virtual|private|public|,|:)\\s*(?!(?:(?:protected|private|public)|virtual))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|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<27>?)+)>)\\s*)?(?![\\w<:.]))", "captures": { "1": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?", - "beginCaptures": { - "0": { - "name": "punctuation.section.angle-brackets.begin.template.call.cpp" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.section.angle-brackets.end.template.call.cpp" - } - }, - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_context" - } - ] + "include": "#template_call_range" }, { "match": "(?:[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}))*", @@ -6550,99 +5592,59 @@ ] }, "4": { - "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" - } - ] - } - } - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "5": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp" }, "6": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "7": { "patterns": [ { - "include": "#scope_resolution_inner_generated" + "include": "#inline_comment" } ] }, "8": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "9": { + "name": "comment.block.cpp" + }, + "10": { "patterns": [ { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "10": {}, - "11": { - "name": "entity.name.scope-resolution.cpp" - }, "12": { + "patterns": [ + { + "include": "#scope_resolution_inner_generated" + } + ] + }, + "13": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + }, + "15": { "name": "meta.template.call.cpp", "patterns": [ { @@ -6650,46 +5652,8 @@ } ] }, - "13": {}, - "14": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" - }, - "15": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "16": { - "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" - } - ] - } - } - } - ] - }, "17": { - "name": "entity.name.type.cpp" + "name": "entity.name.scope-resolution.cpp" }, "18": { "name": "meta.template.call.cpp", @@ -6699,13 +5663,51 @@ } ] }, - "19": {} + "20": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + }, + "21": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "22": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "23": { + "name": "comment.block.cpp" + }, + "24": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "25": { + "name": "entity.name.type.cpp" + }, + "26": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + } } } ] }, "inline_comment": { - "match": "(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/))", + "match": "(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/))", "captures": { "1": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" @@ -6732,7 +5734,7 @@ "name": "invalid.illegal.unexpected.punctuation.definition.comment.end.cpp" }, "label": { - "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:)", + "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)", "captures": { "1": { "patterns": [ @@ -6742,89 +5744,70 @@ ] }, "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "3": { + "5": { "name": "entity.name.label.cpp" }, - "4": { + "6": { "patterns": [ { "include": "#inline_comment" } ] }, - "5": { + "7": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "8": { + "name": "comment.block.cpp" + }, + "9": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "6": { + "10": { "name": "punctuation.separator.label.cpp" } } }, "lambdas": { - "begin": "(?:(?<=[^\\s]|^)(?])|(?<=\\Wreturn|^return))(?:(?:\\s)+)?(\\[(?!\\[| *+\"| *+\\d))((?:[^\\[\\]]|((??)++\\]))*+)(\\](?!((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))[\\[\\];]))", - "end": "(?<=[;}])", + "begin": "((?:(?<=[^\\s]|^)(?])|(?<=\\Wreturn|^return))\\s*(\\[(?!\\[| *+\"| *+\\d))((?>(?:[^\\[\\]]|((?(?:(?>[^\\[\\]]*)\\g<4>?)+)\\]))*))(\\](?!\\[)))", "beginCaptures": { - "1": { + "2": { "name": "punctuation.definition.capture.begin.lambda.cpp" }, - "2": { + "3": { "name": "meta.lambda.capture.cpp", "patterns": [ { "include": "#the_this_keyword" }, { - "match": "((?:[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)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?=\\]|\\z|$)|(,))|(\\=))", + "match": "((?:[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|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?=\\]|\\z|$)|(,))|(\\=))", "captures": { "1": { "name": "variable.parameter.capture.cpp" @@ -6867,52 +5850,26 @@ } ] }, - "3": {}, - "4": { - "name": "punctuation.definition.capture.end.lambda.cpp" - }, "5": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "6": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "7": { - "name": "comment.block.cpp" - }, - "8": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] + "name": "punctuation.definition.capture.end.lambda.cpp" } }, - "endCaptures": {}, + "end": "(?<=})", "patterns": [ { - "begin": "\\(", - "end": "\\)", + "name": "meta.function.definition.parameters.lambda.cpp", + "begin": "(\\()", "beginCaptures": { - "0": { + "1": { "name": "punctuation.definition.parameters.begin.lambda.cpp" } }, + "end": "(\\))", "endCaptures": { - "0": { + "1": { "name": "punctuation.definition.parameters.end.lambda.cpp" } }, - "name": "meta.function.definition.parameters.lambda.cpp", "patterns": [ { "include": "#function_parameter_context" @@ -6920,7 +5877,7 @@ ] }, { - "match": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?line\\b", - "end": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*line\\b)", "beginCaptures": { - "0": { + "1": { "name": "keyword.control.directive.line.cpp" }, - "1": { + "2": { "patterns": [ { "include": "#inline_comment" } ] }, - "2": { + "3": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "3": { + "4": { "name": "comment.block.cpp" }, - "4": { + "5": { "patterns": [ { "match": "\\*\\/", @@ -6992,12 +5949,11 @@ } ] }, - "5": { + "6": { "name": "punctuation.definition.directive.cpp" } }, - "endCaptures": {}, - "name": "meta.preprocessor.line.cpp", + "end": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?define\\b)(?:(?:\\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_least64_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_least16_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_least32_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_least16_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_fast64_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_fast32_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_fast16_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_least8_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_least64_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_least32_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_fast32_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_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)]|uintptr_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|blksize_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|in_port_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)]|uint32_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|u_quad_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint16_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|intmax_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint64_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)]|size_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|signed[^Pattern.new(\n match: \\/\\w\\/,\n)]|time_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|key_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|ino_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|gid_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|dev_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|div_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|float[^Pattern.new(\n match: \\/\\w\\/,\n)]|u_int[^Pattern.new(\n match: \\/\\w\\/,\n)]|uid_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|short[^Pattern.new(\n match: \\/\\w\\/,\n)]|off_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|pid_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|id_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|bool[^Pattern.new(\n match: \\/\\w\\/,\n)]|char[^Pattern.new(\n match: \\/\\w\\/,\n)]|id_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint[^Pattern.new(\n match: \\/\\w\\/,\n)]|void[^Pattern.new(\n match: \\/\\w\\/,\n)]|long[^Pattern.new(\n match: \\/\\w\\/,\n)]|auto[^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": "(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\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_least64_t[^(?-mix:\\w)]|uint_least16_t[^(?-mix:\\w)]|uint_least32_t[^(?-mix:\\w)]|int_least16_t[^(?-mix:\\w)]|uint_fast64_t[^(?-mix:\\w)]|uint_fast32_t[^(?-mix:\\w)]|uint_fast16_t[^(?-mix:\\w)]|uint_least8_t[^(?-mix:\\w)]|int_least64_t[^(?-mix:\\w)]|int_least32_t[^(?-mix:\\w)]|int_fast32_t[^(?-mix:\\w)]|int_fast16_t[^(?-mix:\\w)]|int_least8_t[^(?-mix:\\w)]|uint_fast8_t[^(?-mix:\\w)]|int_fast64_t[^(?-mix:\\w)]|int_fast8_t[^(?-mix:\\w)]|suseconds_t[^(?-mix:\\w)]|useconds_t[^(?-mix:\\w)]|in_addr_t[^(?-mix:\\w)]|uintmax_t[^(?-mix:\\w)]|uintmax_t[^(?-mix:\\w)]|uintptr_t[^(?-mix:\\w)]|blksize_t[^(?-mix:\\w)]|in_port_t[^(?-mix:\\w)]|intmax_t[^(?-mix:\\w)]|unsigned[^(?-mix:\\w)]|blkcnt_t[^(?-mix:\\w)]|uint32_t[^(?-mix:\\w)]|u_quad_t[^(?-mix:\\w)]|uint16_t[^(?-mix:\\w)]|intmax_t[^(?-mix:\\w)]|uint64_t[^(?-mix:\\w)]|intptr_t[^(?-mix:\\w)]|swblk_t[^(?-mix:\\w)]|wchar_t[^(?-mix:\\w)]|u_short[^(?-mix:\\w)]|qaddr_t[^(?-mix:\\w)]|caddr_t[^(?-mix:\\w)]|daddr_t[^(?-mix:\\w)]|fixpt_t[^(?-mix:\\w)]|nlink_t[^(?-mix:\\w)]|segsz_t[^(?-mix:\\w)]|clock_t[^(?-mix:\\w)]|ssize_t[^(?-mix:\\w)]|int16_t[^(?-mix:\\w)]|int32_t[^(?-mix:\\w)]|int64_t[^(?-mix:\\w)]|uint8_t[^(?-mix:\\w)]|int8_t[^(?-mix:\\w)]|mode_t[^(?-mix:\\w)]|quad_t[^(?-mix:\\w)]|ushort[^(?-mix:\\w)]|u_long[^(?-mix:\\w)]|u_char[^(?-mix:\\w)]|double[^(?-mix:\\w)]|size_t[^(?-mix:\\w)]|signed[^(?-mix:\\w)]|time_t[^(?-mix:\\w)]|key_t[^(?-mix:\\w)]|ino_t[^(?-mix:\\w)]|gid_t[^(?-mix:\\w)]|dev_t[^(?-mix:\\w)]|div_t[^(?-mix:\\w)]|float[^(?-mix:\\w)]|u_int[^(?-mix:\\w)]|uid_t[^(?-mix:\\w)]|short[^(?-mix:\\w)]|off_t[^(?-mix:\\w)]|pid_t[^(?-mix:\\w)]|id_t[^(?-mix:\\w)]|bool[^(?-mix:\\w)]|char[^(?-mix:\\w)]|id_t[^(?-mix:\\w)]|uint[^(?-mix:\\w)]|void[^(?-mix:\\w)]|long[^(?-mix:\\w)]|auto[^(?-mix:\\w)]|int[^(?-mix:\\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": [ @@ -7138,48 +6092,39 @@ ] }, "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "3": { + "5": { "name": "variable.language.this.cpp" }, - "4": { + "6": { "name": "variable.other.object.access.cpp" }, - "5": { + "7": { "name": "punctuation.separator.dot-access.cpp" }, - "6": { + "8": { "name": "punctuation.separator.pointer-access.cpp" }, - "7": { + "9": { "patterns": [ { - "match": "(?<=(?:\\.\\*|\\.|->|->\\*))(?:(?:\\s)+)?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?\\*|->)))", + "match": "(?<=(?:\\.\\*|\\.|->|->\\*))\\s*(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?\\*|->)))", "captures": { "1": { "patterns": [ @@ -7221,7 +6166,7 @@ } }, { - "match": "(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?\\*|->)))", + "match": "(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?\\*|->)))", "captures": { "1": { "patterns": [ @@ -7270,13 +6215,13 @@ } ] }, - "8": { + "10": { "name": "variable.other.property.cpp" } } }, "memory_operators": { - "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(delete)(?:(?:\\s)+)?(\\[\\])|(delete))|(new))(?!\\w))", + "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?:(delete)\\s*(\\[\\])|(delete))|(new))(?!\\w))", "captures": { "1": { "patterns": [ @@ -7286,52 +6231,42 @@ ] }, "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "3": { + "5": { "name": "keyword.operator.wordlike.cpp" }, - "4": { + "6": { "name": "keyword.operator.delete.array.cpp" }, - "5": { + "7": { "name": "keyword.operator.delete.array.bracket.cpp" }, - "6": { + "8": { "name": "keyword.operator.delete.cpp" }, - "7": { + "9": { "name": "keyword.operator.new.cpp" } } }, "method_access": { - "begin": "(?:((?:(?:(?:(?>(?:\\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)+)?(~?(?:[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)+)?(\\()", - "end": "\\)", + "begin": "(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\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*(~?(?:[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*(\\()", "beginCaptures": { "1": { "patterns": [ @@ -7373,7 +6308,7 @@ "9": { "patterns": [ { - "match": "(?<=(?:\\.\\*|\\.|->|->\\*))(?:(?:\\s)+)?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?\\*|->)))", + "match": "(?<=(?:\\.\\*|\\.|->|->\\*))\\s*(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?\\*|->)))", "captures": { "1": { "patterns": [ @@ -7415,7 +6350,7 @@ } }, { - "match": "(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?\\*|->)))", + "match": "(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?\\*|->)))", "captures": { "1": { "patterns": [ @@ -7471,8 +6406,9 @@ "name": "punctuation.section.arguments.begin.bracket.round.function.member.cpp" } }, + "end": "(\\))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.arguments.end.bracket.round.function.member.cpp" } }, @@ -7482,8 +6418,12 @@ } ] }, - "misc_keywords": { - "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((import))\\s*(?:(?:(?:((<)[^>]*(>?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=\\/\\/)))|((\\\")[^\\\"]*(\\\"?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=\\/\\/))))|(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\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}))*(?:\\.(?:[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|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=(?:\\/\\/|;)))))|((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:\\n|$)|(?=(?:\\/\\/|;))))\\s*(;?)", "captures": { "1": { "patterns": [ @@ -7493,51 +6433,192 @@ ] }, "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "3": { - "name": "keyword.other.$3.cpp" + "5": { + "name": "keyword.control.directive.import.cpp" + }, + "7": { + "name": "string.quoted.other.lt-gt.include.cpp" + }, + "8": { + "name": "punctuation.definition.string.begin.cpp" + }, + "9": { + "name": "punctuation.definition.string.end.cpp" + }, + "10": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "11": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "12": { + "name": "comment.block.cpp" + }, + "13": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "14": { + "name": "string.quoted.double.include.cpp" + }, + "15": { + "name": "punctuation.definition.string.begin.cpp" + }, + "16": { + "name": "punctuation.definition.string.end.cpp" + }, + "17": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "18": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "19": { + "name": "comment.block.cpp" + }, + "20": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "21": { + "name": "entity.name.other.preprocessor.macro.include.cpp" + }, + "22": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "23": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "24": { + "name": "comment.block.cpp" + }, + "25": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "26": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "27": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "28": { + "name": "comment.block.cpp" + }, + "29": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "30": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "31": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "32": { + "name": "comment.block.cpp" + }, + "33": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "34": { + "name": "punctuation.terminator.statement.cpp" } - } + }, + "name": "meta.preprocessor.import.cpp" }, "ms_attributes": { - "begin": "__declspec\\(", - "end": "\\)", + "name": "support.other.attribute.cpp", + "begin": "(__declspec\\()", "beginCaptures": { - "0": { + "1": { "name": "punctuation.section.attribute.begin.cpp" } }, + "end": "(\\))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.attribute.end.cpp" } }, - "name": "support.other.attribute.cpp", "patterns": [ { "include": "#attributes_context" @@ -7545,8 +6626,6 @@ { "begin": "\\(", "end": "\\)", - "beginCaptures": {}, - "endCaptures": {}, "patterns": [ { "include": "#attributes_context" @@ -7557,7 +6636,7 @@ ] }, { - "match": "(using)(?:\\s)+((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<8>?)+>)(?:(?:\\s)+)?)?::)*)\\s*+)(?:(?:\\s)+)?((?(?:(?>[^<>]*)\\g<9>?)+)>)\\s*)?::)*\\s*+)\\s*((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } + "include": "#template_call_range" } ] }, - "9": { + "10": { "name": "entity.name.namespace.cpp" }, - "10": { + "11": { "name": "punctuation.terminator.statement.cpp" } }, "name": "meta.declaration.namespace.alias.cpp" }, "namespace_block": { - "begin": "((?|\\?\\?>)|(?=[;>\\[\\]=]))", + "name": "meta.block.namespace.cpp", + "begin": "(((?|\\?\\?>)|(?=[;>\\[\\]=]))", "patterns": [ { + "name": "meta.head.namespace.cpp", "begin": "\\G ?", - "end": "(?:\\{|<%|\\?\\?<|(?=;))", - "beginCaptures": {}, + "end": "((?:\\{|<%|\\?\\?<|(?=;)))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.begin.bracket.curly.namespace.cpp" } }, - "name": "meta.head.namespace.cpp", "patterns": [ { "include": "#ever_present_context" @@ -7672,7 +6739,7 @@ "include": "#attributes_context" }, { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<4>?)+>)(?:(?:\\s)+)?)?::)*\\s*+)(?:(?:\\s)+)?((?(?:(?>[^<>]*)\\g<5>?)+)>)\\s*)?::)*\\s*+)\\s*((?|\\?\\?>", - "beginCaptures": {}, + "end": "(\\}|%>|\\?\\?>)", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.end.bracket.curly.namespace.cpp" } }, - "name": "meta.body.namespace.cpp", "patterns": [ { "include": "$self" @@ -7723,11 +6788,9 @@ ] }, { - "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)", - "beginCaptures": {}, - "endCaptures": {}, "name": "meta.tail.namespace.cpp", + "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)", "patterns": [ { "include": "$self" @@ -7737,8 +6800,8 @@ ] }, "noexcept_operator": { - "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)", + "contentName": "meta.arguments.operator.noexcept.cpp", + "begin": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", "beginCaptures": { "1": { "name": "keyword.operator.functionlike.cpp keyword.operator.noexcept.cpp" @@ -7772,12 +6835,12 @@ "name": "punctuation.section.arguments.begin.bracket.round.operator.noexcept.cpp" } }, + "end": "(\\))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.arguments.end.bracket.round.operator.noexcept.cpp" } }, - "contentName": "meta.arguments.operator.noexcept", "patterns": [ { "include": "#evaluation_context" @@ -7785,7 +6848,7 @@ ] }, "non_primitive_types": { - "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "3": { + "5": { "name": "storage.type.cpp storage.type.built-in.cpp" } } @@ -7834,11 +6888,9 @@ { "begin": "(?=.)", "end": "$", - "beginCaptures": {}, - "endCaptures": {}, "patterns": [ { - "match": "(\\G0[xX])([0-9a-fA-F](?:[0-9a-fA-F]|((?<=[0-9a-fA-F])'(?=[0-9a-fA-F])))*)?((?:(?<=[0-9a-fA-F])\\.|\\.(?=[0-9a-fA-F])))([0-9a-fA-F](?:[0-9a-fA-F]|((?<=[0-9a-fA-F])'(?=[0-9a-fA-F])))*)?(?:(?(?:\\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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<62>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<62>?)+>)(?:(?:\\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<62>?)+>)(?:(?:\\s)+)?)?(?![\\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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<62>?)+>)(?:(?:\\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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<62>?)+>)(?:(?:\\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": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", + "name": "meta.function.definition.special.operator-overload.cpp", + "begin": "((?:(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|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<67>?)+)>)\\s*)?(?![\\w<:.]))(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\s*)?::)*)(operator)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\s*)?::)*)(?:(?:((?:delete\\[\\]|delete|new\\[\\]|<=>|<<=|new|>>=|\\->\\*|\\/=|%=|&=|>=|\\|=|\\+\\+|\\-\\-|\\(\\)|\\[\\]|\\->|\\+\\+|<<|>>|\\-\\-|<=|\\^=|==|!=|&&|\\|\\||\\+=|\\-=|\\*=|,|\\+|\\-|!|~|\\*|&|\\*|\\/|%|\\+|\\-|<|>|&|\\^|\\||=))|((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:\\[\\])?)))|(\"\")((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\<|\\())", "beginCaptures": { - "0": { + "1": { "name": "meta.head.function.definition.special.operator-overload.cpp" }, - "1": { + "2": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?", - "beginCaptures": { - "0": { - "name": "punctuation.section.angle-brackets.begin.template.call.cpp" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.section.angle-brackets.end.template.call.cpp" - } - }, - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_context" - } - ] + "include": "#template_call_range" }, { "match": "(?:[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}))*", @@ -8160,7 +7195,7 @@ } ] }, - "2": { + "3": { "patterns": [ { "include": "#attributes_context" @@ -8170,45 +7205,45 @@ } ] }, - "3": { + "4": { "patterns": [ { "include": "#inline_comment" } ] }, - "4": { + "5": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "5": { - "name": "comment.block.cpp" - }, "6": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] + "name": "comment.block.cpp" }, "7": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "8": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "9": { - "name": "comment.block.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "10": { + "name": "comment.block.cpp" + }, + "11": { "patterns": [ { "match": "\\*\\/", @@ -8220,28 +7255,16 @@ } ] }, - "11": { + "13": { "patterns": [ { "include": "#scope_resolution_inner_generated" } ] }, - "12": { + "14": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, - "13": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "14": {}, - "15": { - "name": "entity.name.scope-resolution.cpp" - }, "16": { "name": "meta.template.call.cpp", "patterns": [ @@ -8250,24 +7273,34 @@ } ] }, - "17": {}, "18": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + "name": "entity.name.scope-resolution.cpp" }, "19": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "21": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + }, + "22": { "patterns": [ { "include": "#inline_comment" } ] }, - "20": { + "23": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "21": { + "24": { "name": "comment.block.cpp" }, - "22": { + "25": { "patterns": [ { "match": "\\*\\/", @@ -8279,10 +7312,10 @@ } ] }, - "23": { + "26": { "name": "entity.name.type.cpp" }, - "24": { + "27": { "name": "meta.template.call.cpp", "patterns": [ { @@ -8290,15 +7323,14 @@ } ] }, - "25": {}, - "26": { + "29": { "patterns": [ { "match": "\\*", "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ @@ -8334,123 +7366,123 @@ } ] }, - "27": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "28": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "29": { - "name": "comment.block.cpp" - }, "30": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "31": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "32": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "33": { + "32": { "name": "comment.block.cpp" }, + "33": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "34": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "35": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "36": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "37": { + "36": { "name": "comment.block.cpp" }, + "37": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "38": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "39": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "40": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "41": { + "40": { "name": "comment.block.cpp" }, + "41": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "42": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "43": { - "name": "storage.type.modifier.calling-convention.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "44": { + "name": "comment.block.cpp" + }, + "45": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "45": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, "46": { - "name": "comment.block.cpp" + "name": "storage.type.modifier.calling-convention.cpp" }, "47": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "48": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "49": { + "name": "comment.block.cpp" + }, + "50": { "patterns": [ { "match": "\\*\\/", @@ -8462,20 +7494,20 @@ } ] }, - "48": { + "51": { "patterns": [ { "include": "#inline_comment" } ] }, - "49": { + "52": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "50": { + "53": { "name": "comment.block.cpp" }, - "51": { + "54": { "patterns": [ { "match": "\\*\\/", @@ -8487,7 +7519,7 @@ } ] }, - "52": { + "55": { "patterns": [ { "match": "::", @@ -8502,7 +7534,7 @@ } ] }, - "53": { + "57": { "name": "meta.template.call.cpp", "patterns": [ { @@ -8510,24 +7542,23 @@ } ] }, - "54": {}, - "55": { + "59": { "name": "keyword.other.operator.overload.cpp" }, - "56": { + "60": { "patterns": [ { "include": "#inline_comment" } ] }, - "57": { + "61": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "58": { + "62": { "name": "comment.block.cpp" }, - "59": { + "63": { "patterns": [ { "match": "\\*\\/", @@ -8539,7 +7570,7 @@ } ] }, - "60": { + "64": { "patterns": [ { "match": "::", @@ -8554,7 +7585,7 @@ } ] }, - "61": { + "66": { "name": "meta.template.call.cpp", "patterns": [ { @@ -8562,21 +7593,20 @@ } ] }, - "62": {}, - "63": { + "68": { "name": "entity.name.operator.cpp" }, - "64": { + "69": { "name": "entity.name.operator.type.cpp" }, - "65": { + "70": { "patterns": [ { "match": "\\*", "name": "entity.name.operator.type.pointer.cpp" }, { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ @@ -8612,101 +7642,45 @@ } ] }, - "66": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "67": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "68": { - "name": "comment.block.cpp" - }, - "69": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "70": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, "71": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "72": { - "name": "comment.block.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "73": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] + "name": "comment.block.cpp" }, "74": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "75": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "76": { - "name": "comment.block.cpp" - }, - "77": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "78": { - "name": "entity.name.operator.type.array.cpp" - }, - "79": { - "name": "entity.name.operator.custom-literal.cpp" - }, - "80": { "patterns": [ { "include": "#inline_comment" } ] }, - "81": { + "76": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "82": { + "77": { "name": "comment.block.cpp" }, - "83": { + "78": { "patterns": [ { "match": "\\*\\/", @@ -8718,6 +7692,34 @@ } ] }, + "79": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "80": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "81": { + "name": "comment.block.cpp" + }, + "82": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "83": { + "name": "entity.name.operator.type.array.cpp" + }, "84": { "name": "entity.name.operator.custom-literal.cpp" }, @@ -8745,21 +7747,47 @@ "name": "comment.block.cpp" } ] + }, + "89": { + "name": "entity.name.operator.custom-literal.cpp" + }, + "90": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "91": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "92": { + "name": "comment.block.cpp" + }, + "93": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] } }, - "endCaptures": {}, - "name": "meta.function.definition.special.operator-overload.cpp", + "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", "patterns": [ { + "name": "meta.head.function.definition.special.operator-overload.cpp", "begin": "\\G ?", - "end": "(?:\\{|<%|\\?\\?<|(?=;))", - "beginCaptures": {}, + "end": "((?:\\{|<%|\\?\\?<|(?=;)))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.begin.bracket.curly.function.definition.special.operator-overload.cpp" } }, - "name": "meta.head.function.definition.special.operator-overload.cpp", "patterns": [ { "include": "#ever_present_context" @@ -8768,19 +7796,19 @@ "include": "#template_call_range" }, { - "begin": "\\(", - "end": "\\)", + "contentName": "meta.function.definition.parameters.special.operator-overload.cpp", + "begin": "(\\()", "beginCaptures": { - "0": { + "1": { "name": "punctuation.section.parameters.begin.bracket.round.special.operator-overload.cpp" } }, + "end": "(\\))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.parameters.end.bracket.round.special.operator-overload.cpp" } }, - "contentName": "meta.function.definition.parameters.special.operator-overload", "patterns": [ { "include": "#function_parameter_context" @@ -8799,15 +7827,14 @@ ] }, { + "name": "meta.body.function.definition.special.operator-overload.cpp", "begin": "(?<=\\{|<%|\\?\\?<)", - "end": "\\}|%>|\\?\\?>", - "beginCaptures": {}, + "end": "(\\}|%>|\\?\\?>)", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.end.bracket.curly.function.definition.special.operator-overload.cpp" } }, - "name": "meta.body.function.definition.special.operator-overload.cpp", "patterns": [ { "include": "#function_body_context" @@ -8815,11 +7842,9 @@ ] }, { - "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)", - "beginCaptures": {}, - "endCaptures": {}, "name": "meta.tail.function.definition.special.operator-overload.cpp", + "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)", "patterns": [ { "include": "$self" @@ -8831,292 +7856,22 @@ "operators": { "patterns": [ { - "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)", - "beginCaptures": { - "1": { - "name": "keyword.operator.functionlike.cpp keyword.operator.sizeof.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "6": { - "name": "punctuation.section.arguments.begin.bracket.round.operator.sizeof.cpp" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.section.arguments.end.bracket.round.operator.sizeof.cpp" - } - }, - "contentName": "meta.arguments.operator.sizeof", - "patterns": [ - { - "include": "#evaluation_context" - } - ] + "include": "#sizeof_operator" }, { - "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)", - "beginCaptures": { - "1": { - "name": "keyword.operator.functionlike.cpp keyword.operator.alignof.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "6": { - "name": "punctuation.section.arguments.begin.bracket.round.operator.alignof.cpp" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.section.arguments.end.bracket.round.operator.alignof.cpp" - } - }, - "contentName": "meta.arguments.operator.alignof", - "patterns": [ - { - "include": "#evaluation_context" - } - ] + "include": "#alignof_operator" }, { - "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)", - "beginCaptures": { - "1": { - "name": "keyword.operator.functionlike.cpp keyword.operator.alignas.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "6": { - "name": "punctuation.section.arguments.begin.bracket.round.operator.alignas.cpp" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.section.arguments.end.bracket.round.operator.alignas.cpp" - } - }, - "contentName": "meta.arguments.operator.alignas", - "patterns": [ - { - "include": "#evaluation_context" - } - ] + "include": "#alignas_operator" }, { - "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)", - "beginCaptures": { - "1": { - "name": "keyword.operator.functionlike.cpp keyword.operator.typeid.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "6": { - "name": "punctuation.section.arguments.begin.bracket.round.operator.typeid.cpp" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.section.arguments.end.bracket.round.operator.typeid.cpp" - } - }, - "contentName": "meta.arguments.operator.typeid", - "patterns": [ - { - "include": "#evaluation_context" - } - ] + "include": "#typeid_operator" }, { - "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)", - "beginCaptures": { - "1": { - "name": "keyword.operator.functionlike.cpp keyword.operator.noexcept.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "6": { - "name": "punctuation.section.arguments.begin.bracket.round.operator.noexcept.cpp" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.section.arguments.end.bracket.round.operator.noexcept.cpp" - } - }, - "contentName": "meta.arguments.operator.noexcept", - "patterns": [ - { - "include": "#evaluation_context" - } - ] + "include": "#noexcept_operator" }, { - "begin": "(\\bsizeof\\.\\.\\.)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)", - "beginCaptures": { - "1": { - "name": "keyword.operator.functionlike.cpp keyword.operator.sizeof.variadic.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "6": { - "name": "punctuation.section.arguments.begin.bracket.round.operator.sizeof.variadic.cpp" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.section.arguments.end.bracket.round.operator.sizeof.variadic.cpp" - } - }, - "contentName": "meta.arguments.operator.sizeof.variadic", - "patterns": [ - { - "include": "#evaluation_context" - } - ] + "include": "#sizeof_variadic_operator" }, { "match": "--", @@ -9165,1326 +7920,22 @@ "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)", - "captures": { - "1": { - "name": "storage.type.struct.parameter.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "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" - } - ] - } - } - } - ] - }, - "4": { - "name": "entity.name.type.struct.parameter.cpp" - }, - "5": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "6": { - "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" - } - ] - } - } - } - ] - }, - "7": { - "patterns": [ - { - "match": "\\*", - "name": "storage.modifier.pointer.cpp" - }, - { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - }, - "name": "invalid.illegal.reference-type.cpp" - }, - { - "match": "\\&", - "name": "storage.modifier.reference.cpp" - } - ] - }, - "8": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "9": { - "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" - } - ] - } - } - } - ] - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "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" - } - ] - } - } - } - ] - }, - "12": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "13": { - "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" - } - ] - } - } - } - ] - }, - "14": { - "name": "variable.other.object.declare.cpp" - }, - "15": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "16": { - "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" - } - ] - } - } - } - ] - }, - "17": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "18": { - "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" - } - ] - } - } - } - ] - }, - "19": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "20": { - "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" - } - ] - } - } - } - ] - } - } + "include": "#parameter_struct" }, { - "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)", - "captures": { - "1": { - "name": "storage.type.enum.parameter.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "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" - } - ] - } - } - } - ] - }, - "4": { - "name": "entity.name.type.enum.parameter.cpp" - }, - "5": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "6": { - "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" - } - ] - } - } - } - ] - }, - "7": { - "patterns": [ - { - "match": "\\*", - "name": "storage.modifier.pointer.cpp" - }, - { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - }, - "name": "invalid.illegal.reference-type.cpp" - }, - { - "match": "\\&", - "name": "storage.modifier.reference.cpp" - } - ] - }, - "8": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "9": { - "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" - } - ] - } - } - } - ] - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "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" - } - ] - } - } - } - ] - }, - "12": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "13": { - "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" - } - ] - } - } - } - ] - }, - "14": { - "name": "variable.other.object.declare.cpp" - }, - "15": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "16": { - "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" - } - ] - } - } - } - ] - }, - "17": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "18": { - "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" - } - ] - } - } - } - ] - }, - "19": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "20": { - "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" - } - ] - } - } - } - ] - } - } + "include": "#parameter_enum" }, { - "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)", - "captures": { - "1": { - "name": "storage.type.union.parameter.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "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" - } - ] - } - } - } - ] - }, - "4": { - "name": "entity.name.type.union.parameter.cpp" - }, - "5": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "6": { - "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" - } - ] - } - } - } - ] - }, - "7": { - "patterns": [ - { - "match": "\\*", - "name": "storage.modifier.pointer.cpp" - }, - { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - }, - "name": "invalid.illegal.reference-type.cpp" - }, - { - "match": "\\&", - "name": "storage.modifier.reference.cpp" - } - ] - }, - "8": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "9": { - "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" - } - ] - } - } - } - ] - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "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" - } - ] - } - } - } - ] - }, - "12": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "13": { - "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" - } - ] - } - } - } - ] - }, - "14": { - "name": "variable.other.object.declare.cpp" - }, - "15": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "16": { - "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" - } - ] - } - } - } - ] - }, - "17": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "18": { - "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" - } - ] - } - } - } - ] - }, - "19": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "20": { - "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" - } - ] - } - } - } - ] - } - } + "include": "#parameter_union" }, { - "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)", - "captures": { - "1": { - "name": "storage.type.class.parameter.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "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" - } - ] - } - } - } - ] - }, - "4": { - "name": "entity.name.type.class.parameter.cpp" - }, - "5": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "6": { - "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" - } - ] - } - } - } - ] - }, - "7": { - "patterns": [ - { - "match": "\\*", - "name": "storage.modifier.pointer.cpp" - }, - { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - }, - "name": "invalid.illegal.reference-type.cpp" - }, - { - "match": "\\&", - "name": "storage.modifier.reference.cpp" - } - ] - }, - "8": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "9": { - "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" - } - ] - } - } - } - ] - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "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" - } - ] - } - } - } - ] - }, - "12": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "13": { - "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" - } - ] - } - } - } - ] - }, - "14": { - "name": "variable.other.object.declare.cpp" - }, - "15": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "16": { - "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" - } - ] - } - } - } - ] - }, - "17": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "18": { - "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" - } - ] - } - } - } - ] - }, - "19": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "20": { - "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" - } - ] - } - } - } - ] - } - } + "include": "#parameter_class" } ] }, "parameter": { - "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\w)", - "end": "(?:(?=\\))|(,))", + "name": "meta.parameter.cpp", + "begin": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\w)", "beginCaptures": { "1": { "patterns": [ @@ -10512,12 +7963,12 @@ ] } }, + "end": "(?:(?=\\))|(,))", "endCaptures": { "1": { "name": "punctuation.separator.delimiter.comma.cpp" } }, - "name": "meta.parameter.cpp", "patterns": [ { "include": "#ever_present_context" @@ -10532,7 +7983,7 @@ "include": "#vararg_ellipses" }, { - "match": "((?:((?:(?:volatile)|(?:register)|(?:restrict)|(?:static)|(?:extern)|(?:const)))((?:(?:(?:(?>(?:\\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)))(?=,|\\)|=)", + "match": "((?:((?:volatile|register|restrict|static|extern|const))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))+)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=,|\\)|=)", "captures": { "1": { "patterns": [ @@ -10768,13 +8219,12 @@ "include": "#scope_resolution_parameter_inner_generated" }, { - "match": "(?:(?:struct)|(?:class)|(?:union)|(?:enum))", + "match": "(?:struct|class|union|enum)", "name": "storage.type.$0.cpp" }, { "begin": "(?<==)", "end": "(?:(?=\\))|(,))", - "beginCaptures": {}, "endCaptures": { "1": { "name": "punctuation.separator.delimiter.comma.cpp" @@ -10787,11 +8237,10 @@ ] }, { - "match": "\\=", - "name": "keyword.operator.assignment.cpp" + "include": "#assignment_operator" }, { - "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)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\)|,|\\[|=|\\n)", + "match": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\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}))*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\)|,|\\[|=|\\n)", "captures": { "1": { "patterns": [ @@ -10852,19 +8301,19 @@ "include": "#attributes_context" }, { - "begin": "\\[", - "end": "\\]", + "name": "meta.bracket.square.array.cpp", + "begin": "(\\[)", "beginCaptures": { - "0": { + "1": { "name": "punctuation.definition.begin.bracket.square.array.type.cpp" } }, + "end": "(\\])", "endCaptures": { - "0": { + "1": { "name": "punctuation.definition.end.bracket.square.array.type.cpp" } }, - "name": "meta.bracket.square.array.cpp", "patterns": [ { "include": "#evaluation_context" @@ -10879,7 +8328,7 @@ "include": "#template_call_range" }, { - "match": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*))", + "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*)", "captures": { "0": { "patterns": [ @@ -10888,7 +8337,7 @@ "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ @@ -10979,7 +8428,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": "(class)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:\\[((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\]((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?=,|\\)|\\n)", "captures": { "1": { "name": "storage.type.class.parameter.cpp" @@ -10992,77 +8441,59 @@ ] }, "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "4": { + "6": { "name": "entity.name.type.class.parameter.cpp" }, - "5": { + "7": { "patterns": [ { "include": "#inline_comment" } ] }, - "6": { + "8": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "9": { + "name": "comment.block.cpp" + }, + "10": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "7": { + "11": { "patterns": [ { "match": "\\*", "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ @@ -11098,74 +8529,6 @@ } ] }, - "8": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "9": { - "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" - } - ] - } - } - } - ] - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "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" - } - ] - } - } - } - ] - }, "12": { "patterns": [ { @@ -11174,141 +8537,155 @@ ] }, "13": { - "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" - } - ] - } - } - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "14": { - "name": "variable.other.object.declare.cpp" + "name": "comment.block.cpp" }, "15": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "16": { "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" - } - ] - } - } + "include": "#inline_comment" } ] }, "17": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "18": { - "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" - } - ] - } - } - } - ] + "name": "comment.block.cpp" }, "19": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "20": { "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" - } - ] - } - } + "include": "#inline_comment" + } + ] + }, + "21": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "22": { + "name": "comment.block.cpp" + }, + "23": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "24": { + "name": "variable.other.object.declare.cpp" + }, + "25": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "26": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "27": { + "name": "comment.block.cpp" + }, + "28": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] } } }, "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": "(enum)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:\\[((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\]((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?=,|\\)|\\n)", "captures": { "1": { "name": "storage.type.enum.parameter.cpp" @@ -11321,77 +8698,59 @@ ] }, "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "4": { + "6": { "name": "entity.name.type.enum.parameter.cpp" }, - "5": { + "7": { "patterns": [ { "include": "#inline_comment" } ] }, - "6": { + "8": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "9": { + "name": "comment.block.cpp" + }, + "10": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "7": { + "11": { "patterns": [ { "match": "\\*", "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ @@ -11427,74 +8786,6 @@ } ] }, - "8": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "9": { - "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" - } - ] - } - } - } - ] - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "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" - } - ] - } - } - } - ] - }, "12": { "patterns": [ { @@ -11503,142 +8794,156 @@ ] }, "13": { - "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" - } - ] - } - } - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "14": { - "name": "variable.other.object.declare.cpp" + "name": "comment.block.cpp" }, "15": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "16": { "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" - } - ] - } - } + "include": "#inline_comment" } ] }, "17": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "18": { - "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" - } - ] - } - } - } - ] + "name": "comment.block.cpp" }, "19": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "20": { "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" - } - ] - } - } + "include": "#inline_comment" + } + ] + }, + "21": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "22": { + "name": "comment.block.cpp" + }, + "23": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "24": { + "name": "variable.other.object.declare.cpp" + }, + "25": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "26": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "27": { + "name": "comment.block.cpp" + }, + "28": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] } } }, "parameter_or_maybe_value": { - "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\w)", - "end": "(?:(?=\\))|(,))", + "name": "meta.parameter.cpp", + "begin": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\w)", "beginCaptures": { "1": { "patterns": [ @@ -11666,12 +8971,12 @@ ] } }, + "end": "(?:(?=\\))|(,))", "endCaptures": { "1": { "name": "punctuation.separator.delimiter.comma.cpp" } }, - "name": "meta.parameter.cpp", "patterns": [ { "include": "#ever_present_context" @@ -11695,7 +9000,7 @@ "include": "#vararg_ellipses" }, { - "match": "((?:((?:(?:volatile)|(?:register)|(?:restrict)|(?:static)|(?:extern)|(?:const)))((?:(?:(?:(?>(?:\\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)))(?=,|\\)|=)", + "match": "((?:((?:volatile|register|restrict|static|extern|const))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))+)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=,|\\)|=)", "captures": { "1": { "patterns": [ @@ -11934,13 +9239,12 @@ "include": "#scope_resolution_parameter_inner_generated" }, { - "match": "(?:(?:struct)|(?:class)|(?:union)|(?:enum))", + "match": "(?:struct|class|union|enum)", "name": "storage.type.$0.cpp" }, { "begin": "(?<==)", "end": "(?:(?=\\))|(,))", - "beginCaptures": {}, "endCaptures": { "1": { "name": "punctuation.separator.delimiter.comma.cpp" @@ -11953,7 +9257,7 @@ ] }, { - "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)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=(?:\\)|,|\\[|=|\\/\\/|(?:(?:\\n)|$)))", + "match": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\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}))*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=(?:\\)|,|\\[|=|\\/\\/|(?:\\n|$)))", "captures": { "1": { "patterns": [ @@ -12014,19 +9318,19 @@ "include": "#attributes_context" }, { - "begin": "\\[", - "end": "\\]", + "name": "meta.bracket.square.array.cpp", + "begin": "(\\[)", "beginCaptures": { - "0": { + "1": { "name": "punctuation.definition.begin.bracket.square.array.type.cpp" } }, + "end": "(\\])", "endCaptures": { - "0": { + "1": { "name": "punctuation.definition.end.bracket.square.array.type.cpp" } }, - "name": "meta.bracket.square.array.cpp", "patterns": [ { "include": "#evaluation_context" @@ -12041,7 +9345,7 @@ "include": "#template_call_range" }, { - "match": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*))", + "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*)", "captures": { "0": { "patterns": [ @@ -12050,7 +9354,7 @@ "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ @@ -12144,7 +9448,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": "(struct)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:\\[((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\]((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?=,|\\)|\\n)", "captures": { "1": { "name": "storage.type.struct.parameter.cpp" @@ -12157,77 +9461,59 @@ ] }, "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "4": { + "6": { "name": "entity.name.type.struct.parameter.cpp" }, - "5": { + "7": { "patterns": [ { "include": "#inline_comment" } ] }, - "6": { + "8": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "9": { + "name": "comment.block.cpp" + }, + "10": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "7": { + "11": { "patterns": [ { "match": "\\*", "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ @@ -12263,74 +9549,6 @@ } ] }, - "8": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "9": { - "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" - } - ] - } - } - } - ] - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "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" - } - ] - } - } - } - ] - }, "12": { "patterns": [ { @@ -12339,141 +9557,155 @@ ] }, "13": { - "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" - } - ] - } - } - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "14": { - "name": "variable.other.object.declare.cpp" + "name": "comment.block.cpp" }, "15": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "16": { "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" - } - ] - } - } + "include": "#inline_comment" } ] }, "17": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "18": { - "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" - } - ] - } - } - } - ] + "name": "comment.block.cpp" }, "19": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "20": { "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" - } - ] - } - } + "include": "#inline_comment" + } + ] + }, + "21": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "22": { + "name": "comment.block.cpp" + }, + "23": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "24": { + "name": "variable.other.object.declare.cpp" + }, + "25": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "26": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "27": { + "name": "comment.block.cpp" + }, + "28": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] } } }, "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": "(union)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:\\[((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\]((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?=,|\\)|\\n)", "captures": { "1": { "name": "storage.type.union.parameter.cpp" @@ -12486,77 +9718,59 @@ ] }, "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "4": { + "6": { "name": "entity.name.type.union.parameter.cpp" }, - "5": { + "7": { "patterns": [ { "include": "#inline_comment" } ] }, - "6": { + "8": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "9": { + "name": "comment.block.cpp" + }, + "10": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "7": { + "11": { "patterns": [ { "match": "\\*", "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ @@ -12592,74 +9806,6 @@ } ] }, - "8": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "9": { - "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" - } - ] - } - } - } - ] - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "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" - } - ] - } - } - } - ] - }, "12": { "patterns": [ { @@ -12668,153 +9814,167 @@ ] }, "13": { - "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" - } - ] - } - } - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "14": { - "name": "variable.other.object.declare.cpp" + "name": "comment.block.cpp" }, "15": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "16": { "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" - } - ] - } - } + "include": "#inline_comment" } ] }, "17": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "18": { - "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" - } - ] - } - } - } - ] + "name": "comment.block.cpp" }, "19": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "20": { "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" - } - ] - } - } + "include": "#inline_comment" + } + ] + }, + "21": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "22": { + "name": "comment.block.cpp" + }, + "23": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "24": { + "name": "variable.other.object.declare.cpp" + }, + "25": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "26": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "27": { + "name": "comment.block.cpp" + }, + "28": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] } } }, "parentheses": { - "begin": "\\(", - "end": "\\)", + "name": "meta.parens.cpp", + "begin": "(\\()", "beginCaptures": { - "0": { + "1": { "name": "punctuation.section.parens.begin.bracket.round.cpp" } }, + "end": "(\\))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.parens.end.bracket.round.cpp" } }, - "name": "meta.parens.cpp", "patterns": [ { "include": "#over_qualified_types" @@ -12829,54 +9989,8 @@ ] }, "posix_reserved_types": { - "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\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" - } - ] - } - } - } - ] - }, - "3": { - "name": "support.type.posix-reserved.cpp support.type.built-in.posix-reserved.cpp" - } - } - }, - "pragma": { - "begin": "^((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?pragma\\b", - "end": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*pragma\\b)", + "beginCaptures": { + "1": { + "name": "keyword.control.directive.pragma.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { "name": "punctuation.definition.directive.cpp" } }, - "endCaptures": {}, - "name": "meta.preprocessor.pragma.cpp", + "end": "(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?pragma(?:\\s)+mark)(?:\\s)+(.*)", + "match": "((?:^)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*pragma\\s+mark)\\s+(.*)", "captures": { "1": { "name": "keyword.control.directive.pragma.pragma-mark.cpp" @@ -12941,36 +10091,27 @@ ] }, "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "4": { + "6": { "name": "punctuation.definition.directive.cpp" }, - "5": { + "7": { "name": "entity.name.tag.pragma-mark.cpp" } }, @@ -13004,10 +10145,10 @@ "include": "#language_constants" }, { - "include": "#d9bc4796b0b_string_context_c" + "include": "#string_context_c" }, { - "include": "#d9bc4796b0b_preprocessor_number_literal" + "include": "#preprocessor_number_literal" }, { "include": "#operators" @@ -13025,7 +10166,6 @@ }, "preprocessor_conditional_defined": { "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?((?:(?:ifndef|ifdef)|if))", - "end": "^(?!\\s*+#\\s*(?:else|endif))", + "begin": "((?:^)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*((?:(?:ifndef|ifdef)|if)))", "beginCaptures": { - "0": { - "name": "keyword.control.directive.conditional.$6.cpp" + "1": { + "name": "keyword.control.directive.conditional.$7.cpp" }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "punctuation.definition.directive.cpp" + } + }, + "end": "(?:^)(?!\\s*+#\\s*(?:else|endif))", + "patterns": [ + { + "name": "meta.preprocessor.conditional.cpp", + "begin": "\\G(?<=ifndef|ifdef|if)", + "end": "(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?((?(?:\\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" + "begin": "(?=.)", + "end": "$", + "patterns": [ + { + "match": "(\\G0[xX])([0-9a-fA-F](?:[0-9a-fA-F]|((?<=[0-9a-fA-F])'(?=[0-9a-fA-F])))*)?((?:(?<=[0-9a-fA-F])\\.|\\.(?=[0-9a-fA-F])))([0-9a-fA-F](?:[0-9a-fA-F]|((?<=[0-9a-fA-F])'(?=[0-9a-fA-F])))*)?((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "3": { + "5": { "name": "storage.type.primitive.cpp storage.type.built-in.primitive.cpp" } } }, "pthread_types": { - "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "3": { + "5": { "name": "support.type.posix-reserved.pthread.cpp support.type.built-in.posix-reserved.pthread.cpp" } } }, "qualified_type": { - "match": "\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\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>?)+>)(?:(?:\\s)+)?)?(?![\\w<:.])", + "match": "\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<26>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<26>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|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<26>?)+)>)\\s*)?(?![\\w<:.])", "captures": { "0": { + "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?", - "beginCaptures": { - "0": { - "name": "punctuation.section.angle-brackets.begin.template.call.cpp" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.section.angle-brackets.end.template.call.cpp" - } - }, - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_context" - } - ] + "include": "#template_call_range" }, { "match": "(?:[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}))*", @@ -13354,98 +10664,59 @@ ] }, "3": { - "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" - } - ] - } - } - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "4": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp" }, "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "6": { "patterns": [ { - "include": "#scope_resolution_inner_generated" + "include": "#inline_comment" } ] }, "7": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "8": { + "name": "comment.block.cpp" + }, + "9": { "patterns": [ { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "10": { - "name": "entity.name.scope-resolution.cpp" - }, "11": { + "patterns": [ + { + "include": "#scope_resolution_inner_generated" + } + ] + }, + "12": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + }, + "14": { "name": "meta.template.call.cpp", "patterns": [ { @@ -13453,45 +10724,8 @@ } ] }, - "13": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" - }, - "14": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "15": { - "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" - } - ] - } - } - } - ] - }, "16": { - "name": "entity.name.type.cpp" + "name": "entity.name.scope-resolution.cpp" }, "17": { "name": "meta.template.call.cpp", @@ -13500,12 +10734,50 @@ "include": "#template_call_range" } ] + }, + "19": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + }, + "20": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "21": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "22": { + "name": "comment.block.cpp" + }, + "23": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "24": { + "name": "entity.name.type.cpp" + }, + "25": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] } - }, - "name": "meta.qualified_type.cpp" + } }, "qualifiers_and_specifiers_post_parameters": { - "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "3": { - "name": "storage.modifier.specifier.functional.post-parameters.$3.cpp" + "5": { + "name": "storage.modifier.specifier.functional.post-parameters.$5.cpp" } } }, "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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:(?:\\s)+)?)?::)*)\\s*+", + "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -13559,28 +10822,18 @@ "1": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, - "2": { + "3": { + "name": "meta.template.call.cpp", "patterns": [ { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } + "include": "#template_call_range" } ] } } }, "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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:(?:\\s)+)?)?::)*)\\s*+", + "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -13592,28 +10845,18 @@ "1": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.call.cpp" }, - "2": { + "3": { + "name": "meta.template.call.cpp", "patterns": [ { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } + "include": "#template_call_range" } ] } } }, "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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:(?:\\s)+)?)?(::)", + "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", "captures": { "1": { "patterns": [ @@ -13625,29 +10868,7 @@ "2": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.call.cpp" }, - "3": { - "patterns": [ - { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } - } - ] - }, - "4": {}, - "5": { - "name": "entity.name.scope-resolution.function.call.cpp" - }, - "6": { + "4": { "name": "meta.template.call.cpp", "patterns": [ { @@ -13655,14 +10876,24 @@ } ] }, - "7": {}, - "8": { + "6": { + "name": "entity.name.scope-resolution.function.call.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.call.cpp" } } }, "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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:(?:\\s)+)?)?::)*)\\s*+", + "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -13674,28 +10905,18 @@ "1": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.cpp" }, - "2": { + "3": { + "name": "meta.template.call.cpp", "patterns": [ { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } + "include": "#template_call_range" } ] } } }, "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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:(?:\\s)+)?)?(::)", + "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", "captures": { "1": { "patterns": [ @@ -13707,29 +10928,7 @@ "2": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.cpp" }, - "3": { - "patterns": [ - { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } - } - ] - }, - "4": {}, - "5": { - "name": "entity.name.scope-resolution.function.definition.cpp" - }, - "6": { + "4": { "name": "meta.template.call.cpp", "patterns": [ { @@ -13737,14 +10936,24 @@ } ] }, - "7": {}, - "8": { + "6": { + "name": "entity.name.scope-resolution.function.definition.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.cpp" } } }, "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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:(?:\\s)+)?)?::)*)\\s*+", + "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -13756,28 +10965,18 @@ "1": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.operator-overload.cpp" }, - "2": { + "3": { + "name": "meta.template.call.cpp", "patterns": [ { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } + "include": "#template_call_range" } ] } } }, "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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:(?:\\s)+)?)?(::)", + "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", "captures": { "1": { "patterns": [ @@ -13789,29 +10988,7 @@ "2": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.operator-overload.cpp" }, - "3": { - "patterns": [ - { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } - } - ] - }, - "4": {}, - "5": { - "name": "entity.name.scope-resolution.function.definition.operator-overload.cpp" - }, - "6": { + "4": { "name": "meta.template.call.cpp", "patterns": [ { @@ -13819,14 +10996,24 @@ } ] }, - "7": {}, - "8": { + "6": { + "name": "entity.name.scope-resolution.function.definition.operator-overload.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.operator-overload.cpp" } } }, "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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:(?:\\s)+)?)?(::)", + "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", "captures": { "1": { "patterns": [ @@ -13838,29 +11025,7 @@ "2": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, - "3": { - "patterns": [ - { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } - } - ] - }, - "4": {}, - "5": { - "name": "entity.name.scope-resolution.cpp" - }, - "6": { + "4": { "name": "meta.template.call.cpp", "patterns": [ { @@ -13868,14 +11033,24 @@ } ] }, - "7": {}, - "8": { + "6": { + "name": "entity.name.scope-resolution.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" } } }, "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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:(?:\\s)+)?)?::)*)\\s*+", + "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -13887,28 +11062,18 @@ "1": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.alias.cpp" }, - "2": { + "3": { + "name": "meta.template.call.cpp", "patterns": [ { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } + "include": "#template_call_range" } ] } } }, "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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:(?:\\s)+)?)?(::)", + "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", "captures": { "1": { "patterns": [ @@ -13920,29 +11085,7 @@ "2": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.alias.cpp" }, - "3": { - "patterns": [ - { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } - } - ] - }, - "4": {}, - "5": { - "name": "entity.name.scope-resolution.namespace.alias.cpp" - }, - "6": { + "4": { "name": "meta.template.call.cpp", "patterns": [ { @@ -13950,14 +11093,24 @@ } ] }, - "7": {}, - "8": { + "6": { + "name": "entity.name.scope-resolution.namespace.alias.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.alias.cpp" } } }, "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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:(?:\\s)+)?)?::)*)\\s*+", + "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -13969,28 +11122,18 @@ "1": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.block.cpp" }, - "2": { + "3": { + "name": "meta.template.call.cpp", "patterns": [ { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } + "include": "#template_call_range" } ] } } }, "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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:(?:\\s)+)?)?(::)", + "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", "captures": { "1": { "patterns": [ @@ -14002,29 +11145,7 @@ "2": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.block.cpp" }, - "3": { - "patterns": [ - { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } - } - ] - }, - "4": {}, - "5": { - "name": "entity.name.scope-resolution.namespace.block.cpp" - }, - "6": { + "4": { "name": "meta.template.call.cpp", "patterns": [ { @@ -14032,14 +11153,24 @@ } ] }, - "7": {}, - "8": { + "6": { + "name": "entity.name.scope-resolution.namespace.block.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.block.cpp" } } }, "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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:(?:\\s)+)?)?::)*)\\s*+", + "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -14051,28 +11182,18 @@ "1": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.using.cpp" }, - "2": { + "3": { + "name": "meta.template.call.cpp", "patterns": [ { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } + "include": "#template_call_range" } ] } } }, "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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:(?:\\s)+)?)?(::)", + "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", "captures": { "1": { "patterns": [ @@ -14084,29 +11205,7 @@ "2": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.using.cpp" }, - "3": { - "patterns": [ - { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } - } - ] - }, - "4": {}, - "5": { - "name": "entity.name.scope-resolution.namespace.using.cpp" - }, - "6": { + "4": { "name": "meta.template.call.cpp", "patterns": [ { @@ -14114,14 +11213,24 @@ } ] }, - "7": {}, - "8": { + "6": { + "name": "entity.name.scope-resolution.namespace.using.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.using.cpp" } } }, "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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:(?:\\s)+)?)?::)*)\\s*+", + "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -14133,28 +11242,18 @@ "1": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.parameter.cpp" }, - "2": { + "3": { + "name": "meta.template.call.cpp", "patterns": [ { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } + "include": "#template_call_range" } ] } } }, "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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:(?:\\s)+)?)?(::)", + "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", "captures": { "1": { "patterns": [ @@ -14166,29 +11265,7 @@ "2": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.parameter.cpp" }, - "3": { - "patterns": [ - { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } - } - ] - }, - "4": {}, - "5": { - "name": "entity.name.scope-resolution.parameter.cpp" - }, - "6": { + "4": { "name": "meta.template.call.cpp", "patterns": [ { @@ -14196,14 +11273,24 @@ } ] }, - "7": {}, - "8": { + "6": { + "name": "entity.name.scope-resolution.parameter.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.parameter.cpp" } } }, "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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:(?:\\s)+)?)?::)*)\\s*+", + "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -14215,28 +11302,18 @@ "1": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp" }, - "2": { + "3": { + "name": "meta.template.call.cpp", "patterns": [ { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } + "include": "#template_call_range" } ] } } }, "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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:(?:\\s)+)?)?(::)", + "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", "captures": { "1": { "patterns": [ @@ -14248,29 +11325,7 @@ "2": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp" }, - "3": { - "patterns": [ - { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } - } - ] - }, - "4": {}, - "5": { - "name": "entity.name.scope-resolution.template.call.cpp" - }, - "6": { + "4": { "name": "meta.template.call.cpp", "patterns": [ { @@ -14278,14 +11333,24 @@ } ] }, - "7": {}, - "8": { + "6": { + "name": "entity.name.scope-resolution.template.call.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp" } } }, "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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:(?:\\s)+)?)?::)*)\\s*+", + "match": "(::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -14297,28 +11362,18 @@ "1": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.definition.cpp" }, - "2": { + "3": { + "name": "meta.template.call.cpp", "patterns": [ { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } + "include": "#template_call_range" } ] } } }, "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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:(?:\\s)+)?)?(::)", + "match": "((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", "captures": { "1": { "patterns": [ @@ -14330,29 +11385,7 @@ "2": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.definition.cpp" }, - "3": { - "patterns": [ - { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } - } - ] - }, - "4": {}, - "5": { - "name": "entity.name.scope-resolution.template.definition.cpp" - }, - "6": { + "4": { "name": "meta.template.call.cpp", "patterns": [ { @@ -14360,8 +11393,18 @@ } ] }, - "7": {}, - "8": { + "6": { + "name": "entity.name.scope-resolution.template.definition.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.definition.cpp" } } @@ -14371,13 +11414,13 @@ "name": "punctuation.terminator.statement.cpp" }, "simple_type": { - "match": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<19>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<19>?)+>)(?:(?:\\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<19>?)+>)(?:(?:\\s)+)?)?(?![\\w<:.]))(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?", + "match": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|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<27>?)+)>)\\s*)?(?![\\w<:.]))(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?", "captures": { "1": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?", - "beginCaptures": { - "0": { - "name": "punctuation.section.angle-brackets.begin.template.call.cpp" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.section.angle-brackets.end.template.call.cpp" - } - }, - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_context" - } - ] + "include": "#template_call_range" }, { "match": "(?:[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}))*", @@ -14445,99 +11471,59 @@ ] }, "4": { - "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" - } - ] - } - } - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "5": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp" }, "6": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "7": { "patterns": [ { - "include": "#scope_resolution_inner_generated" + "include": "#inline_comment" } ] }, "8": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "9": { + "name": "comment.block.cpp" + }, + "10": { "patterns": [ { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "10": {}, - "11": { - "name": "entity.name.scope-resolution.cpp" - }, "12": { + "patterns": [ + { + "include": "#scope_resolution_inner_generated" + } + ] + }, + "13": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + }, + "15": { "name": "meta.template.call.cpp", "patterns": [ { @@ -14545,46 +11531,8 @@ } ] }, - "13": {}, - "14": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" - }, - "15": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "16": { - "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" - } - ] - } - } - } - ] - }, "17": { - "name": "entity.name.type.cpp" + "name": "entity.name.scope-resolution.cpp" }, "18": { "name": "meta.template.call.cpp", @@ -14594,15 +11542,53 @@ } ] }, - "19": {}, "20": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + }, + "21": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "22": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "23": { + "name": "comment.block.cpp" + }, + "24": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "25": { + "name": "entity.name.type.cpp" + }, + "26": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "28": { "patterns": [ { "match": "\\*", "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ @@ -14638,78 +11624,60 @@ } ] }, - "21": { + "29": { "patterns": [ { "include": "#inline_comment" } ] }, - "22": { + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "23": { + "33": { "patterns": [ { "include": "#inline_comment" } ] }, - "24": { + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] } } }, "single_line_macro": { - "match": "^((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))#define.*(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))#define.*(?(?:\\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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] } } }, "sizeof_operator": { - "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)", + "contentName": "meta.arguments.operator.sizeof.cpp", + "begin": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", "beginCaptures": { "1": { "name": "keyword.operator.functionlike.cpp keyword.operator.sizeof.cpp" @@ -14793,12 +11752,12 @@ "name": "punctuation.section.arguments.begin.bracket.round.operator.sizeof.cpp" } }, + "end": "(\\))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.arguments.end.bracket.round.operator.sizeof.cpp" } }, - "contentName": "meta.arguments.operator.sizeof", "patterns": [ { "include": "#evaluation_context" @@ -14806,8 +11765,8 @@ ] }, "sizeof_variadic_operator": { - "begin": "(\\bsizeof\\.\\.\\.)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)", + "contentName": "meta.arguments.operator.sizeof.variadic.cpp", + "begin": "(\\bsizeof\\.\\.\\.)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", "beginCaptures": { "1": { "name": "keyword.operator.functionlike.cpp keyword.operator.sizeof.variadic.cpp" @@ -14841,12 +11800,12 @@ "name": "punctuation.section.arguments.begin.bracket.round.operator.sizeof.variadic.cpp" } }, + "end": "(\\))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.arguments.end.bracket.round.operator.sizeof.variadic.cpp" } }, - "contentName": "meta.arguments.operator.sizeof.variadic", "patterns": [ { "include": "#evaluation_context" @@ -14854,20 +11813,20 @@ ] }, "square_brackets": { - "name": "meta.bracket.square.access", + "name": "meta.bracket.square.access.cpp", "begin": "([a-zA-Z_][a-zA-Z_0-9]*|(?<=[\\]\\)]))?(\\[)(?!\\])", "beginCaptures": { "1": { - "name": "variable.other.object" + "name": "variable.other.object.cpp" }, "2": { - "name": "punctuation.definition.begin.bracket.square" + "name": "punctuation.definition.begin.bracket.square.cpp" } }, "end": "\\]", "endCaptures": { "0": { - "name": "punctuation.definition.end.bracket.square" + "name": "punctuation.definition.end.bracket.square.cpp" } }, "patterns": [ @@ -14879,918 +11838,21 @@ "standard_declares": { "patterns": [ { - "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\S)(?![:{])", - "captures": { - "1": { - "name": "storage.type.struct.declare.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "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" - } - ] - } - } - } - ] - }, - "4": { - "name": "entity.name.type.struct.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*", - "name": "storage.modifier.pointer.cpp" - }, - { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - }, - "name": "invalid.illegal.reference-type.cpp" - }, - { - "match": "\\&", - "name": "storage.modifier.reference.cpp" - } - ] - }, - "6": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "7": { - "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" - } - ] - } - } - } - ] - }, - "8": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "9": { - "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" - } - ] - } - } - } - ] - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "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" - } - ] - } - } - } - ] - }, - "12": { - "name": "variable.other.object.declare.cpp" - }, - "13": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "14": { - "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" - } - ] - } - } - } - ] - } - } + "include": "#struct_declare" }, { - "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\S)(?![:{])", - "captures": { - "1": { - "name": "storage.type.union.declare.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "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" - } - ] - } - } - } - ] - }, - "4": { - "name": "entity.name.type.union.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*", - "name": "storage.modifier.pointer.cpp" - }, - { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - }, - "name": "invalid.illegal.reference-type.cpp" - }, - { - "match": "\\&", - "name": "storage.modifier.reference.cpp" - } - ] - }, - "6": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "7": { - "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" - } - ] - } - } - } - ] - }, - "8": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "9": { - "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" - } - ] - } - } - } - ] - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "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" - } - ] - } - } - } - ] - }, - "12": { - "name": "variable.other.object.declare.cpp" - }, - "13": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "14": { - "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" - } - ] - } - } - } - ] - } - } + "include": "#union_declare" }, { - "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\S)(?![:{])", - "captures": { - "1": { - "name": "storage.type.enum.declare.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "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" - } - ] - } - } - } - ] - }, - "4": { - "name": "entity.name.type.enum.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*", - "name": "storage.modifier.pointer.cpp" - }, - { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - }, - "name": "invalid.illegal.reference-type.cpp" - }, - { - "match": "\\&", - "name": "storage.modifier.reference.cpp" - } - ] - }, - "6": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "7": { - "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" - } - ] - } - } - } - ] - }, - "8": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "9": { - "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" - } - ] - } - } - } - ] - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "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" - } - ] - } - } - } - ] - }, - "12": { - "name": "variable.other.object.declare.cpp" - }, - "13": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "14": { - "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" - } - ] - } - } - } - ] - } - } + "include": "#enum_declare" }, { - "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\S)(?![:{])", - "captures": { - "1": { - "name": "storage.type.class.declare.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "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" - } - ] - } - } - } - ] - }, - "4": { - "name": "entity.name.type.class.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*", - "name": "storage.modifier.pointer.cpp" - }, - { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", - "captures": { - "1": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "2": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "3": { - "name": "comment.block.cpp" - }, - "4": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - } - }, - "name": "invalid.illegal.reference-type.cpp" - }, - { - "match": "\\&", - "name": "storage.modifier.reference.cpp" - } - ] - }, - "6": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "7": { - "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" - } - ] - } - } - } - ] - }, - "8": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "9": { - "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" - } - ] - } - } - } - ] - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "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" - } - ] - } - } - } - ] - }, - "12": { - "name": "variable.other.object.declare.cpp" - }, - "13": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "14": { - "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" - } - ] - } - } - } - ] - } - } + "include": "#class_declare" } ] }, "static_assert": { - "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)", + "begin": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", "beginCaptures": { "1": { "patterns": [ @@ -15849,22 +11911,22 @@ "name": "punctuation.section.arguments.begin.bracket.round.static_assert.cpp" } }, + "end": "(\\))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.arguments.end.bracket.round.static_assert.cpp" } }, "patterns": [ { - "begin": "(,)(?:(?:\\s)+)?(?=(?:L|u8|u|U(?:(?:\\s)+)?\\\")?)", - "end": "(?=\\))", + "name": "meta.static_assert.message.cpp", + "begin": "(,)\\s*(?=(?:L|u8|u|U\\s*\\\")?)", "beginCaptures": { "1": { "name": "punctuation.separator.delimiter.comma.cpp" } }, - "endCaptures": {}, - "name": "meta.static_assert.message.cpp", + "end": "(?=\\))", "patterns": [ { "include": "#string_context" @@ -15876,47 +11938,8 @@ } ] }, - "std_space": { - "match": "(?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))", - "captures": { - "0": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "1": { - "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" - } - ] - } - } - } - ] - } - } - }, "storage_specifiers": { - "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "3": { - "name": "storage.modifier.specifier.$3.cpp" + "5": { + "name": "storage.modifier.specifier.$5.cpp" } } }, @@ -15985,22 +11999,22 @@ "string_context": { "patterns": [ { - "begin": "((?:u|u8|U|L)?)\"", - "end": "\"", + "name": "string.quoted.double.cpp", + "begin": "(((?:u|u8|U|L)?)\")", "beginCaptures": { - "0": { + "1": { "name": "punctuation.definition.string.begin.cpp" }, - "1": { + "2": { "name": "meta.encoding.cpp" } }, + "end": "(\")", "endCaptures": { - "0": { + "1": { "name": "punctuation.definition.string.end.cpp" } }, - "name": "string.quoted.double.cpp", "patterns": [ { "match": "(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8})", @@ -16015,7 +12029,7 @@ "name": "constant.character.escape.cpp" }, { - "match": "\\\\x[0-9a-fA-F]{2}", + "match": "\\\\x[0-9a-fA-F]{2,2}", "name": "constant.character.escape.cpp" }, { @@ -16024,22 +12038,22 @@ ] }, { - "begin": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(DLLEXPORT)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(final)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:))?", - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))", + "name": "meta.block.struct.cpp", + "begin": "((((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(DLLEXPORT)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(final)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)((?>[^{]*)))?))", "beginCaptures": { - "0": { + "1": { "name": "meta.head.struct.cpp" }, + "3": { + "name": "storage.type.$3.cpp" + }, + "4": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "5": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "6": { + "name": "comment.block.cpp" + }, + "7": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "8": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "9": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "10": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "11": { + "name": "comment.block.cpp" + }, + "12": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "13": { + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + "14": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "15": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "16": { + "name": "comment.block.cpp" + }, + "17": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "18": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "21": { + "name": "comment.block.cpp" + }, + "22": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "23": { + "name": "entity.name.type.$3.cpp" + }, + "24": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "26": { + "name": "comment.block.cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "28": { + "name": "storage.type.modifier.final.cpp" + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "37": { + "name": "punctuation.separator.colon.inheritance.cpp" + }, + "38": { + "patterns": [ + { + "include": "#inheritance_context" + } + ] + } + }, + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))", + "endCaptures": { "1": { - "name": "storage.type.$1.cpp" + "name": "punctuation.terminator.statement.cpp" + }, + "2": { + "name": "punctuation.terminator.statement.cpp" + } + }, + "patterns": [ + { + "name": "meta.head.struct.cpp", + "begin": "\\G ?", + "end": "((?:\\{|<%|\\?\\?<|(?=;)))", + "endCaptures": { + "1": { + "name": "punctuation.section.block.begin.bracket.curly.struct.cpp" + } + }, + "patterns": [ + { + "include": "#ever_present_context" + }, + { + "include": "#inheritance_context" + }, + { + "include": "#template_call_range" + } + ] + }, + { + "name": "meta.body.struct.cpp", + "begin": "(?<=\\{|<%|\\?\\?<)", + "end": "(\\}|%>|\\?\\?>)", + "endCaptures": { + "1": { + "name": "punctuation.section.block.end.bracket.curly.struct.cpp" + } + }, + "patterns": [ + { + "include": "#function_pointer" + }, + { + "include": "#static_assert" + }, + { + "include": "#constructor_inline" + }, + { + "include": "#destructor_inline" + }, + { + "include": "$self" + } + ] + }, + { + "name": "meta.tail.struct.cpp", + "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)", + "patterns": [ + { + "include": "$self" + } + ] + } + ] + }, + "struct_declare": { + "match": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\S)(?![:{])", + "captures": { + "1": { + "name": "storage.type.struct.declare.cpp" }, "2": { "patterns": [ @@ -16173,313 +12481,16 @@ ] }, "6": { - "patterns": [ - { - "include": "#attributes_context" - }, - { - "include": "#number_literal" - } - ] - }, - "7": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "8": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "9": { - "name": "comment.block.cpp" - }, - "10": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "11": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "12": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "13": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "14": { - "name": "comment.block.cpp" - }, - "15": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "16": { - "patterns": [ - { - "include": "#attributes_context" - }, - { - "include": "#number_literal" - } - ] - }, - "17": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "18": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "19": { - "name": "comment.block.cpp" - }, - "20": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "21": { - "name": "entity.name.type.$1.cpp" - }, - "22": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "23": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "24": { - "name": "comment.block.cpp" - }, - "25": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "26": { - "name": "storage.type.modifier.final.cpp" - }, - "27": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "28": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "29": { - "name": "comment.block.cpp" - }, - "30": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "31": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "32": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "33": { - "name": "comment.block.cpp" - }, - "34": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "35": { - "name": "punctuation.separator.colon.inheritance.cpp" - } - }, - "endCaptures": { - "1": { - "name": "punctuation.terminator.statement.cpp" - }, - "2": { - "name": "punctuation.terminator.statement.cpp" - } - }, - "name": "meta.block.struct.cpp", - "patterns": [ - { - "begin": "\\G ?", - "end": "(?:\\{|<%|\\?\\?<|(?=;))", - "beginCaptures": {}, - "endCaptures": { - "0": { - "name": "punctuation.section.block.begin.bracket.curly.struct.cpp" - } - }, - "name": "meta.head.struct.cpp", - "patterns": [ - { - "include": "#ever_present_context" - }, - { - "include": "#inheritance_context" - }, - { - "include": "#template_call_range" - } - ] - }, - { - "begin": "(?<=\\{|<%|\\?\\?<)", - "end": "\\}|%>|\\?\\?>", - "beginCaptures": {}, - "endCaptures": { - "0": { - "name": "punctuation.section.block.end.bracket.curly.struct.cpp" - } - }, - "name": "meta.body.struct.cpp", - "patterns": [ - { - "include": "#function_pointer" - }, - { - "include": "#static_assert" - }, - { - "include": "#constructor_inline" - }, - { - "include": "#destructor_inline" - }, - { - "include": "$self" - } - ] - }, - { - "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)", - "beginCaptures": {}, - "endCaptures": {}, - "name": "meta.tail.struct.cpp", - "patterns": [ - { - "include": "$self" - } - ] - } - ] - }, - "struct_declare": { - "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\S)(?![:{])", - "captures": { - "1": { - "name": "storage.type.struct.declare.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "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" - } - ] - } - } - } - ] - }, - "4": { "name": "entity.name.type.struct.cpp" }, - "5": { + "7": { "patterns": [ { "match": "\\*", "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ @@ -16515,40 +12526,6 @@ } ] }, - "6": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "7": { - "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" - } - ] - } - } - } - ] - }, "8": { "patterns": [ { @@ -16557,108 +12534,106 @@ ] }, "9": { - "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" - } - ] - } - } - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp" }, "11": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "12": { - "name": "variable.other.object.declare.cpp" - }, - "13": { "patterns": [ { "include": "#inline_comment" } ] }, + "13": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "14": { + "name": "comment.block.cpp" + }, + "15": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "16": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "17": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "18": { + "name": "comment.block.cpp" + }, + "19": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "20": { + "name": "variable.other.object.declare.cpp" + }, + "21": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "22": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "23": { + "name": "comment.block.cpp" + }, + "24": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] } } }, "switch_conditional_parentheses": { - "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)", + "name": "meta.conditional.switch.cpp", + "begin": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", "beginCaptures": { "1": { "patterns": [ @@ -16689,12 +12664,12 @@ "name": "punctuation.section.parens.begin.bracket.round.conditional.switch.cpp" } }, + "end": "(\\))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.parens.end.bracket.round.conditional.switch.cpp" } }, - "name": "meta.conditional.switch.cpp", "patterns": [ { "include": "#evaluation_context" @@ -16705,26 +12680,26 @@ ] }, "switch_statement": { - "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?|\\?\\?>)|(?=[;>\\[\\]=]))", + "name": "meta.block.switch.cpp", + "begin": "(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?|\\?\\?>)|(?=[;>\\[\\]=]))", "patterns": [ { + "name": "meta.head.switch.cpp", "begin": "\\G ?", - "end": "(?:\\{|<%|\\?\\?<|(?=;))", - "beginCaptures": {}, + "end": "((?:\\{|<%|\\?\\?<|(?=;)))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.begin.bracket.curly.switch.cpp" } }, - "name": "meta.head.switch.cpp", "patterns": [ { "include": "#switch_conditional_parentheses" @@ -16763,15 +12736,14 @@ ] }, { + "name": "meta.body.switch.cpp", "begin": "(?<=\\{|<%|\\?\\?<)", - "end": "\\}|%>|\\?\\?>", - "beginCaptures": {}, + "end": "(\\}|%>|\\?\\?>)", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.end.bracket.curly.switch.cpp" } }, - "name": "meta.body.switch.cpp", "patterns": [ { "include": "#default_statement" @@ -16788,11 +12760,9 @@ ] }, { - "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)", - "beginCaptures": {}, - "endCaptures": {}, "name": "meta.tail.switch.cpp", + "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)", "patterns": [ { "include": "$self" @@ -16802,7 +12772,7 @@ ] }, "template_argument_defaulted": { - "match": "(?<=<|,)(?:(?:\\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)+)*)((?:[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)+)?([=])", + "match": "(?<=<|,)\\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+)*)((?:[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*([=])", "captures": { "1": { "name": "storage.type.template.cpp" @@ -16850,32 +12820,32 @@ ] }, "template_call_innards": { - "match": "((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<1>?)+>)(?:(?:\\s)+)?", + "match": "((?(?:(?>[^<>]*)\\g<1>?)+)>)\\s*", "captures": { "0": { + "name": "meta.template.call.cpp", "patterns": [ { "include": "#template_call_range" } ] } - }, - "name": "meta.template.call.cpp" + } }, "template_call_range": { - "begin": "<", - "end": ">", + "name": "meta.template.call.cpp", + "begin": "(<)", "beginCaptures": { - "0": { + "1": { "name": "punctuation.section.angle-brackets.begin.template.call.cpp" } }, + "end": "(>)", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.angle-brackets.end.template.call.cpp" } }, - "name": "meta.template.call.cpp", "patterns": [ { "include": "#template_call_context" @@ -16883,8 +12853,8 @@ ] }, "template_definition": { - "begin": "(?", + "name": "meta.template.definition.cpp", + "begin": "(?)", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.angle-brackets.end.template.definition.cpp" } }, - "name": "meta.template.definition.cpp", "patterns": [ { - "begin": "(?<=\\w)(?:(?:\\s)+)?<", - "end": ">", + "begin": "((?<=\\w)\\s*<)", "beginCaptures": { - "0": { + "1": { "name": "punctuation.section.angle-brackets.begin.template.call.cpp" } }, + "end": "(>)", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.angle-brackets.begin.template.call.cpp" } }, @@ -16925,7 +12895,7 @@ ] }, "template_definition_argument": { - "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}))*)|((?:(?:[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)+)+)((?:[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}))*))|((?:[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)+)?((?:[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)+)?(?:(,)|(?=>|$))", + "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\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}))*)|((?:(?:[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+)+)((?:[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}))*))|((?:[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*((?:[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*(?:(,)|(?=>|$))", "captures": { "1": { "patterns": [ @@ -16935,36 +12905,27 @@ ] }, "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "3": { - "name": "storage.type.template.argument.$3.cpp" + "5": { + "name": "storage.type.template.argument.$5.cpp" }, - "4": { + "6": { "patterns": [ { "match": "(?:[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}))*", @@ -16972,19 +12933,19 @@ } ] }, - "5": { - "name": "entity.name.type.template.cpp" - }, - "6": { - "name": "storage.type.template.cpp" - }, "7": { - "name": "punctuation.vararg-ellipses.template.definition.cpp" + "name": "entity.name.type.template.cpp" }, "8": { - "name": "entity.name.type.template.cpp" + "name": "storage.type.template.cpp" }, "9": { + "name": "punctuation.vararg-ellipses.template.definition.cpp" + }, + "10": { + "name": "entity.name.type.template.cpp" + }, + "11": { "name": "punctuation.separator.delimiter.comma.template.argument.cpp" } } @@ -17009,7 +12970,7 @@ ] }, "template_isolated_definition": { - "match": "(?(?:(?:\\s)+)?$)", + "match": "(?\\s*$)", "captures": { "1": { "name": "storage.type.template.cpp" @@ -17031,15 +12992,16 @@ } }, "ternary_operator": { - "begin": "\\?", - "end": ":", + "applyEndPatternLast": true, + "begin": "(\\?)", "beginCaptures": { - "0": { + "1": { "name": "keyword.operator.ternary.cpp" } }, + "end": "(:)", "endCaptures": { - "0": { + "1": { "name": "keyword.operator.ternary.cpp" } }, @@ -17119,6 +13081,9 @@ { "include": "#square_brackets" }, + { + "include": "#empty_square_brackets" + }, { "include": "#semicolon" }, @@ -17128,7 +13093,7 @@ ] }, "the_this_keyword": { - "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "3": { + "5": { "name": "variable.language.this.cpp" } } }, "type_alias": { - "match": "(using)(?:(?:\\s)+)?(?!namespace)(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<43>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<43>?)+>)(?:(?:\\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<43>?)+>)(?:(?:\\s)+)?)?(?![\\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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<43>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<43>?)+>)(?:(?:\\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<43>?)+>)(?:(?:\\s)+)?)?(?![\\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(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<58>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<58>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|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<58>?)+)>)\\s*)?(?![\\w<:.]))\\s*(\\=)\\s*((?:typename)?)\\s*((?:(?:(?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<58>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<58>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|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<58>?)+)>)\\s*)?(?![\\w<:.]))|(.*(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:(\\[)(\\w*)(\\])\\s*)?\\s*(?:(;)|\\n)", "captures": { "1": { "name": "keyword.other.using.directive.cpp" @@ -17179,7 +13135,7 @@ "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?", - "beginCaptures": { - "0": { - "name": "punctuation.section.angle-brackets.begin.template.call.cpp" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.section.angle-brackets.end.template.call.cpp" - } - }, - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_context" - } - ] + "include": "#template_call_range" }, { "match": "(?:[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}))*", @@ -17247,98 +13186,59 @@ ] }, "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" - } - ] - } - } - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "6": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp" }, "7": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "8": { "patterns": [ { - "include": "#scope_resolution_inner_generated" + "include": "#inline_comment" } ] }, "9": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "10": { + "name": "comment.block.cpp" + }, + "11": { "patterns": [ { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "12": { - "name": "entity.name.scope-resolution.cpp" - }, "13": { + "patterns": [ + { + "include": "#scope_resolution_inner_generated" + } + ] + }, + "14": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + }, + "16": { "name": "meta.template.call.cpp", "patterns": [ { @@ -17346,45 +13246,8 @@ } ] }, - "15": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" - }, - "16": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "17": { - "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" - } - ] - } - } - } - ] - }, "18": { - "name": "entity.name.type.cpp" + "name": "entity.name.scope-resolution.cpp" }, "19": { "name": "meta.template.call.cpp", @@ -17395,50 +13258,62 @@ ] }, "21": { - "name": "keyword.operator.assignment.cpp" + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, "22": { - "name": "keyword.other.typename.cpp" + "patterns": [ + { + "include": "#inline_comment" + } + ] }, "23": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "24": { + "name": "comment.block.cpp" + }, + "25": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "26": { + "name": "entity.name.type.cpp" + }, + "27": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "29": { + "name": "keyword.operator.assignment.cpp" + }, + "30": { + "name": "keyword.other.typename.cpp" + }, + "31": { "patterns": [ { "include": "#storage_specifiers" } ] }, - "24": { - "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" - } - ] - } - } - } - ] - }, - "25": { + "32": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?", - "beginCaptures": { - "0": { - "name": "punctuation.section.angle-brackets.begin.template.call.cpp" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.section.angle-brackets.end.template.call.cpp" - } - }, - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_context" - } - ] + "include": "#template_call_range" }, { "match": "(?:[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}))*", @@ -17488,7 +13346,7 @@ } ] }, - "26": { + "33": { "patterns": [ { "include": "#attributes_context" @@ -17498,106 +13356,67 @@ } ] }, - "27": { + "34": { "patterns": [ { "include": "#inline_comment" } ] }, - "28": { + "35": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "36": { + "name": "comment.block.cpp" + }, + "37": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "29": { + "38": { "patterns": [ { "include": "#inline_comment" } ] }, - "30": { + "39": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "40": { + "name": "comment.block.cpp" + }, + "41": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "31": { + "43": { "patterns": [ { "include": "#scope_resolution_inner_generated" } ] }, - "32": { + "44": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, - "33": { - "patterns": [ - { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } - } - ] - }, - "35": { - "name": "entity.name.scope-resolution.cpp" - }, - "36": { + "46": { "name": "meta.template.call.cpp", "patterns": [ { @@ -17605,47 +13424,49 @@ } ] }, - "38": { + "48": { + "name": "entity.name.scope-resolution.cpp" + }, + "49": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "51": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, - "39": { + "52": { "patterns": [ { "include": "#inline_comment" } ] }, - "40": { + "53": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "54": { + "name": "comment.block.cpp" + }, + "55": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "41": { + "56": { "name": "entity.name.type.cpp" }, - "42": { + "57": { "name": "meta.template.call.cpp", "patterns": [ { @@ -17653,7 +13474,7 @@ } ] }, - "44": { + "59": { "name": "meta.declaration.type.alias.value.unknown.cpp", "patterns": [ { @@ -17661,14 +13482,14 @@ } ] }, - "45": { + "60": { "patterns": [ { "match": "\\*", "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ @@ -17704,129 +13525,102 @@ } ] }, - "46": { + "61": { "patterns": [ { "include": "#inline_comment" } ] }, - "47": { + "62": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "63": { + "name": "comment.block.cpp" + }, + "64": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "48": { + "65": { "patterns": [ { "include": "#inline_comment" } ] }, - "49": { + "66": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "67": { + "name": "comment.block.cpp" + }, + "68": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "50": { + "69": { "patterns": [ { "include": "#inline_comment" } ] }, - "51": { + "70": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "71": { + "name": "comment.block.cpp" + }, + "72": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "52": { + "73": { "name": "punctuation.definition.begin.bracket.square.cpp" }, - "53": { + "74": { "patterns": [ { "include": "#evaluation_context" } ] }, - "54": { + "75": { "name": "punctuation.definition.end.bracket.square.cpp" }, - "55": { + "76": { "name": "punctuation.terminator.statement.cpp" } }, "name": "meta.declaration.type.alias.cpp" }, "type_casting_operators": { - "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "3": { - "name": "keyword.operator.wordlike.cpp keyword.operator.cast.$3.cpp" + "5": { + "name": "keyword.operator.wordlike.cpp keyword.operator.cast.$5.cpp" } } }, "typedef_class": { - "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(DLLEXPORT)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(final)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:))?", - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))", + "name": "meta.block.class.cpp", + "begin": "((((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(DLLEXPORT)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(final)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)((?>[^{]*)))?))", "beginCaptures": { - "0": { + "1": { "name": "meta.head.class.cpp" }, - "1": { - "name": "storage.type.$1.cpp" + "3": { + "name": "storage.type.$3.cpp" }, - "2": { + "4": { "patterns": [ { "include": "#inline_comment" } ] }, - "3": { + "5": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, "6": { - "patterns": [ - { - "include": "#attributes_context" - }, - { - "include": "#number_literal" - } - ] + "name": "comment.block.cpp" }, "7": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "8": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "9": { - "name": "comment.block.cpp" - }, - "10": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "11": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "12": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "13": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "14": { - "name": "comment.block.cpp" - }, - "15": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "16": { "patterns": [ { "include": "#attributes_context" @@ -17985,101 +13706,139 @@ } ] }, - "17": { + "9": { "patterns": [ { "include": "#inline_comment" } ] }, + "10": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "11": { + "name": "comment.block.cpp" + }, + "12": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "13": { + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + "14": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "15": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "16": { + "name": "comment.block.cpp" + }, + "17": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "18": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "19": { - "name": "comment.block.cpp" - }, - "20": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + "include": "#attributes_context" }, { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#number_literal" } ] }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "21": { - "name": "entity.name.type.$1.cpp" + "name": "comment.block.cpp" }, "22": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "23": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "entity.name.type.$3.cpp" }, "24": { - "name": "comment.block.cpp" - }, - "25": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "26": { - "name": "storage.type.modifier.final.cpp" + "name": "comment.block.cpp" }, "27": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "28": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "storage.type.modifier.final.cpp" }, "29": { - "name": "comment.block.cpp" - }, - "30": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "31": { "patterns": [ { "include": "#inline_comment" } ] }, - "32": { + "30": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "33": { + "31": { "name": "comment.block.cpp" }, - "34": { + "32": { "patterns": [ { "match": "\\*\\/", @@ -18091,10 +13850,43 @@ } ] }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "37": { "name": "punctuation.separator.colon.inheritance.cpp" + }, + "38": { + "patterns": [ + { + "include": "#inheritance_context" + } + ] } }, + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))", "endCaptures": { "1": { "name": "punctuation.terminator.statement.cpp" @@ -18103,18 +13895,16 @@ "name": "punctuation.terminator.statement.cpp" } }, - "name": "meta.block.class.cpp", "patterns": [ { + "name": "meta.head.class.cpp", "begin": "\\G ?", - "end": "(?:\\{|<%|\\?\\?<|(?=;))", - "beginCaptures": {}, + "end": "((?:\\{|<%|\\?\\?<|(?=;)))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.begin.bracket.curly.class.cpp" } }, - "name": "meta.head.class.cpp", "patterns": [ { "include": "#ever_present_context" @@ -18128,15 +13918,14 @@ ] }, { + "name": "meta.body.class.cpp", "begin": "(?<=\\{|<%|\\?\\?<)", - "end": "\\}|%>|\\?\\?>", - "beginCaptures": {}, + "end": "(\\}|%>|\\?\\?>)", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.end.bracket.curly.class.cpp" } }, - "name": "meta.body.class.cpp", "patterns": [ { "include": "#function_pointer" @@ -18156,14 +13945,12 @@ ] }, { - "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)", - "beginCaptures": {}, - "endCaptures": {}, "name": "meta.tail.class.cpp", + "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)", "patterns": [ { - "match": "(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ @@ -18298,24 +14085,22 @@ ] }, "typedef_function_pointer": { - "begin": "((?(?:\\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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<25>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<25>?)+>)(?:(?:\\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<25>?)+>)(?:(?:\\s)+)?)?(?![\\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)(?!\\()", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|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<27>?)+)>)\\s*)?(?![\\w<:.]))(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\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*(\\()", "beginCaptures": { "1": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?", - "beginCaptures": { - "0": { - "name": "punctuation.section.angle-brackets.begin.template.call.cpp" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.section.angle-brackets.end.template.call.cpp" - } - }, - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_context" - } - ] + "include": "#template_call_range" }, { "match": "(?:[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}))*", @@ -18425,17 +14193,17 @@ } ] }, - "11": { + "12": { "patterns": [ { "include": "#scope_resolution_inner_generated" } ] }, - "12": { + "13": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, - "13": { + "15": { "name": "meta.template.call.cpp", "patterns": [ { @@ -18443,11 +14211,10 @@ } ] }, - "14": {}, - "15": { + "17": { "name": "entity.name.scope-resolution.cpp" }, - "16": { + "18": { "name": "meta.template.call.cpp", "patterns": [ { @@ -18455,24 +14222,23 @@ } ] }, - "17": {}, - "18": { + "20": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, - "19": { + "21": { "patterns": [ { "include": "#inline_comment" } ] }, - "20": { + "22": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "21": { + "23": { "name": "comment.block.cpp" }, - "22": { + "24": { "patterns": [ { "match": "\\*\\/", @@ -18484,10 +14250,10 @@ } ] }, - "23": { + "25": { "name": "entity.name.type.cpp" }, - "24": { + "26": { "name": "meta.template.call.cpp", "patterns": [ { @@ -18495,15 +14261,14 @@ } ] }, - "25": {}, - "26": { + "28": { "patterns": [ { "match": "\\*", "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ @@ -18539,45 +14304,20 @@ } ] }, - "27": { + "29": { "patterns": [ { "include": "#inline_comment" } ] }, - "28": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "29": { - "name": "comment.block.cpp" - }, "30": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "31": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp" }, "32": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "33": { - "name": "comment.block.cpp" - }, - "34": { "patterns": [ { "match": "\\*\\/", @@ -18589,20 +14329,20 @@ } ] }, - "35": { + "33": { "patterns": [ { "include": "#inline_comment" } ] }, - "36": { + "34": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "37": { + "35": { "name": "comment.block.cpp" }, - "38": { + "36": { "patterns": [ { "match": "\\*\\/", @@ -18614,35 +14354,61 @@ } ] }, + "37": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "38": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "39": { - "name": "punctuation.section.parens.begin.bracket.round.function.pointer.cpp" + "name": "comment.block.cpp" }, "40": { - "name": "punctuation.definition.function.pointer.dereference.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "41": { - "name": "entity.name.type.alias.cpp entity.name.type.pointer.function.cpp" + "name": "punctuation.section.parens.begin.bracket.round.function.pointer.cpp" }, "42": { - "name": "punctuation.definition.begin.bracket.square.cpp" + "name": "punctuation.definition.function.pointer.dereference.cpp" }, "43": { + "name": "entity.name.type.alias.cpp entity.name.type.pointer.function.cpp" + }, + "44": { + "name": "punctuation.definition.begin.bracket.square.cpp" + }, + "45": { "patterns": [ { "include": "#evaluation_context" } ] }, - "44": { + "46": { "name": "punctuation.definition.end.bracket.square.cpp" }, - "45": { + "47": { "name": "punctuation.section.parens.end.bracket.round.function.pointer.cpp" }, - "46": { + "48": { "name": "punctuation.section.parameters.begin.bracket.round.function.pointer.cpp" } }, + "end": "(\\))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=[{=,);]|\\n)(?!\\()", "endCaptures": { "1": { "name": "punctuation.section.parameters.end.bracket.round.function.pointer.cpp" @@ -18681,115 +14447,84 @@ } ] }, + "typedef_keyword": { + "match": "((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(DLLEXPORT)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(final)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:))?", - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))", + "name": "meta.block.struct.cpp", + "begin": "((((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(DLLEXPORT)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(final)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)((?>[^{]*)))?))", "beginCaptures": { - "0": { + "1": { "name": "meta.head.struct.cpp" }, - "1": { - "name": "storage.type.$1.cpp" + "3": { + "name": "storage.type.$3.cpp" }, - "2": { + "4": { "patterns": [ { "include": "#inline_comment" } ] }, - "3": { + "5": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, "6": { - "patterns": [ - { - "include": "#attributes_context" - }, - { - "include": "#number_literal" - } - ] + "name": "comment.block.cpp" }, "7": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "8": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "9": { - "name": "comment.block.cpp" - }, - "10": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "11": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "12": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "13": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "14": { - "name": "comment.block.cpp" - }, - "15": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "16": { "patterns": [ { "include": "#attributes_context" @@ -18799,101 +14534,139 @@ } ] }, - "17": { + "9": { "patterns": [ { "include": "#inline_comment" } ] }, + "10": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "11": { + "name": "comment.block.cpp" + }, + "12": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "13": { + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + "14": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "15": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "16": { + "name": "comment.block.cpp" + }, + "17": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "18": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "19": { - "name": "comment.block.cpp" - }, - "20": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + "include": "#attributes_context" }, { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#number_literal" } ] }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "21": { - "name": "entity.name.type.$1.cpp" + "name": "comment.block.cpp" }, "22": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "23": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "entity.name.type.$3.cpp" }, "24": { - "name": "comment.block.cpp" - }, - "25": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "26": { - "name": "storage.type.modifier.final.cpp" + "name": "comment.block.cpp" }, "27": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "28": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "storage.type.modifier.final.cpp" }, "29": { - "name": "comment.block.cpp" - }, - "30": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "31": { "patterns": [ { "include": "#inline_comment" } ] }, - "32": { + "30": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "33": { + "31": { "name": "comment.block.cpp" }, - "34": { + "32": { "patterns": [ { "match": "\\*\\/", @@ -18905,10 +14678,43 @@ } ] }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "37": { "name": "punctuation.separator.colon.inheritance.cpp" + }, + "38": { + "patterns": [ + { + "include": "#inheritance_context" + } + ] } }, + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))", "endCaptures": { "1": { "name": "punctuation.terminator.statement.cpp" @@ -18917,18 +14723,16 @@ "name": "punctuation.terminator.statement.cpp" } }, - "name": "meta.block.struct.cpp", "patterns": [ { + "name": "meta.head.struct.cpp", "begin": "\\G ?", - "end": "(?:\\{|<%|\\?\\?<|(?=;))", - "beginCaptures": {}, + "end": "((?:\\{|<%|\\?\\?<|(?=;)))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.begin.bracket.curly.struct.cpp" } }, - "name": "meta.head.struct.cpp", "patterns": [ { "include": "#ever_present_context" @@ -18942,15 +14746,14 @@ ] }, { + "name": "meta.body.struct.cpp", "begin": "(?<=\\{|<%|\\?\\?<)", - "end": "\\}|%>|\\?\\?>", - "beginCaptures": {}, + "end": "(\\}|%>|\\?\\?>)", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.end.bracket.curly.struct.cpp" } }, - "name": "meta.body.struct.cpp", "patterns": [ { "include": "#function_pointer" @@ -18970,14 +14773,12 @@ ] }, { - "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)", - "beginCaptures": {}, - "endCaptures": {}, "name": "meta.tail.struct.cpp", + "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)", "patterns": [ { - "match": "(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ @@ -19112,114 +14913,50 @@ ] }, "typedef_union": { - "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(DLLEXPORT)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(final)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:))?", - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))", + "name": "meta.block.union.cpp", + "begin": "((((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(DLLEXPORT)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(final)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)((?>[^{]*)))?))", "beginCaptures": { - "0": { + "1": { "name": "meta.head.union.cpp" }, - "1": { - "name": "storage.type.$1.cpp" + "3": { + "name": "storage.type.$3.cpp" }, - "2": { + "4": { "patterns": [ { "include": "#inline_comment" } ] }, - "3": { + "5": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, "6": { - "patterns": [ - { - "include": "#attributes_context" - }, - { - "include": "#number_literal" - } - ] + "name": "comment.block.cpp" }, "7": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "8": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "9": { - "name": "comment.block.cpp" - }, - "10": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "11": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "12": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "13": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "14": { - "name": "comment.block.cpp" - }, - "15": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "16": { "patterns": [ { "include": "#attributes_context" @@ -19229,101 +14966,139 @@ } ] }, - "17": { + "9": { "patterns": [ { "include": "#inline_comment" } ] }, + "10": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "11": { + "name": "comment.block.cpp" + }, + "12": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "13": { + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + "14": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "15": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "16": { + "name": "comment.block.cpp" + }, + "17": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "18": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "19": { - "name": "comment.block.cpp" - }, - "20": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + "include": "#attributes_context" }, { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#number_literal" } ] }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "21": { - "name": "entity.name.type.$1.cpp" + "name": "comment.block.cpp" }, "22": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "23": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "entity.name.type.$3.cpp" }, "24": { - "name": "comment.block.cpp" - }, - "25": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "26": { - "name": "storage.type.modifier.final.cpp" + "name": "comment.block.cpp" }, "27": { "patterns": [ { - "include": "#inline_comment" + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "28": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "storage.type.modifier.final.cpp" }, "29": { - "name": "comment.block.cpp" - }, - "30": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "31": { "patterns": [ { "include": "#inline_comment" } ] }, - "32": { + "30": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "33": { + "31": { "name": "comment.block.cpp" }, - "34": { + "32": { "patterns": [ { "match": "\\*\\/", @@ -19335,10 +15110,43 @@ } ] }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "37": { "name": "punctuation.separator.colon.inheritance.cpp" + }, + "38": { + "patterns": [ + { + "include": "#inheritance_context" + } + ] } }, + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))", "endCaptures": { "1": { "name": "punctuation.terminator.statement.cpp" @@ -19347,18 +15155,16 @@ "name": "punctuation.terminator.statement.cpp" } }, - "name": "meta.block.union.cpp", "patterns": [ { + "name": "meta.head.union.cpp", "begin": "\\G ?", - "end": "(?:\\{|<%|\\?\\?<|(?=;))", - "beginCaptures": {}, + "end": "((?:\\{|<%|\\?\\?<|(?=;)))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.begin.bracket.curly.union.cpp" } }, - "name": "meta.head.union.cpp", "patterns": [ { "include": "#ever_present_context" @@ -19372,15 +15178,14 @@ ] }, { + "name": "meta.body.union.cpp", "begin": "(?<=\\{|<%|\\?\\?<)", - "end": "\\}|%>|\\?\\?>", - "beginCaptures": {}, + "end": "(\\}|%>|\\?\\?>)", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.end.bracket.curly.union.cpp" } }, - "name": "meta.body.union.cpp", "patterns": [ { "include": "#function_pointer" @@ -19400,14 +15205,12 @@ ] }, { - "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)", - "beginCaptures": {}, - "endCaptures": {}, "name": "meta.tail.union.cpp", + "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)", "patterns": [ { - "match": "(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ @@ -19542,8 +15345,8 @@ ] }, "typeid_operator": { - "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()", - "end": "\\)", + "contentName": "meta.arguments.operator.typeid.cpp", + "begin": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\()", "beginCaptures": { "1": { "name": "keyword.operator.functionlike.cpp keyword.operator.typeid.cpp" @@ -19577,12 +15380,12 @@ "name": "punctuation.section.arguments.begin.bracket.round.operator.typeid.cpp" } }, + "end": "(\\))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.arguments.end.bracket.round.operator.typeid.cpp" } }, - "contentName": "meta.arguments.operator.typeid", "patterns": [ { "include": "#evaluation_context" @@ -19590,7 +15393,7 @@ ] }, "typename": { - "match": "(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__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)(?:[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*+(?:((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<24>?)+>)(?:(?:\\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}))*)\\s*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<24>?)+>)(?:(?:\\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<24>?)+>)(?:(?:\\s)+)?)?(?![\\w<:.]))", + "match": "(((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(((::)?(?:((?-mix:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_cancel|atomic_commit|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|consteval|constexpr|protected|namespace|co_return|constexpr|constexpr|continue|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|co_await|template|volatile|register|restrict|reflexpr|alignof|private|default|mutable|include|concept|__asm__|defined|_Pragma|alignas|typedef|warning|virtual|mutable|struct|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|while|compl|bitor|const|union|ifdef|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|asm|try|for|new|and|xor|not|do|or|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}))*))\\s*+(((?(?:(?>[^<>]*)\\g<36>?)+)>)\\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}))*)\\s*+(((?(?:(?>[^<>]*)\\g<36>?)+)>)\\s*)?(::))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?!(?:transaction_safe_dynamic|__has_cpp_attribute|transaction_safe|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|dynamic_cast|thread_local|synchronized|static_cast|const_cast|constexpr|consteval|protected|constexpr|co_return|namespace|constexpr|noexcept|typename|decltype|template|operator|noexcept|co_yield|co_await|continue|volatile|register|restrict|explicit|override|volatile|reflexpr|noexcept|requires|default|typedef|nullptr|alignof|mutable|concept|virtual|defined|__asm__|include|_Pragma|mutable|warning|private|alignas|module|switch|not_eq|bitand|and_eq|ifndef|return|sizeof|xor_eq|export|static|delete|public|define|extern|inline|import|pragma|friend|typeid|const|compl|bitor|throw|or_eq|while|catch|break|false|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<36>?)+)>)\\s*)?(?![\\w<:.]))", "captures": { "1": { "name": "storage.modifier.cpp" @@ -19603,71 +15406,53 @@ ] }, "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "4": { + "6": { "patterns": [ { "include": "#inline_comment" } ] }, - "5": { + "7": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "8": { + "name": "comment.block.cpp" + }, + "9": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "6": { + "10": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?", - "beginCaptures": { - "0": { - "name": "punctuation.section.angle-brackets.begin.template.call.cpp" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.section.angle-brackets.end.template.call.cpp" - } - }, - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_context" - } - ] + "include": "#template_call_range" }, { "match": "(?:[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}))*", @@ -19717,7 +15485,7 @@ } ] }, - "7": { + "11": { "patterns": [ { "include": "#attributes_context" @@ -19727,322 +15495,6 @@ } ] }, - "8": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "9": { - "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" - } - ] - } - } - } - ] - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "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" - } - ] - } - } - } - ] - }, - "12": { - "patterns": [ - { - "include": "#scope_resolution_inner_generated" - } - ] - }, - "13": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" - }, - "14": { - "patterns": [ - { - "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)(?:[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*+(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<2>?)+>)(?:(?:\\s)+)?)?::", - "captures": { - "1": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "2": {} - } - } - ] - }, - "15": {}, - "16": { - "name": "entity.name.scope-resolution.cpp" - }, - "17": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "18": {}, - "19": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" - }, - "20": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "21": { - "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" - } - ] - } - } - } - ] - }, - "22": { - "name": "entity.name.type.cpp" - }, - "23": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#template_call_range" - } - ] - }, - "24": {} - } - }, - "undef": { - "match": "(^((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?undef\\b)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\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" - } - ] - } - } - } - ] - }, - "4": { - "name": "punctuation.definition.directive.cpp" - }, - "5": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "6": { - "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" - } - ] - } - } - } - ] - }, - "7": { - "name": "entity.name.function.preprocessor.cpp" - } - }, - "name": "meta.preprocessor.undef.cpp" - }, - "union_block": { - "begin": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(DLLEXPORT)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(final)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?:((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(:))?", - "end": "(?:(?:(?<=\\}|%>|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))", - "beginCaptures": { - "0": { - "name": "meta.head.union.cpp" - }, - "1": { - "name": "storage.type.$1.cpp" - }, - "2": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "3": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "4": { - "name": "comment.block.cpp" - }, - "5": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "6": { - "patterns": [ - { - "include": "#attributes_context" - }, - { - "include": "#number_literal" - } - ] - }, - "7": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "8": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "9": { - "name": "comment.block.cpp" - }, - "10": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "11": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, "12": { "patterns": [ { @@ -20069,29 +15521,19 @@ ] }, "16": { - "patterns": [ - { - "include": "#attributes_context" - }, - { - "include": "#number_literal" - } - ] - }, - "17": { "patterns": [ { "include": "#inline_comment" } ] }, - "18": { + "17": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "19": { + "18": { "name": "comment.block.cpp" }, - "20": { + "19": { "patterns": [ { "match": "\\*\\/", @@ -20104,50 +15546,51 @@ ] }, "21": { - "name": "entity.name.type.$1.cpp" - }, - "22": { "patterns": [ { - "include": "#inline_comment" + "include": "#scope_resolution_inner_generated" } ] }, - "23": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "22": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, "24": { - "name": "comment.block.cpp" - }, - "25": { + "name": "meta.template.call.cpp", "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#template_call_range" } ] }, "26": { - "name": "storage.type.modifier.final.cpp" + "name": "entity.name.scope-resolution.cpp" }, "27": { + "name": "meta.template.call.cpp", "patterns": [ { - "include": "#inline_comment" + "include": "#template_call_range" } ] }, - "28": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, "29": { - "name": "comment.block.cpp" + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, "30": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "31": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "32": { + "name": "comment.block.cpp" + }, + "33": { "patterns": [ { "match": "\\*\\/", @@ -20159,20 +15602,39 @@ } ] }, - "31": { + "34": { + "name": "entity.name.type.cpp" + }, + "35": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + } + } + }, + "undef": { + "match": "((?:^)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(#)\\s*undef\\b)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(DLLEXPORT)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?:(?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(final)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))?(?:((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(:)((?>[^{]*)))?))", + "beginCaptures": { + "1": { + "name": "meta.head.union.cpp" + }, + "3": { + "name": "storage.type.$3.cpp" + }, + "4": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "5": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "6": { + "name": "comment.block.cpp" + }, + "7": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "8": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "9": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "10": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "11": { + "name": "comment.block.cpp" + }, + "12": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "13": { + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + "14": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "15": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "16": { + "name": "comment.block.cpp" + }, + "17": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "18": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "21": { + "name": "comment.block.cpp" + }, + "22": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "23": { + "name": "entity.name.type.$3.cpp" + }, + "24": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "26": { + "name": "comment.block.cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "28": { + "name": "storage.type.modifier.final.cpp" + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "37": { + "name": "punctuation.separator.colon.inheritance.cpp" + }, + "38": { + "patterns": [ + { + "include": "#inheritance_context" + } + ] + } + }, + "end": "(?:(?:(?<=\\}|%>|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))", "endCaptures": { "1": { "name": "punctuation.terminator.statement.cpp" @@ -20196,18 +15914,16 @@ "name": "punctuation.terminator.statement.cpp" } }, - "name": "meta.block.union.cpp", "patterns": [ { + "name": "meta.head.union.cpp", "begin": "\\G ?", - "end": "(?:\\{|<%|\\?\\?<|(?=;))", - "beginCaptures": {}, + "end": "((?:\\{|<%|\\?\\?<|(?=;)))", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.begin.bracket.curly.union.cpp" } }, - "name": "meta.head.union.cpp", "patterns": [ { "include": "#ever_present_context" @@ -20221,15 +15937,14 @@ ] }, { + "name": "meta.body.union.cpp", "begin": "(?<=\\{|<%|\\?\\?<)", - "end": "\\}|%>|\\?\\?>", - "beginCaptures": {}, + "end": "(\\}|%>|\\?\\?>)", "endCaptures": { - "0": { + "1": { "name": "punctuation.section.block.end.bracket.curly.union.cpp" } }, - "name": "meta.body.union.cpp", "patterns": [ { "include": "#function_pointer" @@ -20249,11 +15964,9 @@ ] }, { - "begin": "(?<=\\}|%>|\\?\\?>)[\\s]*", - "end": "[\\s]*(?=;)", - "beginCaptures": {}, - "endCaptures": {}, "name": "meta.tail.union.cpp", + "begin": "(?<=\\}|%>|\\?\\?>)[\\s\\n]*", + "end": "[\\s\\n]*(?=;)", "patterns": [ { "include": "$self" @@ -20263,7 +15976,7 @@ ] }, "union_declare": { - "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\S)(?![:{])", + "match": "((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(?:\\&|\\*)((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))))*(?:\\&|\\*))?((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))\\b(?!override\\W|override\\$|final\\W|final\\$)((?(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))(?=\\S)(?![:{])", "captures": { "1": { "name": "storage.type.union.declare.cpp" @@ -20276,43 +15989,34 @@ ] }, "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, - "4": { + "6": { "name": "entity.name.type.union.cpp" }, - "5": { + "7": { "patterns": [ { "match": "\\*", "name": "storage.modifier.pointer.cpp" }, { - "match": "(?:\\&((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))){2,}\\&", + "match": "(?:\\&((?>(?:(?:(?>(?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))){2,}\\&", "captures": { "1": { "patterns": [ @@ -20348,40 +16052,6 @@ } ] }, - "6": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "7": { - "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" - } - ] - } - } - } - ] - }, "8": { "patterns": [ { @@ -20390,107 +16060,105 @@ ] }, "9": { - "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" - } - ] - } - } - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp" }, "11": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] }, "12": { - "name": "variable.other.object.declare.cpp" - }, - "13": { "patterns": [ { "include": "#inline_comment" } ] }, + "13": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, "14": { + "name": "comment.block.cpp" + }, + "15": { "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" - } - ] - } - } + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "16": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "17": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "18": { + "name": "comment.block.cpp" + }, + "19": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "20": { + "name": "variable.other.object.declare.cpp" + }, + "21": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "22": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "23": { + "name": "comment.block.cpp" + }, + "24": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" } ] } } }, "using_name": { - "match": "(using)(?:\\s)+(?!namespace\\b)", + "match": "(using)\\s+(?!namespace\\b)", "captures": { "1": { "name": "keyword.other.using.directive.cpp" @@ -20498,8 +16166,8 @@ } }, "using_namespace": { - "begin": "(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<6>?)+>)(?:(?:\\s)+)?)?::)*\\s*+)?((?(?:(?>[^<>]*)\\g<7>?)+)>)\\s*)?::)*\\s*+)?((?, pugi::xml_node, std::less >, std::allocator, pugi::xml_node> > > > >::type dnode + +std::_Rb_tree_iterator, pugi::xml_node, std::less >, std::allocator, pugi::xml_node> > > > > > dnode_it = dnodes_.find(uid.position) diff --git a/extensions/cpp/test/colorize-results/test-78769_cpp.json b/extensions/cpp/test/colorize-results/test-78769_cpp.json index 1e908057408..eb114a4f007 100644 --- a/extensions/cpp/test/colorize-results/test-78769_cpp.json +++ b/extensions/cpp/test/colorize-results/test-78769_cpp.json @@ -331,7 +331,7 @@ }, { "c": " ", - "t": "source.cpp meta.preprocessor.macro.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", "r": { "dark_plus": "meta.preprocessor: #569CD6", "light_plus": "meta.preprocessor: #0000FF", @@ -342,7 +342,7 @@ }, { "c": "public", - "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", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp storage.type.modifier.access.public.cpp", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -353,7 +353,7 @@ }, { "c": " ", - "t": "source.cpp meta.preprocessor.macro.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", "r": { "dark_plus": "meta.preprocessor: #569CD6", "light_plus": "meta.preprocessor: #0000FF", @@ -364,7 +364,7 @@ }, { "c": "base", - "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", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.qualified_type.cpp entity.name.type.cpp", "r": { "dark_plus": "entity.name.type: #4EC9B0", "light_plus": "entity.name.type: #267F99", @@ -375,7 +375,7 @@ }, { "c": " ", - "t": "source.cpp meta.preprocessor.macro.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", "r": { "dark_plus": "meta.preprocessor: #569CD6", "light_plus": "meta.preprocessor: #0000FF", @@ -386,7 +386,7 @@ }, { "c": "\\", - "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", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp constant.character.escape.line-continuation.cpp", "r": { "dark_plus": "constant.character.escape: #D7BA7D", "light_plus": "constant.character.escape: #FF0000", @@ -1187,4 +1187,4 @@ "hc_black": "meta.preprocessor: #569CD6" } } -] \ No newline at end of file +] diff --git a/extensions/cpp/test/colorize-results/test-80644_cpp.json b/extensions/cpp/test/colorize-results/test-80644_cpp.json index d2b3333f677..069f31ee72b 100644 --- a/extensions/cpp/test/colorize-results/test-80644_cpp.json +++ b/extensions/cpp/test/colorize-results/test-80644_cpp.json @@ -375,7 +375,7 @@ }, { "c": "1", - "t": "source.cpp meta.function.definition.special.constructor.cpp meta.head.function.definition.special.constructor.cpp meta.parameter.initialization constant.numeric.decimal.cpp", + "t": "source.cpp meta.function.definition.special.constructor.cpp meta.head.function.definition.special.constructor.cpp meta.parameter.initialization.cpp constant.numeric.decimal.cpp", "r": { "dark_plus": "constant.numeric: #B5CEA8", "light_plus": "constant.numeric: #098658", @@ -474,7 +474,7 @@ }, { "c": "3", - "t": "source.cpp meta.function.definition.special.constructor.cpp meta.head.function.definition.special.constructor.cpp meta.parameter.initialization constant.numeric.decimal.cpp", + "t": "source.cpp meta.function.definition.special.constructor.cpp meta.head.function.definition.special.constructor.cpp meta.parameter.initialization.cpp constant.numeric.decimal.cpp", "r": { "dark_plus": "constant.numeric: #B5CEA8", "light_plus": "constant.numeric: #098658", @@ -527,4 +527,4 @@ "hc_black": "default: #FFFFFF" } } -] \ No newline at end of file +] diff --git a/extensions/cpp/test/colorize-results/test-92369_cpp.json b/extensions/cpp/test/colorize-results/test-92369_cpp.json new file mode 100644 index 00000000000..fcc9e47435b --- /dev/null +++ b/extensions/cpp/test/colorize-results/test-92369_cpp.json @@ -0,0 +1,1927 @@ +[ + { + "c": "std", + "t": "source.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", + "hc_black": "entity.name.scope-resolution: #4EC9B0" + } + }, + { + "c": "::", + "t": "source.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" + } + }, + { + "c": "tuple_element", + "t": "source.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", + "hc_black": "entity.name.scope-resolution: #4EC9B0" + } + }, + { + "c": "<", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp punctuation.section.angle-brackets.begin.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "0", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp constant.numeric.decimal.cpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ",", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp punctuation.separator.delimiter.comma.template.argument.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "std", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp entity.name.scope-resolution.template.call.cpp", + "r": { + "dark_plus": "entity.name.scope-resolution: #4EC9B0", + "light_plus": "entity.name.scope-resolution: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.scope-resolution: #4EC9B0" + } + }, + { + "c": "::", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "pair", + "t": "source.cpp meta.template.call.cpp meta.template.call.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", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": "<", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.begin.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "pugi", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp entity.name.scope-resolution.template.call.cpp", + "r": { + "dark_plus": "entity.name.scope-resolution: #4EC9B0", + "light_plus": "entity.name.scope-resolution: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.scope-resolution: #4EC9B0" + } + }, + { + "c": "::", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "xml_node", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.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", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": ",", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.separator.delimiter.comma.template.argument.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "std", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp entity.name.scope-resolution.template.call.cpp", + "r": { + "dark_plus": "entity.name.scope-resolution: #4EC9B0", + "light_plus": "entity.name.scope-resolution: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.scope-resolution: #4EC9B0" + } + }, + { + "c": "::", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "map", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.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", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": "<", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.begin.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "std", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp entity.name.scope-resolution.template.call.cpp", + "r": { + "dark_plus": "entity.name.scope-resolution: #4EC9B0", + "light_plus": "entity.name.scope-resolution: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.scope-resolution: #4EC9B0" + } + }, + { + "c": "::", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "basic_string", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.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", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": "<", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.begin.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "char", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": ">", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.end.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.separator.delimiter.comma.template.argument.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "pugi", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp entity.name.scope-resolution.template.call.cpp", + "r": { + "dark_plus": "entity.name.scope-resolution: #4EC9B0", + "light_plus": "entity.name.scope-resolution: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.scope-resolution: #4EC9B0" + } + }, + { + "c": "::", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "xml_node", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.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", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": ",", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.separator.delimiter.comma.template.argument.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "std", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp entity.name.scope-resolution.template.call.cpp", + "r": { + "dark_plus": "entity.name.scope-resolution: #4EC9B0", + "light_plus": "entity.name.scope-resolution: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.scope-resolution: #4EC9B0" + } + }, + { + "c": "::", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "less", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.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", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": "<", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.begin.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "std", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp entity.name.scope-resolution.template.call.cpp", + "r": { + "dark_plus": "entity.name.scope-resolution: #4EC9B0", + "light_plus": "entity.name.scope-resolution: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.scope-resolution: #4EC9B0" + } + }, + { + "c": "::", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "basic_string", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.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", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": "<", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.begin.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "char", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": ">", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.end.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ">", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.end.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.separator.delimiter.comma.template.argument.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "std", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp entity.name.scope-resolution.template.call.cpp", + "r": { + "dark_plus": "entity.name.scope-resolution: #4EC9B0", + "light_plus": "entity.name.scope-resolution: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.scope-resolution: #4EC9B0" + } + }, + { + "c": "::", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "allocator", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.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", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": "<", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.begin.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "std", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp entity.name.scope-resolution.template.call.cpp", + "r": { + "dark_plus": "entity.name.scope-resolution: #4EC9B0", + "light_plus": "entity.name.scope-resolution: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.scope-resolution: #4EC9B0" + } + }, + { + "c": "::", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "pair", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.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", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": "<", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.begin.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "const", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp storage.modifier.specifier.const.cpp", + "r": { + "dark_plus": "storage.modifier: #569CD6", + "light_plus": "storage.modifier: #0000FF", + "dark_vs": "storage.modifier: #569CD6", + "light_vs": "storage.modifier: #0000FF", + "hc_black": "storage.modifier: #569CD6" + } + }, + { + "c": " ", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "std", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp entity.name.scope-resolution.template.call.cpp", + "r": { + "dark_plus": "entity.name.scope-resolution: #4EC9B0", + "light_plus": "entity.name.scope-resolution: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.scope-resolution: #4EC9B0" + } + }, + { + "c": "::", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "basic_string", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.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", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": "<", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.begin.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "char", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": ">", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.end.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.separator.delimiter.comma.template.argument.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "pugi", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp entity.name.scope-resolution.template.call.cpp", + "r": { + "dark_plus": "entity.name.scope-resolution: #4EC9B0", + "light_plus": "entity.name.scope-resolution: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.scope-resolution: #4EC9B0" + } + }, + { + "c": "::", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "xml_node", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.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", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": ">", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.end.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ">", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.end.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ">", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.end.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp meta.qualified_type.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ">", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp meta.template.call.cpp punctuation.section.angle-brackets.end.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp meta.qualified_type.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ">", + "t": "source.cpp meta.template.call.cpp meta.template.call.cpp punctuation.section.angle-brackets.end.template.call.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "::", + "t": "source.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" + } + }, + { + "c": "type dnode", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "std", + "t": "source.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", + "hc_black": "entity.name.scope-resolution: #4EC9B0" + } + }, + { + "c": "::", + "t": "source.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" + } + }, + { + "c": "_Rb_tree_iterator", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "<", + "t": "source.cpp keyword.operator.comparison.cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "std", + "t": "source.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", + "hc_black": "entity.name.scope-resolution: #4EC9B0" + } + }, + { + "c": "::", + "t": "source.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" + } + }, + { + "c": "pair", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "<", + "t": "source.cpp keyword.operator.comparison.cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "const", + "t": "source.cpp storage.modifier.specifier.const.cpp", + "r": { + "dark_plus": "storage.modifier: #569CD6", + "light_plus": "storage.modifier: #0000FF", + "dark_vs": "storage.modifier: #569CD6", + "light_vs": "storage.modifier: #0000FF", + "hc_black": "storage.modifier: #569CD6" + } + }, + { + "c": " ", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "long", + "t": "source.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": ",", + "t": "source.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" + } + }, + { + "c": " ", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "std", + "t": "source.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", + "hc_black": "entity.name.scope-resolution: #4EC9B0" + } + }, + { + "c": "::", + "t": "source.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" + } + }, + { + "c": "pair", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "<", + "t": "source.cpp keyword.operator.comparison.cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "pugi", + "t": "source.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", + "hc_black": "entity.name.scope-resolution: #4EC9B0" + } + }, + { + "c": "::", + "t": "source.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" + } + }, + { + "c": "xml_node", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.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" + } + }, + { + "c": " ", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "std", + "t": "source.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", + "hc_black": "entity.name.scope-resolution: #4EC9B0" + } + }, + { + "c": "::", + "t": "source.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" + } + }, + { + "c": "map", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "<", + "t": "source.cpp keyword.operator.comparison.cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "std", + "t": "source.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", + "hc_black": "entity.name.scope-resolution: #4EC9B0" + } + }, + { + "c": "::", + "t": "source.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" + } + }, + { + "c": "basic_string", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "<", + "t": "source.cpp keyword.operator.comparison.cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "char", + "t": "source.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": ">", + "t": "source.cpp keyword.operator.comparison.cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": ",", + "t": "source.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" + } + }, + { + "c": " ", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "pugi", + "t": "source.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", + "hc_black": "entity.name.scope-resolution: #4EC9B0" + } + }, + { + "c": "::", + "t": "source.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" + } + }, + { + "c": "xml_node", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.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" + } + }, + { + "c": " ", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "std", + "t": "source.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", + "hc_black": "entity.name.scope-resolution: #4EC9B0" + } + }, + { + "c": "::", + "t": "source.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" + } + }, + { + "c": "less", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "<", + "t": "source.cpp keyword.operator.comparison.cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "std", + "t": "source.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", + "hc_black": "entity.name.scope-resolution: #4EC9B0" + } + }, + { + "c": "::", + "t": "source.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" + } + }, + { + "c": "basic_string", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "<", + "t": "source.cpp keyword.operator.comparison.cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "char", + "t": "source.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": ">", + "t": "source.cpp keyword.operator.comparison.cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ">", + "t": "source.cpp keyword.operator.comparison.cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": ",", + "t": "source.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" + } + }, + { + "c": " ", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "std", + "t": "source.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", + "hc_black": "entity.name.scope-resolution: #4EC9B0" + } + }, + { + "c": "::", + "t": "source.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" + } + }, + { + "c": "allocator", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "<", + "t": "source.cpp keyword.operator.comparison.cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "std", + "t": "source.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", + "hc_black": "entity.name.scope-resolution: #4EC9B0" + } + }, + { + "c": "::", + "t": "source.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" + } + }, + { + "c": "pair", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "<", + "t": "source.cpp keyword.operator.comparison.cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "const", + "t": "source.cpp storage.modifier.specifier.const.cpp", + "r": { + "dark_plus": "storage.modifier: #569CD6", + "light_plus": "storage.modifier: #0000FF", + "dark_vs": "storage.modifier: #569CD6", + "light_vs": "storage.modifier: #0000FF", + "hc_black": "storage.modifier: #569CD6" + } + }, + { + "c": " ", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "std", + "t": "source.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", + "hc_black": "entity.name.scope-resolution: #4EC9B0" + } + }, + { + "c": "::", + "t": "source.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" + } + }, + { + "c": "basic_string", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "<", + "t": "source.cpp keyword.operator.comparison.cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "char", + "t": "source.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": ">", + "t": "source.cpp keyword.operator.comparison.cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": ",", + "t": "source.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" + } + }, + { + "c": " ", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "pugi", + "t": "source.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", + "hc_black": "entity.name.scope-resolution: #4EC9B0" + } + }, + { + "c": "::", + "t": "source.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" + } + }, + { + "c": "xml_node", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ">", + "t": "source.cpp keyword.operator.comparison.cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ">", + "t": "source.cpp keyword.operator.comparison.cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ">", + "t": "source.cpp keyword.operator.comparison.cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ">", + "t": "source.cpp keyword.operator.comparison.cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ">", + "t": "source.cpp keyword.operator.comparison.cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ">", + "t": "source.cpp keyword.operator.comparison.cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " dnode_it ", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.cpp keyword.operator.assignment.cpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.cpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "dnodes_", + "t": "source.cpp variable.other.object.access.cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ".", + "t": "source.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" + } + }, + { + "c": "find", + "t": "source.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", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.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" + } + }, + { + "c": "uid", + "t": "source.cpp variable.other.object.access.cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ".", + "t": "source.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" + } + }, + { + "c": "position", + "t": "source.cpp variable.other.property.cpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ")", + "t": "source.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" + } + } +] \ No newline at end of file diff --git a/extensions/cpp/test/colorize-results/test_cc.json b/extensions/cpp/test/colorize-results/test_cc.json index cb2fb192e1d..324b231bb1b 100644 --- a/extensions/cpp/test/colorize-results/test_cc.json +++ b/extensions/cpp/test/colorize-results/test_cc.json @@ -122,7 +122,7 @@ }, { "c": "%d", - "t": "source.cpp string.quoted.double.cpp constant.other.placeholder", + "t": "source.cpp string.quoted.double.cpp constant.other.placeholder.cpp", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -430,7 +430,7 @@ }, { "c": "%d", - "t": "source.cpp string.quoted.double.cpp constant.other.placeholder", + "t": "source.cpp string.quoted.double.cpp constant.other.placeholder.cpp", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -474,7 +474,7 @@ }, { "c": "user_candidate", - "t": "source.cpp meta.bracket.square.access variable.other.object", + "t": "source.cpp meta.bracket.square.access.cpp variable.other.object.cpp", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -485,7 +485,7 @@ }, { "c": "[", - "t": "source.cpp meta.bracket.square.access punctuation.definition.begin.bracket.square", + "t": "source.cpp meta.bracket.square.access.cpp punctuation.definition.begin.bracket.square.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -496,7 +496,7 @@ }, { "c": "i", - "t": "source.cpp meta.bracket.square.access", + "t": "source.cpp meta.bracket.square.access.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -507,7 +507,7 @@ }, { "c": "]", - "t": "source.cpp meta.bracket.square.access punctuation.definition.end.bracket.square", + "t": "source.cpp meta.bracket.square.access.cpp punctuation.definition.end.bracket.square.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -727,7 +727,7 @@ }, { "c": "O", - "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp entity.name.type.parameter.cpp", + "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp entity.name.type.parameter.cpp", "r": { "dark_plus": "entity.name.type: #4EC9B0", "light_plus": "entity.name.type: #267F99", @@ -738,7 +738,7 @@ }, { "c": " ", - "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp", + "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -749,7 +749,7 @@ }, { "c": "obj", - "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp variable.parameter.cpp", + "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp variable.parameter.cpp", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -1464,7 +1464,7 @@ }, { "c": "%s", - "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp string.quoted.double.cpp constant.other.placeholder", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp string.quoted.double.cpp constant.other.placeholder.cpp", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -1486,7 +1486,7 @@ }, { "c": "%s", - "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp string.quoted.double.cpp constant.other.placeholder", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp string.quoted.double.cpp constant.other.placeholder.cpp", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -1805,7 +1805,7 @@ }, { "c": "movw $0x38, %ax; ltr %ax", - "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp meta.asm.cpp string.quoted.double.cpp meta.embedded.assembly", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp meta.asm.cpp string.quoted.double.cpp meta.embedded.assembly.cpp", "r": { "dark_plus": "meta.embedded.assembly: #CE9178", "light_plus": "meta.embedded.assembly: #A31515", @@ -1979,4 +1979,4 @@ "hc_black": "default: #FFFFFF" } } -] \ No newline at end of file +] diff --git a/extensions/cpp/test/colorize-results/test_cpp.json b/extensions/cpp/test/colorize-results/test_cpp.json index cbc126be63f..108895f8f65 100644 --- a/extensions/cpp/test/colorize-results/test_cpp.json +++ b/extensions/cpp/test/colorize-results/test_cpp.json @@ -485,7 +485,7 @@ }, { "c": "int", - "t": "source.cpp meta.block.class.cpp meta.body.class.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "t": "source.cpp meta.block.class.cpp meta.body.class.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -496,7 +496,7 @@ }, { "c": ",", - "t": "source.cpp meta.block.class.cpp meta.body.class.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp punctuation.separator.delimiter.comma.cpp", + "t": "source.cpp meta.block.class.cpp meta.body.class.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp punctuation.separator.delimiter.comma.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -507,7 +507,7 @@ }, { "c": "int", - "t": "source.cpp meta.block.class.cpp meta.body.class.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "t": "source.cpp meta.block.class.cpp meta.body.class.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -793,7 +793,7 @@ }, { "c": "int", - "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -804,7 +804,7 @@ }, { "c": " ", - "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp", + "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -815,7 +815,7 @@ }, { "c": "x", - "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp variable.parameter.cpp", + "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp variable.parameter.cpp", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -826,7 +826,7 @@ }, { "c": ",", - "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp punctuation.separator.delimiter.comma.cpp", + "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp punctuation.separator.delimiter.comma.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -837,7 +837,7 @@ }, { "c": " ", - "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp", + "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -848,7 +848,7 @@ }, { "c": "int", - "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -859,7 +859,7 @@ }, { "c": " ", - "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp", + "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -870,7 +870,7 @@ }, { "c": "y", - "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters meta.parameter.cpp variable.parameter.cpp", + "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp variable.parameter.cpp", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -1123,7 +1123,7 @@ }, { "c": "long", - "t": "source.cpp meta.function.definition.special.operator-overload.cpp meta.head.function.definition.special.operator-overload.cpp meta.function.definition.parameters.special.operator-overload meta.parameter.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "t": "source.cpp meta.function.definition.special.operator-overload.cpp meta.head.function.definition.special.operator-overload.cpp meta.function.definition.parameters.special.operator-overload.cpp meta.parameter.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -1134,7 +1134,7 @@ }, { "c": " ", - "t": "source.cpp meta.function.definition.special.operator-overload.cpp meta.head.function.definition.special.operator-overload.cpp meta.function.definition.parameters.special.operator-overload meta.parameter.cpp", + "t": "source.cpp meta.function.definition.special.operator-overload.cpp meta.head.function.definition.special.operator-overload.cpp meta.function.definition.parameters.special.operator-overload.cpp meta.parameter.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1145,7 +1145,7 @@ }, { "c": "double", - "t": "source.cpp meta.function.definition.special.operator-overload.cpp meta.head.function.definition.special.operator-overload.cpp meta.function.definition.parameters.special.operator-overload meta.parameter.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "t": "source.cpp meta.function.definition.special.operator-overload.cpp meta.head.function.definition.special.operator-overload.cpp meta.function.definition.parameters.special.operator-overload.cpp meta.parameter.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -1519,7 +1519,7 @@ }, { "c": "movl %a %b", - "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp meta.asm.cpp string.quoted.double.cpp meta.embedded.assembly", + "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp meta.asm.cpp string.quoted.double.cpp meta.embedded.assembly.cpp", "r": { "dark_plus": "meta.embedded.assembly: #CE9178", "light_plus": "meta.embedded.assembly: #A31515", @@ -2430,4 +2430,4 @@ "hc_black": "default: #FFFFFF" } } -] \ No newline at end of file +] diff --git a/extensions/css-language-features/server/package.json b/extensions/css-language-features/server/package.json index 7f439dd686a..593ecdff80a 100644 --- a/extensions/css-language-features/server/package.json +++ b/extensions/css-language-features/server/package.json @@ -9,7 +9,7 @@ }, "main": "./out/cssServerMain", "dependencies": { - "vscode-css-languageservice": "^4.1.0", + "vscode-css-languageservice": "^4.1.1", "vscode-languageserver": "^6.1.1" }, "devDependencies": { diff --git a/extensions/css-language-features/server/yarn.lock b/extensions/css-language-features/server/yarn.lock index e57ff40f62e..902123900d0 100644 --- a/extensions/css-language-features/server/yarn.lock +++ b/extensions/css-language-features/server/yarn.lock @@ -689,10 +689,10 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -vscode-css-languageservice@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.1.0.tgz#144c8274e0bf1719fa6f773ca684bd1c7ffd634f" - integrity sha512-iTX3dTp0Y0RFWhIux5jasI8r9swdiWVB1Z3OrZ10iDHxzkETjVPxAQ5BEQU4ag0Awc8TTg1C7sJriHQY2LO14g== +vscode-css-languageservice@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.1.1.tgz#9131dd465e4b20f3ba78ab9734b2c7cdb9237443" + integrity sha512-2r2bYbhscivRu1zqh5kNe8aYpFnfksMYC7wTpKX2HqFsSzSJYXk0sCqPaWsP5ptqz0OFBTXnzx2JlE+Nb5Edgw== dependencies: vscode-languageserver-textdocument "^1.0.1" vscode-languageserver-types "^3.15.1" diff --git a/extensions/emmet/src/util.ts b/extensions/emmet/src/util.ts index b6281272851..49cc45cd630 100644 --- a/extensions/emmet/src/util.ts +++ b/extensions/emmet/src/util.ts @@ -32,7 +32,12 @@ export function updateEmmetExtensionsPath() { let extensionsPath = vscode.workspace.getConfiguration('emmet')['extensionsPath']; if (_currentExtensionsPath !== extensionsPath) { _currentExtensionsPath = extensionsPath; - _emmetHelper.updateExtensionsPath(extensionsPath, vscode.workspace.rootPath).then(null, (err: string) => vscode.window.showErrorMessage(err)); + if (!vscode.workspace.workspaceFolders) { + return; + } else { + const rootPath = vscode.workspace.workspaceFolders[0].uri.fsPath; + _emmetHelper.updateExtensionsPath(extensionsPath, rootPath).then(null, (err: string) => vscode.window.showErrorMessage(err)); + } } } @@ -622,4 +627,4 @@ export function trimQuotes(s: string) { } return s; -} \ No newline at end of file +} diff --git a/extensions/extension-editing/package.json b/extensions/extension-editing/package.json index fe4a7b44d2a..75d77925b0d 100644 --- a/extensions/extension-editing/package.json +++ b/extensions/extension-editing/package.json @@ -35,9 +35,13 @@ "url": "vscode://schemas/language-configuration" }, { - "fileMatch": "*icon-theme.json", + "fileMatch": ["*icon-theme.json", "!*product-icon-theme.json"], "url": "vscode://schemas/icon-theme" }, + { + "fileMatch": "*product-icon-theme.json", + "url": "vscode://schemas/product-icon-theme" + }, { "fileMatch": "*color-theme.json", "url": "vscode://schemas/color-theme" diff --git a/extensions/fsharp/cgmanifest.json b/extensions/fsharp/cgmanifest.json index 46584d1b7ef..e27527d9df1 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": "b4f43aafa6f843707410fabe9f904ec04fa536dc" + "commitHash": "af2a9a88639f384c6e5d1340d510dc6fb743d514" } }, "license": "MIT", diff --git a/extensions/fsharp/syntaxes/fsharp.tmLanguage.json b/extensions/fsharp/syntaxes/fsharp.tmLanguage.json index ed1e9fcb5f8..ac4b1de7093 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/b4f43aafa6f843707410fabe9f904ec04fa536dc", + "version": "https://github.com/ionide/ionide-fsgrammar/commit/af2a9a88639f384c6e5d1340d510dc6fb743d514", "name": "fsharp", "scopeName": "source.fsharp", "patterns": [ @@ -476,7 +476,7 @@ }, "endCaptures": { "1": { - "name": "keyword.fsharp" + "name": "keyword.symbol.arrow.fsharp" } }, "patterns": [ @@ -488,12 +488,12 @@ "end": "\\s*(?=(->))", "beginCaptures": { "1": { - "name": "keyword.symbol.fsharp" + "name": "keyword.symbol.arrow.fsharp" } }, "endCaptures": { "1": { - "name": "keyword.symbol.fsharp" + "name": "keyword.symbol.arrow.fsharp" } }, "patterns": [ @@ -545,15 +545,6 @@ } ] }, - { - "name": "comment.block.markdown.fsharp.end", - "match": "^(\\s*\\*\\)$)", - "captures": { - "1": { - "name": "comment.block.fsharp" - } - } - }, { "name": "comment.block.fsharp", "begin": "(\\(\\*(?!\\)))", @@ -567,6 +558,20 @@ "1": { "name": "comment.block.fsharp" } + }, + "patterns": [ + { + "include": "#comments" + } + ] + }, + { + "name": "comment.block.markdown.fsharp.end", + "match": "(\\*\\))", + "captures": { + "1": { + "name": "comment.block.fsharp" + } } }, { @@ -592,7 +597,7 @@ "match": "\\(\\)" }, { - "name": "constant.numeric.floating-point.fsharp", + "name": "constant.numeric.float.fsharp", "match": "\\b-?[0-9][0-9_]*((\\.([0-9][0-9_]*([eE][+-]??[0-9][0-9_]*)?)?)|([eE][+-]??[0-9][0-9_]*))" }, { @@ -600,7 +605,7 @@ "match": "\\b(-?((0(x|X)[0-9a-fA-F][0-9a-fA-F_]*)|(0(o|O)[0-7][0-7_]*)|(0(b|B)[01][01_]*)|([0-9][0-9_]*)))" }, { - "name": "constant.others.fsharp", + "name": "constant.other.fsharp", "match": "\\b(true|false|null|unit|void)\\b" } ] @@ -918,7 +923,7 @@ "name": "support.function.attribute.fsharp" }, "4": { - "name": "keyword.fsharp" + "name": "storage.modifier.fsharp" }, "5": { "name": "variable.fsharp" @@ -970,7 +975,7 @@ "name": "support.function.attribute.fsharp" }, "4": { - "name": "keyword.fsharp" + "name": "storage.modifier.fsharp" }, "5": { "name": "variable.fsharp" @@ -1061,9 +1066,21 @@ }, "keywords": { "patterns": [ + { + "name": "storage.modifier", + "match": "\\b(private|public|internal)\\b" + }, { "name": "keyword.fsharp", - "match": "\\b(private|to|public|internal|function|yield!|yield|class|exception|match|delegate|of|new|in|as|if|then|else|elif|for|begin|end|inherit|do|let\\!|return\\!|return|interface|with|abstract|enum|member|try|finally|and|when|or|use|use\\!|struct|while|mutable|assert|base|done|downcast|downto|extern|fixed|global|lazy|upcast|not)(?!')\\b" + "match": "\\b(private|to|public|internal|function|class|exception|delegate|of|as|begin|end|inherit|let!|interface|abstract|enum|member|and|when|or|use|use\\!|struct|mutable|assert|base|done|downcast|downto|extern|fixed|global|lazy|upcast|not)(?!')\\b" + }, + { + "name": "keyword.control", + "match": "\\b(match|yield|yield!|with|if|then|else|elif|for|in|return!|return|try|finally|while|do)(?!')\\b" + }, + { + "name": "keyword.symbol.new", + "match": "\\b(new)\\b" }, { "name": "keyword.symbol.fsharp", @@ -1075,7 +1092,7 @@ "patterns": [ { "name": "entity.name.section.fsharp", - "begin": "\\b(namespace global)|(namespace|module)\\s*(public|internal|private|rec)?\\s+([[:alpha:]][[:alpha:]0-9'_. ]*)", + "begin": "\\b(namespace global)|\\b(namespace|module)\\s*(public|internal|private|rec)?\\s+([[:alpha:]][[:alpha:]0-9'_. ]*)", "end": "(\\s?=|\\s|$)", "beginCaptures": { "1": { @@ -1085,7 +1102,7 @@ "name": "keyword.fsharp" }, "3": { - "name": "keyword.fsharp" + "name": "storage.modifier.fsharp" }, "4": { "name": "entity.name.section.fsharp" @@ -1507,7 +1524,7 @@ "name": "keyword.fsharp" }, "2": { - "name": "keyword.fsharp" + "name": "storage.modifier.fsharp" } }, "endCaptures": { @@ -1619,7 +1636,7 @@ "name": "keyword.symbol.fsharp" }, "2": { - "name": "keyword.fsharp" + "name": "storage.modifier.fsharp" } } }, @@ -1770,8 +1787,8 @@ "compiler_directives": { "patterns": [ { - "name": "compiler_directive.fsharp", - "match": "\\s?(#if|#elif|#else|#elseif|#endif|#light|#nowarn)", + "name": "keyword.control.directive.fsharp", + "match": "\\s?(#if|#elif|#elseif|#else|#endif|#light|#nowarn)", "captures": {} } ] diff --git a/extensions/fsharp/test/colorize-results/test_fs.json b/extensions/fsharp/test/colorize-results/test_fs.json index 9b68ea7507e..5b4d901897e 100644 --- a/extensions/fsharp/test/colorize-results/test_fs.json +++ b/extensions/fsharp/test/colorize-results/test_fs.json @@ -276,7 +276,7 @@ }, { "c": "new", - "t": "source.fsharp keyword.fsharp", + "t": "source.fsharp keyword.symbol.new", "r": { "dark_plus": "keyword: #569CD6", "light_plus": "keyword: #0000FF", @@ -1396,4 +1396,4 @@ "hc_black": "default: #FFFFFF" } } -] +] \ No newline at end of file diff --git a/extensions/git/package.json b/extensions/git/package.json index da0582803eb..35a4cb143be 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -40,10 +40,7 @@ "command": "git.init", "title": "%command.init%", "category": "Git", - "icon": { - "light": "resources/icons/light/git.svg", - "dark": "resources/icons/dark/git.svg" - } + "icon": "$(add)" }, { "command": "git.openRepository", @@ -59,37 +56,25 @@ "command": "git.refresh", "title": "%command.refresh%", "category": "Git", - "icon": { - "light": "resources/icons/light/refresh.svg", - "dark": "resources/icons/dark/refresh.svg" - } + "icon": "$(refresh)" }, { "command": "git.openChange", "title": "%command.openChange%", "category": "Git", - "icon": { - "light": "resources/icons/light/open-change.svg", - "dark": "resources/icons/dark/open-change.svg" - } + "icon": "$(compare-changes)" }, { "command": "git.openFile", "title": "%command.openFile%", "category": "Git", - "icon": { - "light": "resources/icons/light/open-file.svg", - "dark": "resources/icons/dark/open-file.svg" - } + "icon": "$(go-to-file)" }, { "command": "git.openFile2", "title": "%command.openFile%", "category": "Git", - "icon": { - "light": "resources/icons/light/open-file.svg", - "dark": "resources/icons/dark/open-file.svg" - } + "icon": "$(go-to-file)" }, { "command": "git.openHEADFile", @@ -100,37 +85,25 @@ "command": "git.stage", "title": "%command.stage%", "category": "Git", - "icon": { - "light": "resources/icons/light/stage.svg", - "dark": "resources/icons/dark/stage.svg" - } + "icon": "$(add)" }, { "command": "git.stageAll", "title": "%command.stageAll%", "category": "Git", - "icon": { - "light": "resources/icons/light/stage.svg", - "dark": "resources/icons/dark/stage.svg" - } + "icon": "$(add)" }, { "command": "git.stageAllTracked", "title": "%command.stageAllTracked%", "category": "Git", - "icon": { - "light": "resources/icons/light/stage.svg", - "dark": "resources/icons/dark/stage.svg" - } + "icon": "$(add)" }, { "command": "git.stageAllUntracked", "title": "%command.stageAllUntracked%", "category": "Git", - "icon": { - "light": "resources/icons/light/stage.svg", - "dark": "resources/icons/dark/stage.svg" - } + "icon": "$(add)" }, { "command": "git.stageSelectedRanges", @@ -146,37 +119,25 @@ "command": "git.stageChange", "title": "%command.stageChange%", "category": "Git", - "icon": { - "light": "resources/icons/light/stage.svg", - "dark": "resources/icons/dark/stage.svg" - } + "icon": "$(add)" }, { "command": "git.revertChange", "title": "%command.revertChange%", "category": "Git", - "icon": { - "light": "resources/icons/light/clean.svg", - "dark": "resources/icons/dark/clean.svg" - } + "icon": "$(discard)" }, { "command": "git.unstage", "title": "%command.unstage%", "category": "Git", - "icon": { - "light": "resources/icons/light/unstage.svg", - "dark": "resources/icons/dark/unstage.svg" - } + "icon": "$(remove)" }, { "command": "git.unstageAll", "title": "%command.unstageAll%", "category": "Git", - "icon": { - "light": "resources/icons/light/unstage.svg", - "dark": "resources/icons/dark/unstage.svg" - } + "icon": "$(remove)" }, { "command": "git.unstageSelectedRanges", @@ -187,46 +148,31 @@ "command": "git.clean", "title": "%command.clean%", "category": "Git", - "icon": { - "light": "resources/icons/light/clean.svg", - "dark": "resources/icons/dark/clean.svg" - } + "icon": "$(discard)" }, { "command": "git.cleanAll", "title": "%command.cleanAll%", "category": "Git", - "icon": { - "light": "resources/icons/light/clean.svg", - "dark": "resources/icons/dark/clean.svg" - } + "icon": "$(discard)" }, { "command": "git.cleanAllTracked", "title": "%command.cleanAllTracked%", "category": "Git", - "icon": { - "light": "resources/icons/light/clean.svg", - "dark": "resources/icons/dark/clean.svg" - } + "icon": "$(discard)" }, { "command": "git.cleanAllUntracked", "title": "%command.cleanAllUntracked%", "category": "Git", - "icon": { - "light": "resources/icons/light/clean.svg", - "dark": "resources/icons/dark/clean.svg" - } + "icon": "$(discard)" }, { "command": "git.commit", "title": "%command.commit%", "category": "Git", - "icon": { - "light": "resources/icons/light/check.svg", - "dark": "resources/icons/dark/check.svg" - } + "icon": "$(check)" }, { "command": "git.commitStaged", @@ -1314,11 +1260,6 @@ } ], "timeline/item/context": [ - { - "command": "git.timeline.openDiff", - "group": "inline", - "when": "config.git.enabled && !git.missing && timelineItem =~ /git:file\\b/" - }, { "command": "git.timeline.openDiff", "group": "1_timeline", @@ -1887,7 +1828,6 @@ }, "dependencies": { "byline": "^5.0.0", - "dayjs": "1.8.19", "file-type": "^7.2.0", "iconv-lite": "^0.4.24", "jschardet": "2.1.1", @@ -1900,7 +1840,7 @@ "@types/byline": "4.2.31", "@types/file-type": "^5.2.1", "@types/mocha": "2.2.43", - "@types/node": "^12.11.7", + "@types/node": "^12.12.31", "@types/which": "^1.0.28", "mocha": "^3.2.0", "mocha-junit-reporter": "^1.23.3", diff --git a/extensions/git/resources/icons/dark/check.svg b/extensions/git/resources/icons/dark/check.svg deleted file mode 100644 index 2d16f390078..00000000000 --- a/extensions/git/resources/icons/dark/check.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/resources/icons/dark/clean.svg b/extensions/git/resources/icons/dark/clean.svg deleted file mode 100644 index de85d6ba679..00000000000 --- a/extensions/git/resources/icons/dark/clean.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/resources/icons/dark/git.svg b/extensions/git/resources/icons/dark/git.svg deleted file mode 100644 index 4d9389336b9..00000000000 --- a/extensions/git/resources/icons/dark/git.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/resources/icons/dark/open-change.svg b/extensions/git/resources/icons/dark/open-change.svg deleted file mode 100644 index 41ebc85a8c8..00000000000 --- a/extensions/git/resources/icons/dark/open-change.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/resources/icons/dark/open-file.svg b/extensions/git/resources/icons/dark/open-file.svg deleted file mode 100644 index ed302ae1398..00000000000 --- a/extensions/git/resources/icons/dark/open-file.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/resources/icons/dark/refresh.svg b/extensions/git/resources/icons/dark/refresh.svg deleted file mode 100644 index e1f05aadeeb..00000000000 --- a/extensions/git/resources/icons/dark/refresh.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/extensions/git/resources/icons/dark/stage.svg b/extensions/git/resources/icons/dark/stage.svg deleted file mode 100644 index 4d9389336b9..00000000000 --- a/extensions/git/resources/icons/dark/stage.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/resources/icons/dark/unstage.svg b/extensions/git/resources/icons/dark/unstage.svg deleted file mode 100644 index 4c5a9c1e3a5..00000000000 --- a/extensions/git/resources/icons/dark/unstage.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/resources/icons/light/check.svg b/extensions/git/resources/icons/light/check.svg deleted file mode 100644 index a9f8aa131b5..00000000000 --- a/extensions/git/resources/icons/light/check.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/resources/icons/light/clean.svg b/extensions/git/resources/icons/light/clean.svg deleted file mode 100644 index b70626957d0..00000000000 --- a/extensions/git/resources/icons/light/clean.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/resources/icons/light/git.svg b/extensions/git/resources/icons/light/git.svg deleted file mode 100644 index 01a9de7d5ab..00000000000 --- a/extensions/git/resources/icons/light/git.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/resources/icons/light/open-change.svg b/extensions/git/resources/icons/light/open-change.svg deleted file mode 100644 index 772c3c198fc..00000000000 --- a/extensions/git/resources/icons/light/open-change.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/resources/icons/light/open-file.svg b/extensions/git/resources/icons/light/open-file.svg deleted file mode 100644 index 392a840c5ef..00000000000 --- a/extensions/git/resources/icons/light/open-file.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/resources/icons/light/refresh.svg b/extensions/git/resources/icons/light/refresh.svg deleted file mode 100644 index 9b1d9108409..00000000000 --- a/extensions/git/resources/icons/light/refresh.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/extensions/git/resources/icons/light/stage.svg b/extensions/git/resources/icons/light/stage.svg deleted file mode 100644 index 01a9de7d5ab..00000000000 --- a/extensions/git/resources/icons/light/stage.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/resources/icons/light/unstage.svg b/extensions/git/resources/icons/light/unstage.svg deleted file mode 100644 index d12a8ee3135..00000000000 --- a/extensions/git/resources/icons/light/unstage.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index a27fa783fc0..451ff28afca 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -2360,7 +2360,13 @@ export class CommandCenter { title = localize('git.title.diffRefs', '{0} ({1}) ⟷ {0} ({2})', basename, item.shortPreviousRef, item.shortRef); } - return commands.executeCommand('vscode.diff', toGitUri(uri, item.previousRef), item.ref === '' ? uri : toGitUri(uri, item.ref), title); + const options: TextDocumentShowOptions = { + preserveFocus: true, + preview: true, + viewColumn: ViewColumn.Active + }; + + return commands.executeCommand('vscode.diff', toGitUri(uri, item.previousRef), item.ref === '' ? uri : toGitUri(uri, item.ref), title, options); } @command('git.timeline.copyCommitId', { repository: false }) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index ac3120c115a..851036b15f4 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { promises as fs, exists } from 'fs'; +import { promises as fs, exists, realpath } from 'fs'; import * as path from 'path'; import * as os from 'os'; import * as cp from 'child_process'; @@ -21,6 +21,7 @@ import { StringDecoder } from 'string_decoder'; // https://github.com/microsoft/vscode/issues/65693 const MAX_CLI_LENGTH = 30000; +const isWindows = process.platform === 'win32'; export interface IGit { path: string; @@ -45,7 +46,7 @@ interface MutableRemote extends Remote { isReadOnly: boolean; } -// TODO[ECA]: Move to git.d.ts once we are good with the api +// TODO@eamodio: Move to git.d.ts once we are good with the api /** * Log file options. */ @@ -419,8 +420,40 @@ export class Git { async getRepositoryRoot(repositoryPath: string): Promise { const result = await this.exec(repositoryPath, ['rev-parse', '--show-toplevel']); + // Keep trailing spaces which are part of the directory name - return path.normalize(result.stdout.trimLeft().replace(/(\r\n|\r|\n)+$/, '')); + const repoPath = path.normalize(result.stdout.trimLeft().replace(/(\r\n|\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 + const repoUri = Uri.file(repoPath); + const pathUri = Uri.file(repositoryPath); + if (repoUri.authority.length !== 0 && pathUri.authority.length === 0) { + let match = /(?<=^\/?)([a-zA-Z])(?=:\/)/.exec(pathUri.path); + if (match !== null) { + const [, letter] = match; + + try { + const networkPath = await new Promise(resolve => + realpath.native(`${letter}:`, { encoding: 'utf8' }, (err, resolvedPath) => + // eslint-disable-next-line eqeqeq + resolve(err != null ? undefined : resolvedPath), + ), + ); + if (networkPath !== undefined) { + return path.normalize( + repoUri.fsPath.replace(networkPath, `${letter.toLowerCase()}:`), + ); + } + } catch { } + } + + return path.normalize(pathUri.fsPath); + } + } + + return repoPath; } async getRepositoryDotGit(repositoryPath: string): Promise { diff --git a/extensions/git/src/timelineProvider.ts b/extensions/git/src/timelineProvider.ts index 3fe8e306c95..f2701c4be01 100644 --- a/extensions/git/src/timelineProvider.ts +++ b/extensions/git/src/timelineProvider.ts @@ -4,19 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vscode-nls'; -import * as dayjs from 'dayjs'; -import * as advancedFormat from 'dayjs/plugin/advancedFormat'; -import { CancellationToken, Disposable, Event, EventEmitter, ThemeIcon, Timeline, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvider, Uri, workspace } from 'vscode'; +import { CancellationToken, Disposable, env, Event, EventEmitter, ThemeIcon, Timeline, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvider, Uri, workspace } from 'vscode'; import { Model } from './model'; import { Repository, Resource } from './repository'; import { debounce } from './decorators'; -dayjs.extend(advancedFormat); - const localize = nls.loadMessageBundle(); -// TODO[ECA]: Localize or use a setting for date format - export class GitTimelineItem extends TimelineItem { static is(item: TimelineItem): item is GitTimelineItem { return item instanceof GitTimelineItem; @@ -71,21 +65,21 @@ export class GitTimelineProvider implements TimelineProvider { readonly id = 'git-history'; readonly label = localize('git.timeline.source', 'Git History'); - private _disposable: Disposable; + private disposable: Disposable; - private _repo: Repository | undefined; - private _repoDisposable: Disposable | undefined; - private _repoStatusDate: Date | undefined; + private repo: Repository | undefined; + private repoDisposable: Disposable | undefined; + private repoStatusDate: Date | undefined; constructor(private readonly _model: Model) { - this._disposable = Disposable.from( + this.disposable = Disposable.from( _model.onDidOpenRepository(this.onRepositoriesChanged, this), - workspace.registerTimelineProvider('*', this), + workspace.registerTimelineProvider(['file', 'git', 'vscode-remote', 'gitlens-git'], this), ); } dispose() { - this._disposable.dispose(); + this.disposable.dispose(); } async provideTimeline(uri: Uri, options: TimelineOptions, _token: CancellationToken): Promise { @@ -93,33 +87,33 @@ export class GitTimelineProvider implements TimelineProvider { const repo = this._model.getRepository(uri); if (!repo) { - this._repoDisposable?.dispose(); - this._repoStatusDate = undefined; - this._repo = undefined; + this.repoDisposable?.dispose(); + this.repoStatusDate = undefined; + this.repo = undefined; return { items: [] }; } - if (this._repo?.root !== repo.root) { - this._repoDisposable?.dispose(); + if (this.repo?.root !== repo.root) { + this.repoDisposable?.dispose(); - this._repo = repo; - this._repoStatusDate = new Date(); - this._repoDisposable = Disposable.from( + this.repo = repo; + this.repoStatusDate = new Date(); + this.repoDisposable = Disposable.from( repo.onDidChangeRepository(uri => this.onRepositoryChanged(repo, uri)), repo.onDidRunGitStatus(() => this.onRepositoryStatusChanged(repo)) ); } - // TODO[ECA]: Ensure that the uri is a file -- if not we could get the history of the repo? + // TODO@eamodio: Ensure that the uri is a file -- if not we could get the history of the repo? let limit: number | undefined; - if (typeof options.limit === 'string') { + if (options.limit !== undefined && typeof options.limit !== 'number') { try { - const result = await this._model.git.exec(repo.root, ['rev-list', '--count', `${options.limit}..`, '--', uri.fsPath]); + const result = await this._model.git.exec(repo.root, ['rev-list', '--count', `${options.limit.id}..`, '--', uri.fsPath]); if (!result.exitCode) { - // Ask for 1 more than so we can determine if there are more commits - limit = Number(result.stdout) + 1; + // Ask for 2 more (1 for the limit commit and 1 for the next commit) than so we can determine if there are more commits + limit = Number(result.stdout) + 2; } } catch { @@ -130,21 +124,14 @@ export class GitTimelineProvider implements TimelineProvider { limit = options.limit === undefined ? undefined : options.limit + 1; } - const commits = await repo.logFile(uri, { maxEntries: limit, hash: options.cursor, - reverse: options.before, // sortByAuthorDate: true }); - const more = limit === undefined || options.before ? false : commits.length >= limit; const paging = commits.length ? { - more: more, - cursors: { - before: commits[0]?.hash, - after: commits[commits.length - (more ? 1 : 2)]?.hash - } + cursor: limit === undefined ? undefined : (commits.length >= limit ? commits[commits.length - 1]?.hash : undefined) } : undefined; // If we asked for an extra commit, strip it off @@ -152,16 +139,15 @@ export class GitTimelineProvider implements TimelineProvider { commits.splice(commits.length - 1, 1); } - let dateFormatter: dayjs.Dayjs; - const items = commits.map(c => { + const dateFormatter = new Intl.DateTimeFormat(env.language, { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' }); + + const items = commits.map((c, i) => { const date = c.commitDate; // c.authorDate - dateFormatter = dayjs(date); - - const item = new GitTimelineItem(c.hash, `${c.hash}^`, c.message, date?.getTime() ?? 0, c.hash, 'git:file:commit'); + const item = new GitTimelineItem(c.hash, commits[i + 1]?.hash ?? `${c.hash}^`, c.message, date?.getTime() ?? 0, c.hash, 'git:file:commit'); item.iconPath = new (ThemeIcon as any)('git-commit'); item.description = c.authorName; - item.detail = `${c.authorName} (${c.authorEmail}) \u2014 ${c.hash.substr(0, 8)}\n${dateFormatter.format('MMMM Do, YYYY h:mma')}\n\n${c.message}`; + item.detail = `${c.authorName} (${c.authorEmail}) \u2014 ${c.hash.substr(0, 8)}\n${dateFormatter.format(date)}\n\n${c.message}`; item.command = { title: 'Open Comparison', command: 'git.timeline.openDiff', @@ -171,19 +157,18 @@ export class GitTimelineProvider implements TimelineProvider { return item; }); - if (options.cursor === undefined || options.before) { + if (options.cursor === undefined) { const you = localize('git.timeline.you', 'You'); const index = repo.indexGroup.resourceStates.find(r => r.resourceUri.fsPath === uri.fsPath); if (index) { - const date = this._repoStatusDate ?? new Date(); - dateFormatter = dayjs(date); + const date = this.repoStatusDate ?? new Date(); const item = new GitTimelineItem('~', 'HEAD', localize('git.timeline.stagedChanges', 'Staged Changes'), date.getTime(), 'index', 'git:file:index'); - // TODO[ECA]: Replace with a better icon -- reflecting its status maybe? + // TODO@eamodio: Replace with a better icon -- reflecting its status maybe? item.iconPath = new (ThemeIcon as any)('git-commit'); - item.description = you; - item.detail = localize('git.timeline.detail', '{0} \u2014 {1}\n{2}\n\n{3}', you, localize('git.index', 'Index'), dateFormatter.format('MMMM Do, YYYY h:mma'), Resource.getStatusText(index.type)); + item.description = ''; + item.detail = localize('git.timeline.detail', '{0} \u2014 {1}\n{2}\n\n{3}', you, localize('git.index', 'Index'), dateFormatter.format(date), Resource.getStatusText(index.type)); item.command = { title: 'Open Comparison', command: 'git.timeline.openDiff', @@ -196,13 +181,12 @@ export class GitTimelineProvider implements TimelineProvider { const working = repo.workingTreeGroup.resourceStates.find(r => r.resourceUri.fsPath === uri.fsPath); if (working) { const date = new Date(); - dateFormatter = dayjs(date); const item = new GitTimelineItem('', index ? '~' : 'HEAD', localize('git.timeline.uncommitedChanges', 'Uncommited Changes'), date.getTime(), 'working', 'git:file:working'); - // TODO[ECA]: Replace with a better icon -- reflecting its status maybe? + // TODO@eamodio: Replace with a better icon -- reflecting its status maybe? item.iconPath = new (ThemeIcon as any)('git-commit'); - item.description = you; - item.detail = localize('git.timeline.detail', '{0} \u2014 {1}\n{2}\n\n{3}', you, localize('git.workingTree', 'Working Tree'), dateFormatter.format('MMMM Do, YYYY h:mma'), Resource.getStatusText(working.type)); + item.description = ''; + item.detail = localize('git.timeline.detail', '{0} \u2014 {1}\n{2}\n\n{3}', you, localize('git.workingTree', 'Working Tree'), dateFormatter.format(date), Resource.getStatusText(working.type)); item.command = { title: 'Open Comparison', command: 'git.timeline.openDiff', @@ -222,7 +206,7 @@ export class GitTimelineProvider implements TimelineProvider { private onRepositoriesChanged(_repo: Repository) { // console.log(`GitTimelineProvider.onRepositoriesChanged`); - // TODO[ECA]: Being naive for now and just always refreshing each time there is a new repository + // TODO@eamodio: Being naive for now and just always refreshing each time there is a new repository this.fireChanged(); } @@ -236,7 +220,7 @@ export class GitTimelineProvider implements TimelineProvider { // console.log(`GitTimelineProvider.onRepositoryStatusChanged`); // This is crappy, but for now just save the last time a status was run and use that as the timestamp for staged items - this._repoStatusDate = new Date(); + this.repoStatusDate = new Date(); this.fireChanged(); } diff --git a/extensions/git/yarn.lock b/extensions/git/yarn.lock index ac135e43a80..f72600de7bc 100644 --- a/extensions/git/yarn.lock +++ b/extensions/git/yarn.lock @@ -26,10 +26,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.51.tgz#b31d716fb8d58eeb95c068a039b9b6292817d5fb" integrity sha512-El3+WJk2D/ppWNd2X05aiP5l2k4EwF7KwheknQZls+I26eSICoWRhRIJ56jGgw2dqNGQ5LtNajmBU2ajS28EvQ== -"@types/node@^12.11.7": - version "12.11.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a" - integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA== +"@types/node@^12.12.31": + version "12.12.31" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.31.tgz#d6b4f9645fee17f11319b508fb1001797425da51" + integrity sha512-T+wnJno8uh27G9c+1T+a1/WYCHzLeDqtsGJkoEdSp2X8RTh3oOCZQcUnjAx90CS8cmmADX51O0FI/tu9s0yssg== "@types/which@^1.0.28": version "1.0.28" @@ -185,11 +185,6 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -dayjs@1.8.19: - version "1.8.19" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.8.19.tgz#5117dc390d8f8e586d53891dbff3fa308f51abfe" - integrity sha512-7kqOoj3oQSmqbvtvGFLU5iYqies+SqUiEGNT0UtUPPxcPYgY1BrkXR0Cq2R9HYSimBXN+xHkEN4Hi399W+Ovlg== - debug@2.6.8: version "2.6.8" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" diff --git a/extensions/github-authentication/build/postinstall.js b/extensions/github-authentication/build/postinstall.js index e12fd05f191..2f311efc223 100644 --- a/extensions/github-authentication/build/postinstall.js +++ b/extensions/github-authentication/build/postinstall.js @@ -20,7 +20,16 @@ function main() { } } - fs.writeFileSync(path.join(__dirname, '../src/common/config.json'), JSON.stringify(content)); + const githubAppId = process.env.GITHUB_APP_ID; + const githubAppSecret = process.env.GITHUB_APP_SECRET; + + if (githubAppId && githubAppSecret) { + content.GITHUB_APP = { id: githubAppId, secret: githubAppSecret } + } + + if (Object.keys(content).length > 0) { + fs.writeFileSync(path.join(__dirname, '../src/common/config.json'), JSON.stringify(content)); + } } main(); diff --git a/extensions/github-authentication/package.json b/extensions/github-authentication/package.json index 254516ee527..bf2d01cbe3f 100644 --- a/extensions/github-authentication/package.json +++ b/extensions/github-authentication/package.json @@ -22,7 +22,8 @@ "postinstall": "node build/postinstall.js" }, "dependencies": { - "uuid": "^3.3.3" + "uuid": "^3.3.3", + "vscode-nls": "^4.1.2" }, "devDependencies": { "@types/keytar": "^4.4.2", diff --git a/extensions/github-authentication/src/common/clientRegistrar.ts b/extensions/github-authentication/src/common/clientRegistrar.ts index c16c7221ff2..4666ec6a6c0 100644 --- a/extensions/github-authentication/src/common/clientRegistrar.ts +++ b/extensions/github-authentication/src/common/clientRegistrar.ts @@ -3,7 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Uri } from 'vscode'; +import { Uri, env } from 'vscode'; +import * as fs from 'fs'; +import * as path from 'path'; export interface ClientDetails { id?: string; @@ -19,6 +21,8 @@ export interface ClientConfig { VSO: ClientDetails; VSO_PPE: ClientDetails; VSO_DEV: ClientDetails; + + GITHUB_APP: ClientDetails; } export class Registrar { @@ -26,7 +30,8 @@ export class Registrar { constructor() { try { - this._config = require('./config.json') as ClientConfig; + const fileContents = fs.readFileSync(path.join(env.appRoot, 'extensions/github-authentication/src/common/config.json')).toString(); + this._config = JSON.parse(fileContents); } catch (e) { this._config = { OSS: {}, @@ -35,10 +40,20 @@ export class Registrar { EXPLORATION: {}, VSO: {}, VSO_PPE: {}, - VSO_DEV: {} + VSO_DEV: {}, + GITHUB_APP: {} }; } } + + getGitHubAppDetails(): ClientDetails { + if (!this._config.GITHUB_APP.id || !this._config.GITHUB_APP.secret) { + throw new Error(`No GitHub App client configuration available`); + } + + return this._config.GITHUB_APP; + } + getClientDetails(callbackUri: Uri): ClientDetails { let details: ClientDetails | undefined; switch (callbackUri.scheme) { diff --git a/extensions/github-authentication/src/common/keychain.ts b/extensions/github-authentication/src/common/keychain.ts index c687febb442..9a3c0c662d2 100644 --- a/extensions/github-authentication/src/common/keychain.ts +++ b/extensions/github-authentication/src/common/keychain.ts @@ -6,8 +6,11 @@ // keytar depends on a native module shipped in vscode, so this is // how we load it import * as keytarType from 'keytar'; -import { env } from 'vscode'; +import * as vscode from 'vscode'; import Logger from './logger'; +import * as nls from 'vscode-nls'; + +const localize = nls.loadMessageBundle(); function getKeytar(): Keytar | undefined { try { @@ -25,7 +28,7 @@ export type Keytar = { deletePassword: typeof keytarType['deletePassword']; }; -const SERVICE_ID = `${env.uriScheme}-github.login`; +const SERVICE_ID = `${vscode.env.uriScheme}-github.login`; const ACCOUNT_ID = 'account'; export class Keychain { @@ -46,6 +49,11 @@ export class Keychain { } catch (e) { // Ignore Logger.error(`Setting token failed: ${e}`); + const troubleshooting = localize('troubleshooting', "Troubleshooting Guide"); + const result = await vscode.window.showErrorMessage(localize('keychainWriteError', "Writing login information to the keychain failed with error '{0}'.", e.message), troubleshooting); + if (result === troubleshooting) { + vscode.env.openExternal(vscode.Uri.parse('https://code.visualstudio.com/docs/editor/settings-sync#_troubleshooting-keychain-issues')); + } } } diff --git a/extensions/github-authentication/src/extension.ts b/extensions/github-authentication/src/extension.ts index c377e020939..dee2f40bafa 100644 --- a/extensions/github-authentication/src/extension.ts +++ b/extensions/github-authentication/src/extension.ts @@ -20,10 +20,11 @@ export async function activate(context: vscode.ExtensionContext) { displayName: 'GitHub', onDidChangeSessions: onDidChangeSessions.event, getSessions: () => Promise.resolve(loginService.sessions), - login: async (scopes: string[]) => { + login: async (scopeList: string[]) => { try { - const session = await loginService.login(scopes.join(' ')); + const session = await loginService.login(scopeList.join(' ')); Logger.info('Login success!'); + onDidChangeSessions.fire({ added: [session.id], removed: [], changed: [] }); return session; } catch (e) { vscode.window.showErrorMessage(`Sign in failed: ${e}`); @@ -32,7 +33,8 @@ export async function activate(context: vscode.ExtensionContext) { } }, logout: async (id: string) => { - return loginService.logout(id); + await loginService.logout(id); + onDidChangeSessions.fire({ added: [], removed: [id], changed: [] }); } }); diff --git a/extensions/github-authentication/src/github.ts b/extensions/github-authentication/src/github.ts index 52c7c4e333a..6fbe82516ac 100644 --- a/extensions/github-authentication/src/github.ts +++ b/extensions/github-authentication/src/github.ts @@ -4,11 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; +import * as uuid from 'uuid'; import { keychain } from './common/keychain'; import { GitHubServer } from './githubServer'; import Logger from './common/logger'; -export const onDidChangeSessions = new vscode.EventEmitter(); +export const onDidChangeSessions = new vscode.EventEmitter(); interface SessionData { id: string; @@ -29,14 +30,16 @@ export class GitHubAuthenticationProvider { private pollForChange() { setTimeout(async () => { const storedSessions = await this.readSessions(); - let didChange = false; + + const added: string[] = []; + const removed: string[] = []; storedSessions.forEach(session => { const matchesExisting = this._sessions.some(s => s.id === session.id); // Another window added a session to the keychain, add it to our state as well if (!matchesExisting) { this._sessions.push(session); - didChange = true; + added.push(session.id); } }); @@ -49,12 +52,12 @@ export class GitHubAuthenticationProvider { this._sessions.splice(sessionIndex, 1); } - didChange = true; + removed.push(session.id); } }); - if (didChange) { - onDidChangeSessions.fire(); + if (added.length || removed.length) { + onDidChangeSessions.fire({ added, removed, changed: [] }); } this.pollForChange(); @@ -101,16 +104,26 @@ export class GitHubAuthenticationProvider { } public async login(scopes: string): Promise { - const token = await this._githubServer.login(scopes); + const token = scopes === 'vso' ? await this.loginAndInstallApp(scopes) : await this._githubServer.login(scopes); const session = await this.tokenToSession(token, scopes.split(' ')); await this.setToken(session); return session; } + public async loginAndInstallApp(scopes: string): Promise { + const token = await this._githubServer.login(scopes); + const hasUserInstallation = await this._githubServer.hasUserInstallation(token); + if (hasUserInstallation) { + return token; + } else { + return this._githubServer.installApp(); + } + } + private async tokenToSession(token: string, scopes: string[]): Promise { const userInfo = await this._githubServer.getUserInfo(token); return { - id: userInfo.id, + id: uuid(), getAccessToken: () => Promise.resolve(token), accountName: userInfo.accountName, scopes: scopes @@ -130,7 +143,9 @@ export class GitHubAuthenticationProvider { public async logout(id: string) { const sessionIndex = this._sessions.findIndex(session => session.id === id); if (sessionIndex > -1) { - this._sessions.splice(sessionIndex, 1); + const session = this._sessions.splice(sessionIndex, 1)[0]; + const token = await session.getAccessToken(); + await this._githubServer.revokeToken(token); } this.storeSessions(); diff --git a/extensions/github-authentication/src/githubServer.ts b/extensions/github-authentication/src/githubServer.ts index 336a27b2392..5f4d3dfa8c3 100644 --- a/extensions/github-authentication/src/githubServer.ts +++ b/extensions/github-authentication/src/githubServer.ts @@ -71,13 +71,58 @@ export class GitHubServer { Logger.info('Logging in...'); const state = uuid(); const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate`)); - const clientDetails = ClientRegistrar.getClientDetails(callbackUri); + const clientDetails = scopes === 'vso' ? ClientRegistrar.getGitHubAppDetails() : ClientRegistrar.getClientDetails(callbackUri); const uri = vscode.Uri.parse(`https://github.com/login/oauth/authorize?redirect_uri=${encodeURIComponent(callbackUri.toString())}&scope=${scopes}&state=${state}&client_id=${clientDetails.id}`); vscode.env.openExternal(uri); return promiseFromEvent(uriHandler.event, exchangeCodeForToken(state, clientDetails)); } + public async hasUserInstallation(token: string): Promise { + return new Promise((resolve, reject) => { + Logger.info('Getting user installations...'); + const post = https.request({ + host: 'api.github.com', + path: `/user/installations`, + method: 'GET', + headers: { + Accept: 'application/vnd.github.machine-man-preview+json', + Authorization: `token ${token}`, + 'User-Agent': 'Visual-Studio-Code' + } + }, result => { + const buffer: Buffer[] = []; + result.on('data', (chunk: Buffer) => { + buffer.push(chunk); + }); + result.on('end', () => { + if (result.statusCode === 200) { + const json = JSON.parse(Buffer.concat(buffer).toString()); + Logger.info('Got installation info!'); + const hasInstallation = json.installations.some((installation: { app_slug: string }) => installation.app_slug === 'microsoft-visual-studio-code'); + resolve(hasInstallation); + } else { + reject(new Error(result.statusMessage)); + } + }); + }); + + post.end(); + post.on('error', err => { + reject(err); + }); + }); + } + + public async installApp(): Promise { + const clientDetails = ClientRegistrar.getGitHubAppDetails(); + const state = uuid(); + const uri = vscode.Uri.parse(`https://github.com/apps/microsoft-visual-studio-code/installations/new?state=${state}`); + + vscode.env.openExternal(uri); + return promiseFromEvent(uriHandler.event, exchangeCodeForToken(state, clientDetails)); + } + public async getUserInfo(token: string): Promise<{ id: string, accountName: string }> { return new Promise((resolve, reject) => { Logger.info('Getting account info...'); @@ -111,4 +156,46 @@ export class GitHubServer { }); }); } + + public async revokeToken(token: string): Promise { + return new Promise(async (resolve, reject) => { + const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate`)); + const clientDetails = ClientRegistrar.getClientDetails(callbackUri); + const detailsString = `${clientDetails.id}:${clientDetails.secret}`; + + const payload = JSON.stringify({ access_token: token }); + + Logger.info('Revoking token...'); + const post = https.request({ + host: 'api.github.com', + path: `/applications/${clientDetails.id}/token`, + method: 'DELETE', + headers: { + Authorization: `Basic ${Buffer.from(detailsString).toString('base64')}`, + 'User-Agent': 'Visual-Studio-Code', + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(payload) + } + }, result => { + const buffer: Buffer[] = []; + result.on('data', (chunk: Buffer) => { + buffer.push(chunk); + }); + result.on('end', () => { + if (result.statusCode === 204) { + Logger.info('Revoked token!'); + resolve(); + } else { + reject(new Error(result.statusMessage)); + } + }); + }); + + post.write(payload); + post.end(); + post.on('error', err => { + reject(err); + }); + }); + } } diff --git a/extensions/github-authentication/yarn.lock b/extensions/github-authentication/yarn.lock index dcab74a1fea..c1f0b96f5b5 100644 --- a/extensions/github-authentication/yarn.lock +++ b/extensions/github-authentication/yarn.lock @@ -436,6 +436,11 @@ uuid@^3.3.3: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== +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== + which-pm-runs@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" diff --git a/extensions/html-language-features/client/src/htmlMain.ts b/extensions/html-language-features/client/src/htmlMain.ts index c54a97f9b07..567e073f838 100644 --- a/extensions/html-language-features/client/src/htmlMain.ts +++ b/extensions/html-language-features/client/src/htmlMain.ts @@ -21,13 +21,12 @@ import { EMPTY_ELEMENTS } from './htmlEmptyTagsShared'; import { activateTagClosing } from './tagClosing'; import TelemetryReporter from 'vscode-extension-telemetry'; import { getCustomDataPathsInAllWorkspaces, getCustomDataPathsFromAllExtensions } from './customData'; -import { activateMirrorCursor } from './mirrorCursor'; namespace TagCloseRequest { export const type: RequestType = new RequestType('html/tag'); } -namespace MatchingTagPositionRequest { - export const type: RequestType = new RequestType('html/matchingTagPosition'); +namespace OnTypeRenameRequest { + export const type: RequestType = new RequestType('html/onTypeRename'); } // experimental: semantic tokens @@ -131,14 +130,6 @@ export function activate(context: ExtensionContext) { disposable = activateTagClosing(tagRequestor, { html: true, handlebars: true }, 'html.autoClosingTags'); toDispose.push(disposable); - const matchingTagPositionRequestor = (document: TextDocument, position: Position) => { - let param = client.code2ProtocolConverter.asTextDocumentPositionParams(document, position); - return client.sendRequest(MatchingTagPositionRequest.type, param); - }; - - disposable = activateMirrorCursor(matchingTagPositionRequestor, { html: true, handlebars: true }, 'html.mirrorCursorOnMatchingTag'); - toDispose.push(disposable); - disposable = client.onTelemetry(e => { if (telemetryReporter) { telemetryReporter.sendTelemetryEvent(e.key, e.data); @@ -289,6 +280,15 @@ export function activate(context: ExtensionContext) { return results; } }); + + languages.registerOnTypeRenameProvider(documentSelector, { + async provideOnTypeRenameRanges(document, position) { + const param = client.code2ProtocolConverter.asTextDocumentPositionParams(document, position); + const response = await client.sendRequest(OnTypeRenameRequest.type, param); + + return response || []; + } + }); } function getPackageInfo(context: ExtensionContext): IPackageInfo | null { diff --git a/extensions/html-language-features/client/src/mirrorCursor.ts b/extensions/html-language-features/client/src/mirrorCursor.ts deleted file mode 100644 index 5c9ae446087..00000000000 --- a/extensions/html-language-features/client/src/mirrorCursor.ts +++ /dev/null @@ -1,259 +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 { - window, - workspace, - Disposable, - TextDocument, - Position, - TextEditorSelectionChangeEvent, - Selection, - Range, - WorkspaceEdit -} from 'vscode'; - -export function activateMirrorCursor( - matchingTagPositionProvider: (document: TextDocument, position: Position) => Thenable, - supportedLanguages: { [id: string]: boolean }, - configName: string -): Disposable { - let disposables: Disposable[] = []; - - window.onDidChangeTextEditorSelection(event => onDidChangeTextEditorSelection(event), null, disposables); - - let isEnabled = false; - updateEnabledState(); - - window.onDidChangeActiveTextEditor(updateEnabledState, null, disposables); - - function updateEnabledState() { - isEnabled = false; - let editor = window.activeTextEditor; - if (!editor) { - return; - } - let document = editor.document; - if (!supportedLanguages[document.languageId]) { - return; - } - if (!workspace.getConfiguration(undefined, document.uri).get(configName)) { - return; - } - isEnabled = true; - } - - let prevCursors: readonly Selection[] = []; - let cursors: readonly Selection[] = []; - let inMirrorMode = false; - - function onDidChangeTextEditorSelection(event: TextEditorSelectionChangeEvent) { - if (!isEnabled) { - return; - } - - if (event.textEditor.document?.languageId !== 'html' && event.textEditor.document?.languageId !== 'handlebars') { - return; - } - - prevCursors = cursors; - cursors = event.selections; - - if (cursors.length === 1) { - if (inMirrorMode && prevCursors.length === 2) { - if (cursors[0].isEqual(prevCursors[0]) || cursors[0].isEqual(prevCursors[1])) { - return; - } - } - if (event.selections[0].isEmpty) { - matchingTagPositionProvider(event.textEditor.document, event.selections[0].active).then(matchingTagPosition => { - if (matchingTagPosition && window.activeTextEditor) { - const charBeforeAndAfterPositionsRoughlyEqual = isCharBeforeAndAfterPositionsRoughlyEqual( - event.textEditor.document, - event.selections[0].anchor, - new Position(matchingTagPosition.line, matchingTagPosition.character) - ); - - if (charBeforeAndAfterPositionsRoughlyEqual) { - inMirrorMode = true; - const newCursor = new Selection( - matchingTagPosition.line, - matchingTagPosition.character, - matchingTagPosition.line, - matchingTagPosition.character - ); - window.activeTextEditor.selections = [...window.activeTextEditor.selections, newCursor]; - } - } - }); - } - } - - const exitMirrorMode = () => { - inMirrorMode = false; - window.activeTextEditor!.selections = [window.activeTextEditor!.selections[0]]; - }; - - if (cursors.length === 2 && inMirrorMode) { - /** - * Both cursors are positions - */ - if (event.selections[0].isEmpty && event.selections[1].isEmpty) { - if ( - prevCursors.length === 2 && - event.selections[0].anchor.line !== prevCursors[0].anchor.line && - event.selections[1].anchor.line !== prevCursors[0].anchor.line - ) { - exitMirrorMode(); - return; - } - - const charBeforeAndAfterPositionsRoughlyEqual = isCharBeforeAndAfterPositionsRoughlyEqual( - event.textEditor.document, - event.selections[0].anchor, - event.selections[1].anchor - ); - - if (!charBeforeAndAfterPositionsRoughlyEqual) { - exitMirrorMode(); - return; - } else { - // Need to cleanup in the case of
- if ( - shouldDoCleanupForHtmlAttributeInput( - event.textEditor.document, - event.selections[0].anchor, - event.selections[1].anchor - ) - ) { - const cleanupEdit = new WorkspaceEdit(); - const cleanupRange = new Range(event.selections[1].anchor.translate(0, -1), event.selections[1].anchor); - cleanupEdit.replace(event.textEditor.document.uri, cleanupRange, ''); - exitMirrorMode(); - workspace.applyEdit(cleanupEdit); - } - } - } else { - /** - * Both cursors are selections - */ - const charBeforeAndAfterAnchorPositionsRoughlyEqual = isCharBeforeAndAfterPositionsRoughlyEqual( - event.textEditor.document, - event.selections[0].anchor, - event.selections[1].anchor - ); - - const charBeforeAndAfterActivePositionsRoughlyEqual = isCharBeforeAndAfterPositionsRoughlyEqual( - event.textEditor.document, - event.selections[0].active, - event.selections[1].active - ); - - if (!charBeforeAndAfterAnchorPositionsRoughlyEqual || !charBeforeAndAfterActivePositionsRoughlyEqual) { - exitMirrorMode(); - } - } - } - } - - return Disposable.from(...disposables); -} - -function getCharBefore(document: TextDocument, position: Position) { - const offset = document.offsetAt(position); - if (offset === 0) { - return ''; - } - - return document.getText(new Range(document.positionAt(offset - 1), position)); -} - -function getCharAfter(document: TextDocument, position: Position) { - const offset = document.offsetAt(position); - if (offset === document.getText().length) { - return ''; - } - - return document.getText(new Range(position, document.positionAt(offset + 1))); -} - -// Check if chars before and after the two positions are equal -// For the chars before, `<` and `/` are considered equal to handle the case of `<|>` -function isCharBeforeAndAfterPositionsRoughlyEqual(document: TextDocument, firstPos: Position, secondPos: Position) { - const charBeforePrimarySelection = getCharBefore(document, firstPos); - const charAfterPrimarySelection = getCharAfter(document, firstPos); - const charBeforeSecondarySelection = getCharBefore(document, secondPos); - const charAfterSecondarySelection = getCharAfter(document, secondPos); - - /** - * Special case for exiting - * |
- * |
- */ - if ( - charBeforePrimarySelection === ' ' && - charBeforeSecondarySelection === ' ' && - charAfterPrimarySelection === '<' && - charAfterSecondarySelection === '<' - ) { - return false; - } - /** - * Special case for exiting - * |
- * |
- */ - if (charBeforePrimarySelection === '\n' && charBeforeSecondarySelection === '\n') { - return false; - } - /** - * Special case for exiting - *
| - *
| - */ - if (charAfterPrimarySelection === '\n' && charAfterSecondarySelection === '\n') { - return false; - } - - // Exit mirror mode when cursor position no longer mirror - // Unless it's in the case of `<|>` - const charBeforeBothPositionRoughlyEqual = - charBeforePrimarySelection === charBeforeSecondarySelection || - (charBeforePrimarySelection === '/' && charBeforeSecondarySelection === '<') || - (charBeforeSecondarySelection === '/' && charBeforePrimarySelection === '<'); - const charAfterBothPositionRoughlyEqual = - charAfterPrimarySelection === charAfterSecondarySelection || - (charAfterPrimarySelection === ' ' && charAfterSecondarySelection === '>') || - (charAfterSecondarySelection === ' ' && charAfterPrimarySelection === '>'); - - return charBeforeBothPositionRoughlyEqual && charAfterBothPositionRoughlyEqual; -} - -function shouldDoCleanupForHtmlAttributeInput(document: TextDocument, firstPos: Position, secondPos: Position) { - // Need to cleanup in the case of
- const charBeforePrimarySelection = getCharBefore(document, firstPos); - const charAfterPrimarySelection = getCharAfter(document, firstPos); - const charBeforeSecondarySelection = getCharBefore(document, secondPos); - const charAfterSecondarySelection = getCharAfter(document, secondPos); - - const primaryBeforeSecondary = document.offsetAt(firstPos) < document.offsetAt(secondPos); - - /** - * Check two cases - *
- *
- * Before 1st cursor: ` ` - * After 1st cursor: `>` or ` ` - * Before 2nd cursor: ` ` - * After 2nd cursor: `>` - */ - return ( - primaryBeforeSecondary && - charBeforePrimarySelection === ' ' && - (charAfterPrimarySelection === '>' || charAfterPrimarySelection === ' ') && - charBeforeSecondarySelection === ' ' && - charAfterSecondarySelection === '>' - ); -} diff --git a/extensions/html-language-features/package.json b/extensions/html-language-features/package.json index 00289a314a1..b92820fade8 100644 --- a/extensions/html-language-features/package.json +++ b/extensions/html-language-features/package.json @@ -165,7 +165,8 @@ "type": "boolean", "scope": "resource", "default": false, - "description": "%html.mirrorCursorOnMatchingTag%" + "description": "%html.mirrorCursorOnMatchingTag%", + "deprecationMessage": "%html.mirrorCursorOnMatchingTagDeprecationMessage%" }, "html.trace.server": { "type": "string", diff --git a/extensions/html-language-features/package.nls.json b/extensions/html-language-features/package.nls.json index 8fa34d25270..90e4e73f568 100644 --- a/extensions/html-language-features/package.nls.json +++ b/extensions/html-language-features/package.nls.json @@ -25,5 +25,6 @@ "html.validate.scripts": "Controls whether the built-in HTML language support validates embedded scripts.", "html.validate.styles": "Controls whether the built-in HTML language support validates embedded styles.", "html.autoClosingTags": "Enable/disable autoclosing of HTML tags.", - "html.mirrorCursorOnMatchingTag": "Enable/disable mirroring cursor on matching HTML tag." + "html.mirrorCursorOnMatchingTag": "Enable/disable mirroring cursor on matching HTML tag.", + "html.mirrorCursorOnMatchingTagDeprecationMessage": "Deprecated in favor of `editor.renameOnType`" } diff --git a/extensions/html-language-features/server/src/htmlServerMain.ts b/extensions/html-language-features/server/src/htmlServerMain.ts index 9ed3ce0f0a0..78385d54719 100644 --- a/extensions/html-language-features/server/src/htmlServerMain.ts +++ b/extensions/html-language-features/server/src/htmlServerMain.ts @@ -28,8 +28,8 @@ import { SemanticTokenProvider, newSemanticTokenProvider } from './modes/semanti namespace TagCloseRequest { export const type: RequestType = new RequestType('html/tag'); } -namespace MatchingTagPositionRequest { - export const type: RequestType = new RequestType('html/matchingTagPosition'); +namespace OnTypeRenameRequest { + export const type: RequestType = new RequestType('html/onTypeRename'); } // experimental: semantic tokens @@ -499,20 +499,20 @@ connection.onRenameRequest((params, token) => { }, null, `Error while computing rename for ${params.textDocument.uri}`, token); }); -connection.onRequest(MatchingTagPositionRequest.type, (params, token) => { +connection.onRequest(OnTypeRenameRequest.type, (params, token) => { return runSafe(() => { const document = documents.get(params.textDocument.uri); if (document) { const pos = params.position; if (pos.character > 0) { const mode = languageModes.getModeAtPosition(document, Position.create(pos.line, pos.character - 1)); - if (mode && mode.findMatchingTagPosition) { - return mode.findMatchingTagPosition(document, pos); + if (mode && mode.doOnTypeRename) { + return mode.doOnTypeRename(document, pos); } } } return null; - }, null, `Error while computing matching tag position for ${params.textDocument.uri}`, token); + }, null, `Error while computing synced regions for ${params.textDocument.uri}`, token); }); let semanticTokensProvider: SemanticTokenProvider | undefined; diff --git a/extensions/html-language-features/server/src/modes/htmlMode.ts b/extensions/html-language-features/server/src/modes/htmlMode.ts index 251821d1272..b4b72fe7c96 100644 --- a/extensions/html-language-features/server/src/modes/htmlMode.ts +++ b/extensions/html-language-features/server/src/modes/htmlMode.ts @@ -85,6 +85,10 @@ export function getHTMLMode(htmlLanguageService: HTMLLanguageService, workspace: const htmlDocument = htmlDocuments.get(document); return htmlLanguageService.findMatchingTagPosition(document, position, htmlDocument); }, + doOnTypeRename(document: TextDocument, position: Position) { + const htmlDocument = htmlDocuments.get(document); + return htmlLanguageService.findSyncedRegions(document, position, htmlDocument); + }, dispose() { htmlDocuments.dispose(); } diff --git a/extensions/html-language-features/server/src/modes/languageModes.ts b/extensions/html-language-features/server/src/modes/languageModes.ts index 43bbc1f3d6c..624e8de448f 100644 --- a/extensions/html-language-features/server/src/modes/languageModes.ts +++ b/extensions/html-language-features/server/src/modes/languageModes.ts @@ -47,6 +47,7 @@ export interface LanguageMode { doHover?: (document: TextDocument, position: Position) => Hover | null; doSignatureHelp?: (document: TextDocument, position: Position) => SignatureHelp | null; doRename?: (document: TextDocument, position: Position, newName: string) => WorkspaceEdit | null; + doOnTypeRename?: (document: TextDocument, position: Position) => Range[] | null; findDocumentHighlight?: (document: TextDocument, position: Position) => DocumentHighlight[]; findDocumentSymbols?: (document: TextDocument) => SymbolInformation[]; findDocumentLinks?: (document: TextDocument, documentContext: DocumentContext) => DocumentLink[]; diff --git a/extensions/image-preview/src/extension.ts b/extensions/image-preview/src/extension.ts index cbb487e094a..cc4eb798cfe 100644 --- a/extensions/image-preview/src/extension.ts +++ b/extensions/image-preview/src/extension.ts @@ -23,7 +23,7 @@ export function activate(context: vscode.ExtensionContext) { const previewManager = new PreviewManager(extensionRoot, sizeStatusBarEntry, binarySizeStatusBarEntry, zoomStatusBarEntry); - context.subscriptions.push(vscode.window.registerCustomEditorProvider(PreviewManager.viewType, previewManager)); + context.subscriptions.push(vscode.window.registerCustomEditorProvider2(PreviewManager.viewType, previewManager)); context.subscriptions.push(vscode.commands.registerCommand('imagePreview.zoomIn', () => { previewManager.activePreview?.zoomIn(); diff --git a/extensions/image-preview/src/preview.ts b/extensions/image-preview/src/preview.ts index fc43a136a01..815df001baa 100644 --- a/extensions/image-preview/src/preview.ts +++ b/extensions/image-preview/src/preview.ts @@ -27,8 +27,8 @@ export class PreviewManager implements vscode.CustomEditorProvider { private readonly zoomStatusBarEntry: ZoomStatusBarEntry, ) { } - public async resolveCustomDocument(_document: vscode.CustomDocument): Promise { - return {}; + public async openCustomDocument(uri: vscode.Uri) { + return new vscode.CustomDocument(uri); } public async resolveCustomEditor( diff --git a/extensions/javascript/package.json b/extensions/javascript/package.json index b7303520549..b6b0e90da42 100644 --- a/extensions/javascript/package.json +++ b/extensions/javascript/package.json @@ -90,6 +90,34 @@ "path": "./syntaxes/Regular Expressions (JavaScript).tmLanguage" } ], + "semanticTokenScopes": [ + { + "language": "javascript", + "scopes": { + "property": ["variable.other.property.js"], + "property.readonly": ["variable.other.constant.property.js"], + "variable": ["variable.other.readwrite.js"], + "variable.readonly": ["variable.other.constant.object.js"], + "function": ["entity.name.function.js"], + "namespace": ["entity.name.type.module.js"], + "variable.defaultLibrary": ["support.variable.js"], + "function.defaultLibrary": ["support.function.js"] + } + }, + { + "language": "javascriptreact", + "scopes": { + "property": ["variable.other.property.jsx"], + "property.readonly": ["variable.other.constant.property.jsx"], + "variable": ["variable.other.readwrite.jsx"], + "variable.readonly": ["variable.other.constant.object.jsx"], + "function": ["entity.name.function.jsx"], + "namespace": ["entity.name.type.module.jsx"], + "variable.defaultLibrary": ["support.variable.js"], + "function.defaultLibrary": ["support.function.js"] + } + } + ], "snippets": [ { "language": "javascript", diff --git a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json index c18fb6cfef7..40c0a9b33c1 100644 --- a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json +++ b/extensions/javascript/syntaxes/JavaScript.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/TypeScript-TmLanguage/commit/f065e7e88d1c20160c5ec92455aad99a1016284f", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/84422d92e164c379ed817ef8e1d6c35b61de233e", "name": "JavaScript (with React support)", "scopeName": "source.js", "patterns": [ @@ -15,16 +15,19 @@ "include": "#statements" }, { - "name": "comment.line.shebang.ts", - "match": "\\A(#!).*(?=$)", - "captures": { - "1": { - "name": "punctuation.definition.comment.ts" - } - } + "include": "#shebang" } ], "repository": { + "shebang": { + "name": "comment.line.shebang.js", + "match": "\\A(#!).*(?=$)", + "captures": { + "1": { + "name": "punctuation.definition.comment.js" + } + } + }, "statements": { "patterns": [ { @@ -426,7 +429,7 @@ "patterns": [ { "name": "meta.var-single-variable.expr.js", - "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(\\!)?(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(\\!)?(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "beginCaptures": { "1": { "name": "meta.definition.variable.js entity.name.function.js" @@ -484,7 +487,7 @@ "patterns": [ { "name": "meta.var-single-variable.expr.js", - "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "beginCaptures": { "1": { "name": "meta.definition.variable.js variable.other.constant.js entity.name.function.js" @@ -868,7 +871,7 @@ } }, { - "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.js" @@ -1108,7 +1111,7 @@ "include": "#comment" }, { - "match": "(?x)(\\#?[_$[:alpha:]][_$[:alnum:]]*)(?:(\\?)|(\\!))?(?=\\s*\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(\\#?[_$[:alpha:]][_$[:alnum:]]*)(?:(\\?)|(\\!))?(?=\\s*\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "meta.definition.property.js entity.name.function.js" @@ -1431,7 +1434,7 @@ }, { "name": "meta.arrow.js", - "begin": "(?x) (?:\n (? is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", + "begin": "(?x) (?:\n (? is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", "beginCaptures": { "1": { "name": "storage.modifier.async.js" @@ -2625,7 +2628,7 @@ }, { "name": "meta.object.member.js", - "match": "(?x)(?:([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*:(\\s*\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/)*\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*:(\\s*\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/)*\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "0": { "name": "meta.object-literal.key.js" @@ -2688,7 +2691,7 @@ "name": "keyword.control.as.js" } }, - "end": "(?=$|^|[,}]|\\|\\||\\&\\&|((?]|\\|\\||\\&\\&|\\!\\=\\=|$|^|((?\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\()", - "end": "(?<=\\))(?!(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\()", "patterns": [ { - "name": "meta.function-call.js", - "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))", - "end": "(?=\\s*(?:(\\?\\.\\s*)|(\\!))?(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\()", + "begin": "(?=(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?((<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\())", + "end": "(?<=\\))(?!(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?((<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\())", "patterns": [ { - "include": "#support-function-call-identifiers" + "name": "meta.function-call.js", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))", + "end": "(?=\\s*(?:(\\?\\.\\s*)|(\\!))?((<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\())", + "patterns": [ + { + "include": "#function-call-target" + } + ] }, { - "name": "entity.name.function.js", - "match": "(\\#?[_$[:alpha:]][_$[:alnum:]]*)" + "include": "#comment" + }, + { + "include": "#function-call-optionals" + }, + { + "include": "#type-arguments" + }, + { + "include": "#paren-expression" } ] }, { - "include": "#comment" + "begin": "(?=(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))(<\\s*[\\{\\[\\(]\\s*$))", + "end": "(?<=\\>)(?!(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))(<\\s*[\\{\\[\\(]\\s*$))", + "patterns": [ + { + "name": "meta.function-call.js", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))", + "end": "(?=(<\\s*[\\{\\[\\(]\\s*$))", + "patterns": [ + { + "include": "#function-call-target" + } + ] + }, + { + "include": "#comment" + }, + { + "include": "#function-call-optionals" + }, + { + "include": "#type-arguments" + } + ] + } + ] + }, + "function-call-target": { + "patterns": [ + { + "include": "#support-function-call-identifiers" }, + { + "name": "entity.name.function.js", + "match": "(\\#?[_$[:alpha:]][_$[:alnum:]]*)" + } + ] + }, + "function-call-optionals": { + "patterns": [ { "name": "meta.function-call.js punctuation.accessor.optional.js", "match": "\\?\\." @@ -2863,12 +2915,6 @@ { "name": "meta.function-call.js keyword.operator.definiteassignment.js", "match": "\\!" - }, - { - "include": "#type-arguments" - }, - { - "include": "#paren-expression" } ] }, @@ -2900,7 +2946,7 @@ "name": "keyword.operator.new.js" } }, - "end": "(?<=\\))|(?=[;),}\\]:\\-\\+]|\\|\\||\\&\\&|$|((?]|\\|\\||\\&\\&|\\!\\=\\=|$|((?]|\\|\\||\\&\\&|\\!\\=\\=|$|(([\\&\\~\\^\\|]\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s+instanceof(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.)))|((?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.js" @@ -3178,7 +3224,7 @@ "name": "keyword.control.as.js" } }, - "end": "(?=$|^|[;,:})\\]]|\\|\\||\\&\\&|((?]|\\|\\||\\&\\&|\\!\\=\\=|$|((?=|<>|<|>" }, { - "match": "(\\!)\\s*(/)(?![/*])", + "match": "(?<=[_$[:alnum:]])(\\!)\\s*(/)(?![/*])", "captures": { "1": { "name": "keyword.operator.logical.js" @@ -3571,30 +3617,6 @@ } } }, - { - "name": "support.class.node.js", - "match": "(?x)(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", + "match": "(?x)(?:(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*)?([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", "captures": { "1": { "name": "punctuation.accessor.js" @@ -4333,6 +4351,10 @@ "name": "keyword.operator.expression.infer.js", "match": "(?\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?`)", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)(<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?`)", "end": "(?=`)", "patterns": [ { "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*))", - "end": "(?=(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?`)", + "end": "(?=(<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?`)", "patterns": [ { "include": "#support-function-call-identifiers" @@ -4565,7 +4587,7 @@ }, { "name": "string.template.js", - "begin": "([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)`)", + "begin": "([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)`)", "beginCaptures": { "1": { "name": "entity.name.function.tagged-template.js" diff --git a/extensions/javascript/syntaxes/JavaScriptReact.tmLanguage.json b/extensions/javascript/syntaxes/JavaScriptReact.tmLanguage.json index 29cd81e4ab3..ccffc56a2d2 100644 --- a/extensions/javascript/syntaxes/JavaScriptReact.tmLanguage.json +++ b/extensions/javascript/syntaxes/JavaScriptReact.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/TypeScript-TmLanguage/commit/f065e7e88d1c20160c5ec92455aad99a1016284f", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/84422d92e164c379ed817ef8e1d6c35b61de233e", "name": "JavaScript (with React support)", "scopeName": "source.js.jsx", "patterns": [ @@ -15,16 +15,19 @@ "include": "#statements" }, { - "name": "comment.line.shebang.ts", - "match": "\\A(#!).*(?=$)", - "captures": { - "1": { - "name": "punctuation.definition.comment.ts" - } - } + "include": "#shebang" } ], "repository": { + "shebang": { + "name": "comment.line.shebang.js.jsx", + "match": "\\A(#!).*(?=$)", + "captures": { + "1": { + "name": "punctuation.definition.comment.js.jsx" + } + } + }, "statements": { "patterns": [ { @@ -426,7 +429,7 @@ "patterns": [ { "name": "meta.var-single-variable.expr.js.jsx", - "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(\\!)?(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(\\!)?(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "beginCaptures": { "1": { "name": "meta.definition.variable.js.jsx entity.name.function.js.jsx" @@ -484,7 +487,7 @@ "patterns": [ { "name": "meta.var-single-variable.expr.js.jsx", - "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "beginCaptures": { "1": { "name": "meta.definition.variable.js.jsx variable.other.constant.js.jsx entity.name.function.js.jsx" @@ -868,7 +871,7 @@ } }, { - "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.js.jsx" @@ -1108,7 +1111,7 @@ "include": "#comment" }, { - "match": "(?x)(\\#?[_$[:alpha:]][_$[:alnum:]]*)(?:(\\?)|(\\!))?(?=\\s*\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(\\#?[_$[:alpha:]][_$[:alnum:]]*)(?:(\\?)|(\\!))?(?=\\s*\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "meta.definition.property.js.jsx entity.name.function.js.jsx" @@ -1431,7 +1434,7 @@ }, { "name": "meta.arrow.js.jsx", - "begin": "(?x) (?:\n (? is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", + "begin": "(?x) (?:\n (? is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", "beginCaptures": { "1": { "name": "storage.modifier.async.js.jsx" @@ -2625,7 +2628,7 @@ }, { "name": "meta.object.member.js.jsx", - "match": "(?x)(?:([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*:(\\s*\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/)*\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*:(\\s*\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/)*\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "0": { "name": "meta.object-literal.key.js.jsx" @@ -2688,7 +2691,7 @@ "name": "keyword.control.as.js.jsx" } }, - "end": "(?=$|^|[,}]|\\|\\||\\&\\&|((?]|\\|\\||\\&\\&|\\!\\=\\=|$|^|((?\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\()", - "end": "(?<=\\))(?!(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\()", "patterns": [ { - "name": "meta.function-call.js.jsx", - "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))", - "end": "(?=\\s*(?:(\\?\\.\\s*)|(\\!))?(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\()", + "begin": "(?=(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?((<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\())", + "end": "(?<=\\))(?!(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?((<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\())", "patterns": [ { - "include": "#support-function-call-identifiers" + "name": "meta.function-call.js.jsx", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))", + "end": "(?=\\s*(?:(\\?\\.\\s*)|(\\!))?((<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\())", + "patterns": [ + { + "include": "#function-call-target" + } + ] }, { - "name": "entity.name.function.js.jsx", - "match": "(\\#?[_$[:alpha:]][_$[:alnum:]]*)" + "include": "#comment" + }, + { + "include": "#function-call-optionals" + }, + { + "include": "#type-arguments" + }, + { + "include": "#paren-expression" } ] }, { - "include": "#comment" + "begin": "(?=(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))(<\\s*[\\{\\[\\(]\\s*$))", + "end": "(?<=\\>)(?!(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))(<\\s*[\\{\\[\\(]\\s*$))", + "patterns": [ + { + "name": "meta.function-call.js.jsx", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))", + "end": "(?=(<\\s*[\\{\\[\\(]\\s*$))", + "patterns": [ + { + "include": "#function-call-target" + } + ] + }, + { + "include": "#comment" + }, + { + "include": "#function-call-optionals" + }, + { + "include": "#type-arguments" + } + ] + } + ] + }, + "function-call-target": { + "patterns": [ + { + "include": "#support-function-call-identifiers" }, + { + "name": "entity.name.function.js.jsx", + "match": "(\\#?[_$[:alpha:]][_$[:alnum:]]*)" + } + ] + }, + "function-call-optionals": { + "patterns": [ { "name": "meta.function-call.js.jsx punctuation.accessor.optional.js.jsx", "match": "\\?\\." @@ -2863,12 +2915,6 @@ { "name": "meta.function-call.js.jsx keyword.operator.definiteassignment.js.jsx", "match": "\\!" - }, - { - "include": "#type-arguments" - }, - { - "include": "#paren-expression" } ] }, @@ -2900,7 +2946,7 @@ "name": "keyword.operator.new.js.jsx" } }, - "end": "(?<=\\))|(?=[;),}\\]:\\-\\+]|\\|\\||\\&\\&|$|((?]|\\|\\||\\&\\&|\\!\\=\\=|$|((?]|\\|\\||\\&\\&|\\!\\=\\=|$|(([\\&\\~\\^\\|]\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s+instanceof(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.)))|((?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.js.jsx" @@ -3178,7 +3224,7 @@ "name": "keyword.control.as.js.jsx" } }, - "end": "(?=$|^|[;,:})\\]]|\\|\\||\\&\\&|((?]|\\|\\||\\&\\&|\\!\\=\\=|$|((?=|<>|<|>" }, { - "match": "(\\!)\\s*(/)(?![/*])", + "match": "(?<=[_$[:alnum:]])(\\!)\\s*(/)(?![/*])", "captures": { "1": { "name": "keyword.operator.logical.js.jsx" @@ -3571,30 +3617,6 @@ } } }, - { - "name": "support.class.node.js.jsx", - "match": "(?x)(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", + "match": "(?x)(?:(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*)?([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", "captures": { "1": { "name": "punctuation.accessor.js.jsx" @@ -4333,6 +4351,10 @@ "name": "keyword.operator.expression.infer.js.jsx", "match": "(?\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?`)", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)(<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?`)", "end": "(?=`)", "patterns": [ { "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*))", - "end": "(?=(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?`)", + "end": "(?=(<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?`)", "patterns": [ { "include": "#support-function-call-identifiers" @@ -4565,7 +4587,7 @@ }, { "name": "string.template.js.jsx", - "begin": "([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)`)", + "begin": "([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)`)", "beginCaptures": { "1": { "name": "entity.name.function.tagged-template.js.jsx" diff --git a/extensions/json-language-features/client/src/jsonMain.ts b/extensions/json-language-features/client/src/jsonMain.ts index 7b307332241..0f934004cdd 100644 --- a/extensions/json-language-features/client/src/jsonMain.ts +++ b/extensions/json-language-features/client/src/jsonMain.ts @@ -40,8 +40,13 @@ export interface ISchemaAssociations { [pattern: string]: string[]; } +export interface ISchemaAssociation { + fileMatch: string[]; + uri: string; +} + namespace SchemaAssociationNotification { - export const type: NotificationType = new NotificationType('json/schemaAssociations'); + export const type: NotificationType = new NotificationType('json/schemaAssociations'); } namespace ResultLimitReachedNotification { @@ -183,6 +188,9 @@ export function activate(context: ExtensionContext) { // handle content request client.onRequest(VSCodeContentRequest.type, (uriPath: string) => { const uri = Uri.parse(uriPath); + if (uri.scheme === 'untitled') { + return Promise.reject(new Error(localize('untitled.schema', 'Unable to load {0}', uri.toString()))); + } if (uri.scheme !== 'http' && uri.scheme !== 'https') { return workspace.openTextDocument(uri).then(doc => { schemaDocuments[uri.toString()] = true; @@ -264,10 +272,10 @@ export function activate(context: ExtensionContext) { toDispose.push(commands.registerCommand('_json.retryResolveSchema', handleRetryResolveSchemaCommand)); - client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociation(context)); + client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociations(context)); extensions.onDidChange(_ => { - client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociation(context)); + client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociations(context)); }); // manually register / deregister format provider based on the `html.format.enable` setting avoiding issues with late registration. See #71652. @@ -324,8 +332,8 @@ export function deactivate(): Promise { return telemetryReporter ? telemetryReporter.dispose() : Promise.resolve(null); } -function getSchemaAssociation(_context: ExtensionContext): ISchemaAssociations { - const associations: ISchemaAssociations = {}; +function getSchemaAssociations(_context: ExtensionContext): ISchemaAssociation[] { + const associations: ISchemaAssociation[] = []; extensions.all.forEach(extension => { const packageJSON = extension.packageJSON; if (packageJSON && packageJSON.contributes && packageJSON.contributes.jsonValidation) { @@ -333,23 +341,24 @@ function getSchemaAssociation(_context: ExtensionContext): ISchemaAssociations { if (Array.isArray(jsonValidation)) { jsonValidation.forEach(jv => { let { fileMatch, url } = jv; - if (fileMatch && url) { + if (typeof fileMatch === 'string') { + fileMatch = [fileMatch]; + } + if (Array.isArray(fileMatch) && url) { if (url[0] === '.' && url[1] === '/') { url = Uri.file(path.join(extension.extensionPath, url)).toString(); } - if (fileMatch[0] === '%') { - fileMatch = fileMatch.replace(/%APP_SETTINGS_HOME%/, '/User'); - fileMatch = fileMatch.replace(/%MACHINE_SETTINGS_HOME%/, '/Machine'); - fileMatch = fileMatch.replace(/%APP_WORKSPACES_HOME%/, '/Workspaces'); - } else if (fileMatch.charAt(0) !== '/' && !fileMatch.match(/\w+:\/\//)) { - fileMatch = '/' + fileMatch; - } - let association = associations[fileMatch]; - if (!association) { - association = []; - associations[fileMatch] = association; - } - association.push(url); + fileMatch = fileMatch.map(fm => { + if (fm[0] === '%') { + fm = fm.replace(/%APP_SETTINGS_HOME%/, '/User'); + fm = fm.replace(/%MACHINE_SETTINGS_HOME%/, '/Machine'); + fm = fm.replace(/%APP_WORKSPACES_HOME%/, '/Workspaces'); + } else if (!fm.match(/^(\w+:\/\/|\/|!)/)) { + fm = '/' + fm; + } + return fm; + }); + associations.push({ fileMatch, uri: url }); } }); } diff --git a/extensions/json-language-features/package.nls.json b/extensions/json-language-features/package.nls.json index 5d132ccd776..17bc29f9372 100644 --- a/extensions/json-language-features/package.nls.json +++ b/extensions/json-language-features/package.nls.json @@ -3,7 +3,7 @@ "description": "Provides rich language support for JSON files.", "json.schemas.desc": "Associate schemas to JSON files in the current project", "json.schemas.url.desc": "A URL to a schema or a relative path to a schema in the current directory", - "json.schemas.fileMatch.desc": "An array of file patterns to match against when resolving JSON files to schemas.", + "json.schemas.fileMatch.desc": "An array of file patterns to match against when resolving JSON files to schemas. `*` can be used as a wildcard. Exclusion patterns can also be defined and start with '!'. A file matches when there at least one matching pattern and the last matching pattern is not an exclusion pattern.", "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", diff --git a/extensions/json-language-features/server/README.md b/extensions/json-language-features/server/README.md index 43cc04837d5..18e397c5336 100644 --- a/extensions/json-language-features/server/README.md +++ b/extensions/json-language-features/server/README.md @@ -62,7 +62,7 @@ The server supports the following settings: - `format` - `enable`: Whether the server should register the formatting support. This option is only applicable if the client supports *dynamicRegistration* for *rangeFormatting* and `initializationOptions.provideFormatter` is not defined. - `schema`: Configures association of file names to schema URL or schemas and/or associations of schema URL to schema content. - - `fileMatch`: an array of file names or paths (separated by `/`). `*` can be used as a wildcard. + - `fileMatch`: an array of file names or paths (separated by `/`). `*` can be used as a wildcard. Exclusion patterns can also be defined and start with '!'. A file matches when there at least one matching pattern and the last matching pattern is not an exclusion pattern. - `url`: The URL of the schema, optional when also a schema is provided. - `schema`: The schema content. - `resultLimit`: The max number foldig ranges and otline symbols to be computed (for performance reasons) diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index ae5b4a9e073..b66807fddf2 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -14,7 +14,7 @@ "dependencies": { "jsonc-parser": "^2.2.1", "request-light": "^0.2.5", - "vscode-json-languageservice": "^3.5.1", + "vscode-json-languageservice": "^3.5.2", "vscode-languageserver": "^6.1.1", "vscode-uri": "^2.1.1" }, diff --git a/extensions/json-language-features/server/src/jsonServerMain.ts b/extensions/json-language-features/server/src/jsonServerMain.ts index 182eef7e9ee..e339620d1e1 100644 --- a/extensions/json-language-features/server/src/jsonServerMain.ts +++ b/extensions/json-language-features/server/src/jsonServerMain.ts @@ -23,8 +23,13 @@ interface ISchemaAssociations { [pattern: string]: string[]; } +interface ISchemaAssociation { + fileMatch: string[]; + uri: string; +} + namespace SchemaAssociationNotification { - export const type: NotificationType = new NotificationType('json/schemaAssociations'); + export const type: NotificationType = new NotificationType('json/schemaAssociations'); } namespace VSCodeContentRequest { @@ -230,7 +235,7 @@ namespace LimitExceededWarnings { } let jsonConfigurationSettings: JSONSchemaSettings[] | undefined = undefined; -let schemaAssociations: ISchemaAssociations | undefined = undefined; +let schemaAssociations: ISchemaAssociations | ISchemaAssociation[] | undefined = undefined; let formatterRegistration: Thenable | null = null; // The settings have changed. Is send on server activation as well. @@ -291,12 +296,16 @@ function updateConfiguration() { schemas: new Array() }; if (schemaAssociations) { - for (const pattern in schemaAssociations) { - const association = schemaAssociations[pattern]; - if (Array.isArray(association)) { - association.forEach(uri => { - languageSettings.schemas.push({ uri, fileMatch: [pattern] }); - }); + if (Array.isArray(schemaAssociations)) { + Array.prototype.push.apply(languageSettings.schemas, schemaAssociations); + } else { + for (const pattern in schemaAssociations) { + const association = schemaAssociations[pattern]; + if (Array.isArray(association)) { + association.forEach(uri => { + languageSettings.schemas.push({ uri, fileMatch: [pattern] }); + }); + } } } } diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index f6b78986f8f..eb34e96baab 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -80,10 +80,10 @@ request-light@^0.2.5: https-proxy-agent "^2.2.3" vscode-nls "^4.1.1" -vscode-json-languageservice@^3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.5.1.tgz#75779d466107cbc8c4cc9828df100df71c1870f8" - integrity sha512-F8jPqcAC1mbQOMKvGYS4dGEw9JCZxVEi7tc5ASZLfcfwKq2URZKB4fOtdy1GEsTLsrW11tVrBjEPntpXzqp/NA== +vscode-json-languageservice@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.5.2.tgz#4b898140a8e581359c10660845a4cae15dcbb4f9" + integrity sha512-9cUvBq00O08lpWVVOx6tQ1yLxCHss79nsUdEAVYGomRyMbnPBmc0AkYPcXI9WK1EM6HBo0R9Zo3NjFhcICpy4A== dependencies: jsonc-parser "^2.2.1" vscode-languageserver-textdocument "^1.0.1" diff --git a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json index 1babbfd546d..af6042b5594 100644 --- a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json +++ b/extensions/markdown-basics/syntaxes/markdown.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-markdown-tm-grammar/commit/8fbbc11a6bb917f287bbe21d0573454020599547", + "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/7cf9aa7bb76c55428063383610edc0a631230d58", "name": "Markdown", "scopeName": "text.html.markdown", "patterns": [ @@ -2267,7 +2267,7 @@ "name": "meta.other.valid-ampersand.markdown" }, "bold": { - "begin": "(?x) (\\*\\*(?=\\w)|(?]*+> # HTML tags\n | (?`+)([^`]|(?!(?(?!`))`)*+\\k\n # Raw\n | \\\\[\\\\`*_{}\\[\\]()#.!+\\->]?+ # Escapes\n | \\[\n (\n (? # Named group\n [^\\[\\]\\\\] # Match most chars\n | \\\\. # Escaped chars\n | \\[ \\g*+ \\] # Nested brackets\n )*+\n \\]\n (\n ( # Reference Link\n [ ]? # Optional space\n \\[[^\\]]*+\\] # Ref name\n )\n | ( # Inline Link\n \\( # Opening paren\n [ \\t]*+ # Optional whitespace\n ? # URL\n [ \\t]*+ # Optional whitespace\n ( # Optional Title\n (?['\"])\n (.*?)\n \\k<title>\n )?\n \\)\n )\n )\n )\n | (?!(?<=\\S)\\1). # Everything besides\n # style closer\n )++\n (?<=\\S)(?=__\\b|\\*\\*)\\1 # Close\n)\n", + "begin": "(?x) (?<open>(\\*\\*(?=\\w)|(?<!\\w)\\*\\*|(?<!\\w)\\b__))(?=\\S) (?=\n (\n <[^>]*+> # HTML tags\n | (?<raw>`+)([^`]|(?!(?<!`)\\k<raw>(?!`))`)*+\\k<raw>\n # Raw\n | \\\\[\\\\`*_{}\\[\\]()#.!+\\->]?+ # Escapes\n | \\[\n (\n (?<square> # Named group\n [^\\[\\]\\\\] # Match most chars\n | \\\\. # Escaped chars\n | \\[ \\g<square>*+ \\] # Nested brackets\n )*+\n \\]\n (\n ( # Reference Link\n [ ]? # Optional space\n \\[[^\\]]*+\\] # Ref name\n )\n | ( # Inline Link\n \\( # Opening paren\n [ \\t]*+ # Optional whitespace\n <?(.*?)>? # URL\n [ \\t]*+ # Optional whitespace\n ( # Optional Title\n (?<title>['\"])\n (.*?)\n \\k<title>\n )?\n \\)\n )\n )\n )\n | (?!(?<=\\S)\\k<open>). # Everything besides\n # style closer\n )++\n (?<=\\S)(?=__\\b|\\*\\*)\\k<open> # Close\n)\n", "captures": { "1": { "name": "punctuation.definition.bold.markdown" @@ -2412,7 +2412,7 @@ "name": "meta.image.reference.markdown" }, "italic": { - "begin": "(?x) (\\*(?=\\w)|(?<!\\w)\\*|(?<!\\w)\\b_)(?=\\S) # Open\n (?=\n (\n <[^>]*+> # HTML tags\n | (?<raw>`+)([^`]|(?!(?<!`)\\k<raw>(?!`))`)*+\\k<raw>\n # Raw\n | \\\\[\\\\`*_{}\\[\\]()#.!+\\->]?+ # Escapes\n | \\[\n (\n (?<square> # Named group\n [^\\[\\]\\\\] # Match most chars\n | \\\\. # Escaped chars\n | \\[ \\g<square>*+ \\] # Nested brackets\n )*+\n \\]\n (\n ( # Reference Link\n [ ]? # Optional space\n \\[[^\\]]*+\\] # Ref name\n )\n | ( # Inline Link\n \\( # Opening paren\n [ \\t]*+ # Optional whtiespace\n <?(.*?)>? # URL\n [ \\t]*+ # Optional whtiespace\n ( # Optional Title\n (?<title>['\"])\n (.*?)\n \\k<title>\n )?\n \\)\n )\n )\n )\n | \\1\\1 # Must be bold closer\n | (?!(?<=\\S)\\1). # Everything besides\n # style closer\n )++\n (?<=\\S)(?=_\\b|\\*)\\1 # Close\n )\n", + "begin": "(?x) (?<open>(\\*(?=\\w)|(?<!\\w)\\*|(?<!\\w)\\b_))(?=\\S) # Open\n (?=\n (\n <[^>]*+> # HTML tags\n | (?<raw>`+)([^`]|(?!(?<!`)\\k<raw>(?!`))`)*+\\k<raw>\n # Raw\n | \\\\[\\\\`*_{}\\[\\]()#.!+\\->]?+ # Escapes\n | \\[\n (\n (?<square> # Named group\n [^\\[\\]\\\\] # Match most chars\n | \\\\. # Escaped chars\n | \\[ \\g<square>*+ \\] # Nested brackets\n )*+\n \\]\n (\n ( # Reference Link\n [ ]? # Optional space\n \\[[^\\]]*+\\] # Ref name\n )\n | ( # Inline Link\n \\( # Opening paren\n [ \\t]*+ # Optional whtiespace\n <?(.*?)>? # URL\n [ \\t]*+ # Optional whtiespace\n ( # Optional Title\n (?<title>['\"])\n (.*?)\n \\k<title>\n )?\n \\)\n )\n )\n )\n | \\k<open>\\k<open> # Must be bold closer\n | (?!(?<=\\S)\\k<open>). # Everything besides\n # style closer\n )++\n (?<=\\S)(?=_\\b|\\*)\\k<open> # Close\n )\n", "captures": { "1": { "name": "punctuation.definition.italic.markdown" diff --git a/extensions/markdown-language-features/src/features/foldingProvider.ts b/extensions/markdown-language-features/src/features/foldingProvider.ts index 4850b3759de..590a7a1b246 100644 --- a/extensions/markdown-language-features/src/features/foldingProvider.ts +++ b/extensions/markdown-language-features/src/features/foldingProvider.ts @@ -77,6 +77,9 @@ export default class MarkdownFoldingProvider implements vscode.FoldingRangeProvi return token.map[1] > token.map[0]; case 'html_block': + if (isRegionMarker(token)) { + return false; + } return token.map[1] > token.map[0] + 1; default: @@ -92,7 +95,7 @@ export default class MarkdownFoldingProvider implements vscode.FoldingRangeProvi if (document.lineAt(end).isEmptyOrWhitespace && end >= start + 1) { end = end - 1; } - return new vscode.FoldingRange(start, end); + return new vscode.FoldingRange(start, end, listItem.type === 'html_block' && listItem.content.startsWith('<!--') ? vscode.FoldingRangeKind.Comment : undefined); }); } } diff --git a/extensions/markdown-language-features/src/features/preview.ts b/extensions/markdown-language-features/src/features/preview.ts index ad8edc1c2ad..2b71b348f4d 100644 --- a/extensions/markdown-language-features/src/features/preview.ts +++ b/extensions/markdown-language-features/src/features/preview.ts @@ -457,7 +457,10 @@ export class DynamicMarkdownPreview extends Disposable { const folder = vscode.workspace.getWorkspaceFolder(base); if (folder) { - baseRoots.push(folder.uri); + const workspaceRoots = vscode.workspace.workspaceFolders?.map(folder => folder.uri); + if (workspaceRoots) { + baseRoots.push(...workspaceRoots); + } } else if (!base.scheme || base.scheme === 'file') { baseRoots.push(vscode.Uri.file(path.dirname(base.fsPath))); } diff --git a/extensions/markdown-language-features/src/features/previewManager.ts b/extensions/markdown-language-features/src/features/previewManager.ts index 68d68118309..98c3c9a776a 100644 --- a/extensions/markdown-language-features/src/features/previewManager.ts +++ b/extensions/markdown-language-features/src/features/previewManager.ts @@ -52,7 +52,7 @@ class PreviewStore extends Disposable { } } -export class MarkdownPreviewManager extends Disposable implements vscode.WebviewPanelSerializer, vscode.CustomEditorProvider { +export class MarkdownPreviewManager extends Disposable implements vscode.WebviewPanelSerializer, vscode.CustomTextEditorProvider { private static readonly markdownPreviewActiveContextKey = 'markdownPreviewFocus'; private readonly _topmostLineMonitor = new TopmostLineMonitor(); @@ -63,6 +63,8 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview private _activePreview: DynamicMarkdownPreview | undefined = undefined; + private readonly customEditorViewType = 'vscode.markdown.preview.editor'; + public constructor( private readonly _contentProvider: MarkdownContentProvider, private readonly _logger: Logger, @@ -70,7 +72,7 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview ) { super(); this._register(vscode.window.registerWebviewPanelSerializer(DynamicMarkdownPreview.viewType, this)); - this._register(vscode.window.registerCustomEditorProvider('vscode.markdown.preview.editor', this)); + this._register(vscode.window.registerCustomEditorProvider(this.customEditorViewType, this)); } public refresh() { @@ -148,12 +150,12 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview this.registerDynamicPreview(preview); } - public async resolveCustomDocument(_document: vscode.CustomDocument): Promise<vscode.CustomEditorCapabilities> { - return {}; + public async openCustomDocument(uri: vscode.Uri) { + return new vscode.CustomDocument(uri); } - public async resolveCustomEditor( - document: vscode.CustomDocument, + public async resolveCustomTextEditor( + document: vscode.TextDocument, webview: vscode.WebviewPanel ): Promise<void> { const preview = DynamicMarkdownPreview.revive( diff --git a/extensions/markdown-language-features/src/telemetryReporter.ts b/extensions/markdown-language-features/src/telemetryReporter.ts index d00dca386d1..1104332512d 100644 --- a/extensions/markdown-language-features/src/telemetryReporter.ts +++ b/extensions/markdown-language-features/src/telemetryReporter.ts @@ -48,12 +48,12 @@ export function loadDefaultTelemetryReporter(): TelemetryReporter { } function getPackageInfo(): IPackageInfo | null { - const extention = vscode.extensions.getExtension('Microsoft.vscode-markdown'); - if (extention && extention.packageJSON) { + const extension = vscode.extensions.getExtension('Microsoft.vscode-markdown'); + if (extension && extension.packageJSON) { return { - name: extention.packageJSON.name, - version: extention.packageJSON.version, - aiKey: extention.packageJSON.aiKey + name: extension.packageJSON.name, + version: extension.packageJSON.version, + aiKey: extension.packageJSON.aiKey }; } return null; diff --git a/extensions/markdown-language-features/src/test/foldingProvider.test.ts b/extensions/markdown-language-features/src/test/foldingProvider.test.ts index 2ad8444d967..11a4c60752d 100644 --- a/extensions/markdown-language-features/src/test/foldingProvider.test.ts +++ b/extensions/markdown-language-features/src/test/foldingProvider.test.ts @@ -175,6 +175,18 @@ a`); assert.strictEqual(firstFold.start, 1); assert.strictEqual(firstFold.end, 3); }); + + test('Should fold html block comments', async () => { + const folds = await getFoldsForDocument(`x +<!-- +fa +-->`); + 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); + }); }); diff --git a/extensions/markdown-language-features/yarn.lock b/extensions/markdown-language-features/yarn.lock index 5c7a22c74f0..23cd1744005 100644 --- a/extensions/markdown-language-features/yarn.lock +++ b/extensions/markdown-language-features/yarn.lock @@ -191,9 +191,9 @@ abbrev@1: integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== acorn@^6.2.1: - version "6.3.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.3.0.tgz#0087509119ffa4fc0a0041d1e93a417e68cb856e" - integrity sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA== + version "6.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" + integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== ajv-errors@^1.0.0: version "1.0.1" diff --git a/extensions/merge-conflict/src/documentTracker.ts b/extensions/merge-conflict/src/documentTracker.ts index 41be7a803e2..74a7d9dfe99 100644 --- a/extensions/merge-conflict/src/documentTracker.ts +++ b/extensions/merge-conflict/src/documentTracker.ts @@ -49,7 +49,7 @@ class OriginDocumentMergeConflictTracker implements interfaces.IDocumentMergeCon export default class DocumentMergeConflictTracker implements vscode.Disposable, interfaces.IDocumentMergeConflictTrackerService { private cache: Map<string, ScanTask> = new Map(); - private delayExpireTime: number = 250; + private delayExpireTime: number = 0; getConflicts(document: vscode.TextDocument, origin: string): PromiseLike<interfaces.IDocumentMergeConflict[]> { // Attempt from cache diff --git a/extensions/npm/package.json b/extensions/npm/package.json index 79a95f7fc1b..265dffcacf7 100644 --- a/extensions/npm/package.json +++ b/extensions/npm/package.json @@ -42,6 +42,12 @@ "filenames": [ ".npmignore" ] + }, + { + "id": "properties", + "filenames": [ + ".npmrc" + ] } ], "views": { @@ -57,18 +63,12 @@ { "command": "npm.runScript", "title": "%command.run%", - "icon": { - "light": "resources/light/continue.svg", - "dark": "resources/dark/continue.svg" - } + "icon": "$(run)" }, { "command": "npm.debugScript", "title": "%command.debug%", - "icon": { - "light": "resources/light/debug.svg", - "dark": "resources/dark/debug.svg" - } + "icon": "$(debug)" }, { "command": "npm.openScript", @@ -81,10 +81,7 @@ { "command": "npm.refresh", "title": "%command.refresh%", - "icon": { - "light": "resources/light/refresh.svg", - "dark": "resources/dark/refresh.svg" - } + "icon": "$(refresh)" }, { "command": "npm.runSelectedScript", diff --git a/extensions/npm/resources/dark/continue.svg b/extensions/npm/resources/dark/continue.svg deleted file mode 100644 index 8b0a58eca9b..00000000000 --- a/extensions/npm/resources/dark/continue.svg +++ /dev/null @@ -1,3 +0,0 @@ -<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path fill-rule="evenodd" clip-rule="evenodd" d="M4 2V14.4805L12.9146 8.24024L4 2ZM11.1809 8.24024L4.995 12.5684V3.91209L11.1809 8.24024Z" fill="#C5C5C5"/> -</svg> diff --git a/extensions/npm/resources/dark/debug.svg b/extensions/npm/resources/dark/debug.svg deleted file mode 100644 index e4c1b7a927b..00000000000 --- a/extensions/npm/resources/dark/debug.svg +++ /dev/null @@ -1,5 +0,0 @@ -<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path d="M3.01996 8.50166L3.01725 8.46085C3.01812 8.47446 3.01903 8.48807 3.01996 8.50166Z" fill="#C5C5C5"/> -<path d="M7.2577 8.90466L5.4876 7.12558L6.07922 6.53397L7.95097 8.41518L9.79605 6.57011L10.3877 7.16171L8.61769 8.93167L10.3878 10.7108L9.79619 11.3024L7.92445 9.42114L6.07936 11.2662L5.48775 10.6746L7.2577 8.90466Z" fill="#C5C5C5"/> -<path fill-rule="evenodd" clip-rule="evenodd" d="M10.8775 3.91833V4.5H11.6624L13.3203 2.8421L13.9119 3.43371L12.3421 5.00354L12.361 5.05303C12.6914 5.91515 12.8775 6.88815 12.8775 7.91833C12.8775 8.11403 12.8708 8.30766 12.8576 8.49886L12.8546 8.5425H14.92V9.37916H12.7495L12.7434 9.41265C12.567 10.3771 12.2239 11.2564 11.7565 11.9964L11.7218 12.0515L13.7182 14.058L13.1251 14.6481L11.2153 12.7287L11.1576 12.7932C10.2949 13.7582 9.17697 14.3367 7.95917 14.3367C6.72251 14.3367 5.58881 13.7401 4.72075 12.748L4.66326 12.6823L2.79157 14.554L2.19995 13.9624L4.16317 11.9992L4.12918 11.9442C3.6785 11.2152 3.34718 10.3545 3.17494 9.41265L3.16882 9.37916H1V8.5425H3.0637L3.0607 8.49886C3.04755 8.30766 3.04084 8.11403 3.04084 7.91833C3.04084 6.90159 3.22212 5.94055 3.54446 5.08683L3.56303 5.03764L1.95216 3.41862L2.54527 2.8285L4.20835 4.5H5.04084V3.91833C5.04084 2.30658 6.34742 1 7.95917 1C9.57092 1 10.8775 2.30658 10.8775 3.91833ZM5.87751 3.91833V4.5H10.0408V3.91833C10.0408 2.76866 9.10884 1.83667 7.95917 1.83667C6.8095 1.83667 5.87751 2.76866 5.87751 3.91833ZM11.5938 5.38957L11.5739 5.33667H4.34441L4.32451 5.38957C4.0411 6.1427 3.8775 7.00011 3.8775 7.91833C3.8775 9.52826 4.38048 10.9522 5.15153 11.9546C5.9219 12.9561 6.9225 13.5 7.95917 13.5C8.99584 13.5 9.99644 12.9561 10.7668 11.9546C11.5379 10.9522 12.0408 9.52826 12.0408 7.91833C12.0408 7.00011 11.8772 6.14269 11.5938 5.38957Z" fill="#C5C5C5"/> -</svg> diff --git a/extensions/npm/resources/dark/prepostscript.svg b/extensions/npm/resources/dark/prepostscript.svg deleted file mode 100644 index a8c87f2d8f6..00000000000 --- a/extensions/npm/resources/dark/prepostscript.svg +++ /dev/null @@ -1,5 +0,0 @@ -<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> -<g opacity="0.5"> -<path d="M2.80723 14.9754C2.57119 14.9721 2.33826 14.9211 2.12247 14.8254C1.90667 14.7297 1.71248 14.5913 1.55158 14.4186C1.2385 14.1334 1.04433 13.7408 1.00775 13.3189C0.966225 12.8828 1.09269 12.4473 1.36133 12.1013C2.56779 10.8289 4.9473 8.4494 6.67811 6.75479C6.30983 5.75887 6.32704 4.66127 6.72637 3.67739C7.05474 2.85876 7.63869 2.16805 8.39129 1.70807C8.9817 1.31706 9.66031 1.07944 10.3657 1.01673C11.0711 0.954022 11.7809 1.06819 12.4311 1.34892L13.0482 1.6162L10.1824 4.56738L11.4371 5.82582L14.3809 2.94887L14.6482 3.56788C14.8735 4.08976 14.993 4.65119 14.9997 5.21961C15.0064 5.78802 14.9002 6.35211 14.6872 6.87915C14.476 7.40029 14.1623 7.87368 13.7647 8.27122C13.5394 8.49169 13.2904 8.68653 13.0222 8.85218C12.4673 9.22275 11.8324 9.45636 11.1697 9.5338C10.5069 9.61124 9.83521 9.5303 9.20982 9.29764C8.11194 10.4113 5.37142 13.1704 3.89119 14.5522C3.59426 14.8219 3.20832 14.9726 2.80723 14.9754ZM10.7448 1.92802C10.087 1.92637 9.44359 2.12018 8.89614 2.48485C8.68265 2.6152 8.48437 2.76897 8.30498 2.9433C7.82789 3.42423 7.50926 4.03953 7.39182 4.70669C7.27437 5.37385 7.36374 6.06098 7.64792 6.67591L7.78342 6.97288L7.55048 7.20025C5.81224 8.89672 3.28146 11.4201 2.06479 12.7045C1.95646 12.8658 1.91012 13.0608 1.93435 13.2535C1.95857 13.4463 2.05171 13.6238 2.19657 13.7532C2.28005 13.8462 2.38177 13.9211 2.49541 13.9731C2.59557 14.0184 2.70383 14.043 2.81373 14.0455C2.98064 14.0413 3.14044 13.977 3.26383 13.8646C4.83687 12.3964 7.87622 9.32641 8.76807 8.42435L8.9973 8.19326L9.29242 8.32783C9.80618 8.56732 10.3731 8.66985 10.9382 8.62545C11.5033 8.58106 12.0473 8.39125 12.5174 8.07447C12.7313 7.9426 12.9296 7.78694 13.1085 7.61045C13.4183 7.30153 13.6631 6.93374 13.8286 6.52874C13.994 6.12375 14.0767 5.68974 14.0719 5.25228C14.0719 5.03662 14.0505 4.82148 14.0078 4.61007L11.4306 7.12508L8.87944 4.57759L11.3944 1.98834C11.1804 1.94674 10.9628 1.92653 10.7448 1.92802Z" fill="#C5C5C5"/> -</g> -</svg> diff --git a/extensions/npm/resources/dark/refresh.svg b/extensions/npm/resources/dark/refresh.svg deleted file mode 100644 index ec0c43f0bc3..00000000000 --- a/extensions/npm/resources/dark/refresh.svg +++ /dev/null @@ -1,4 +0,0 @@ -<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path fill-rule="evenodd" clip-rule="evenodd" d="M5.56253 2.51574C3.46348 3.45007 2 5.55411 2 7.99996C2 11.3137 4.68629 14 8 14C11.3137 14 14 11.3137 14 7.99996C14 5.32516 12.2497 3.05916 9.83199 2.28479L9.52968 3.23829C11.5429 3.88451 13 5.77207 13 7.99996C13 10.7614 10.7614 13 8 13C5.23858 13 3 10.7614 3 7.99996C3 6.31101 3.83742 4.81764 5.11969 3.91242L5.56253 2.51574Z" fill="#C5C5C5"/> -<path fill-rule="evenodd" clip-rule="evenodd" d="M5 3H2V2H5.5L6 2.5V6H5V3Z" fill="#C5C5C5"/> -</svg> diff --git a/extensions/npm/resources/dark/script.svg b/extensions/npm/resources/dark/script.svg deleted file mode 100644 index 7137a9d7bb5..00000000000 --- a/extensions/npm/resources/dark/script.svg +++ /dev/null @@ -1,3 +0,0 @@ -<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path d="M2.80723 14.9754C2.57119 14.9721 2.33826 14.9211 2.12247 14.8254C1.90667 14.7297 1.71248 14.5913 1.55158 14.4186C1.2385 14.1334 1.04433 13.7408 1.00775 13.3189C0.966225 12.8828 1.09269 12.4473 1.36133 12.1013C2.56779 10.8289 4.9473 8.4494 6.67811 6.75479C6.30983 5.75887 6.32704 4.66127 6.72637 3.67739C7.05474 2.85876 7.63869 2.16805 8.39129 1.70807C8.9817 1.31706 9.66031 1.07944 10.3657 1.01673C11.0711 0.954022 11.7809 1.06819 12.4311 1.34892L13.0482 1.6162L10.1824 4.56738L11.4371 5.82582L14.3809 2.94887L14.6482 3.56788C14.8735 4.08976 14.993 4.65119 14.9997 5.21961C15.0064 5.78802 14.9002 6.35211 14.6872 6.87915C14.476 7.40029 14.1623 7.87368 13.7647 8.27122C13.5394 8.49169 13.2904 8.68653 13.0222 8.85218C12.4673 9.22275 11.8324 9.45636 11.1697 9.5338C10.5069 9.61124 9.83521 9.5303 9.20982 9.29764C8.11194 10.4113 5.37142 13.1704 3.89119 14.5522C3.59426 14.8219 3.20832 14.9726 2.80723 14.9754ZM10.7448 1.92802C10.087 1.92637 9.44359 2.12018 8.89614 2.48485C8.68265 2.6152 8.48437 2.76897 8.30498 2.9433C7.82789 3.42423 7.50926 4.03953 7.39182 4.70669C7.27438 5.37385 7.36374 6.06098 7.64792 6.67591L7.78342 6.97288L7.55048 7.20025C5.81224 8.89672 3.28146 11.4201 2.06479 12.7045C1.95646 12.8658 1.91012 13.0608 1.93435 13.2535C1.95857 13.4463 2.05171 13.6238 2.19657 13.7532C2.28005 13.8462 2.38177 13.9211 2.49541 13.9731C2.59557 14.0184 2.70383 14.043 2.81373 14.0455C2.98064 14.0413 3.14044 13.977 3.26383 13.8646C4.83687 12.3964 7.87622 9.32641 8.76807 8.42435L8.9973 8.19326L9.29242 8.32783C9.80617 8.56732 10.3731 8.66985 10.9382 8.62545C11.5033 8.58106 12.0473 8.39125 12.5174 8.07447C12.7313 7.9426 12.9296 7.78694 13.1085 7.61045C13.4183 7.30153 13.6631 6.93374 13.8286 6.52874C13.994 6.12375 14.0767 5.68974 14.0719 5.25228C14.0719 5.03662 14.0505 4.82148 14.0078 4.61007L11.4306 7.12508L8.87944 4.57759L11.3944 1.98834C11.1804 1.94674 10.9628 1.92653 10.7448 1.92802Z" fill="#C5C5C5"/> -</svg> diff --git a/extensions/npm/resources/light/continue.svg b/extensions/npm/resources/light/continue.svg deleted file mode 100644 index 2563bfa114b..00000000000 --- a/extensions/npm/resources/light/continue.svg +++ /dev/null @@ -1,3 +0,0 @@ -<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path fill-rule="evenodd" clip-rule="evenodd" d="M4 2V14.4805L12.9146 8.24024L4 2ZM11.1809 8.24024L4.995 12.5684V3.91209L11.1809 8.24024Z" fill="#424242"/> -</svg> diff --git a/extensions/npm/resources/light/debug.svg b/extensions/npm/resources/light/debug.svg deleted file mode 100644 index 81a5ffb6b11..00000000000 --- a/extensions/npm/resources/light/debug.svg +++ /dev/null @@ -1,5 +0,0 @@ -<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path d="M3.01996 8.50166L3.01725 8.46085C3.01812 8.47446 3.01903 8.48807 3.01996 8.50166Z" fill="#424242"/> -<path d="M7.2577 8.90466L5.4876 7.12558L6.07922 6.53397L7.95097 8.41518L9.79605 6.57011L10.3877 7.16171L8.61769 8.93167L10.3878 10.7108L9.79619 11.3024L7.92445 9.42114L6.07936 11.2662L5.48775 10.6746L7.2577 8.90466Z" fill="#424242"/> -<path fill-rule="evenodd" clip-rule="evenodd" d="M10.8775 3.91833V4.5H11.6624L13.3203 2.8421L13.9119 3.43371L12.3421 5.00354L12.361 5.05303C12.6914 5.91515 12.8775 6.88815 12.8775 7.91833C12.8775 8.11403 12.8708 8.30766 12.8576 8.49886L12.8546 8.5425H14.92V9.37916H12.7495L12.7434 9.41265C12.567 10.3771 12.2239 11.2564 11.7565 11.9964L11.7218 12.0515L13.7182 14.058L13.1251 14.6481L11.2153 12.7287L11.1576 12.7932C10.2949 13.7582 9.17697 14.3367 7.95917 14.3367C6.72251 14.3367 5.58881 13.7401 4.72075 12.748L4.66326 12.6823L2.79157 14.554L2.19995 13.9624L4.16317 11.9992L4.12918 11.9442C3.6785 11.2152 3.34718 10.3545 3.17494 9.41265L3.16882 9.37916H1V8.5425H3.0637L3.0607 8.49886C3.04755 8.30766 3.04084 8.11403 3.04084 7.91833C3.04084 6.90159 3.22212 5.94055 3.54446 5.08683L3.56303 5.03764L1.95216 3.41862L2.54527 2.8285L4.20835 4.5H5.04084V3.91833C5.04084 2.30658 6.34742 1 7.95917 1C9.57092 1 10.8775 2.30658 10.8775 3.91833ZM5.87751 3.91833V4.5H10.0408V3.91833C10.0408 2.76866 9.10884 1.83667 7.95917 1.83667C6.8095 1.83667 5.87751 2.76866 5.87751 3.91833ZM11.5938 5.38957L11.5739 5.33667H4.34441L4.32451 5.38957C4.0411 6.1427 3.8775 7.00011 3.8775 7.91833C3.8775 9.52826 4.38048 10.9522 5.15153 11.9546C5.9219 12.9561 6.9225 13.5 7.95917 13.5C8.99584 13.5 9.99644 12.9561 10.7668 11.9546C11.5379 10.9522 12.0408 9.52826 12.0408 7.91833C12.0408 7.00011 11.8772 6.14269 11.5938 5.38957Z" fill="#424242"/> -</svg> diff --git a/extensions/npm/resources/light/prepostscript.svg b/extensions/npm/resources/light/prepostscript.svg deleted file mode 100644 index 87eb59e12a6..00000000000 --- a/extensions/npm/resources/light/prepostscript.svg +++ /dev/null @@ -1,5 +0,0 @@ -<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> -<g opacity="0.5"> -<path d="M2.80723 14.9754C2.57119 14.9721 2.33826 14.9211 2.12247 14.8254C1.90667 14.7297 1.71248 14.5913 1.55158 14.4186C1.2385 14.1334 1.04433 13.7408 1.00775 13.3189C0.966225 12.8828 1.09269 12.4473 1.36133 12.1013C2.56779 10.8289 4.9473 8.4494 6.67811 6.75479C6.30983 5.75887 6.32704 4.66127 6.72637 3.67739C7.05474 2.85876 7.63869 2.16805 8.39129 1.70807C8.9817 1.31706 9.66031 1.07944 10.3657 1.01673C11.0711 0.954022 11.7809 1.06819 12.4311 1.34892L13.0482 1.6162L10.1824 4.56738L11.4371 5.82582L14.3809 2.94887L14.6482 3.56788C14.8735 4.08976 14.993 4.65119 14.9997 5.21961C15.0064 5.78802 14.9002 6.35211 14.6872 6.87915C14.476 7.40029 14.1623 7.87368 13.7647 8.27122C13.5394 8.49169 13.2904 8.68653 13.0222 8.85218C12.4673 9.22275 11.8324 9.45636 11.1697 9.5338C10.5069 9.61124 9.83521 9.5303 9.20982 9.29764C8.11194 10.4113 5.37142 13.1704 3.89119 14.5522C3.59426 14.8219 3.20832 14.9726 2.80723 14.9754ZM10.7448 1.92802C10.087 1.92637 9.44359 2.12018 8.89614 2.48485C8.68265 2.6152 8.48437 2.76897 8.30498 2.9433C7.82789 3.42423 7.50926 4.03953 7.39182 4.70669C7.27437 5.37385 7.36374 6.06098 7.64792 6.67591L7.78342 6.97288L7.55048 7.20025C5.81224 8.89672 3.28146 11.4201 2.06479 12.7045C1.95646 12.8658 1.91012 13.0608 1.93435 13.2535C1.95857 13.4463 2.05171 13.6238 2.19657 13.7532C2.28005 13.8462 2.38177 13.9211 2.49541 13.9731C2.59557 14.0184 2.70383 14.043 2.81373 14.0455C2.98064 14.0413 3.14044 13.977 3.26383 13.8646C4.83687 12.3964 7.87622 9.32641 8.76807 8.42435L8.9973 8.19326L9.29242 8.32783C9.80618 8.56732 10.3731 8.66985 10.9382 8.62545C11.5033 8.58106 12.0473 8.39125 12.5174 8.07447C12.7313 7.9426 12.9296 7.78694 13.1085 7.61045C13.4183 7.30153 13.6631 6.93374 13.8286 6.52874C13.994 6.12375 14.0767 5.68974 14.0719 5.25228C14.0719 5.03662 14.0505 4.82148 14.0078 4.61007L11.4306 7.12508L8.87944 4.57759L11.3944 1.98834C11.1804 1.94674 10.9628 1.92653 10.7448 1.92802Z" fill="#424242"/> -</g> -</svg> diff --git a/extensions/npm/resources/light/refresh.svg b/extensions/npm/resources/light/refresh.svg deleted file mode 100644 index a5b88123a0e..00000000000 --- a/extensions/npm/resources/light/refresh.svg +++ /dev/null @@ -1,4 +0,0 @@ -<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path fill-rule="evenodd" clip-rule="evenodd" d="M5.56253 2.51574C3.46348 3.45007 2 5.55411 2 7.99996C2 11.3137 4.68629 14 8 14C11.3137 14 14 11.3137 14 7.99996C14 5.32516 12.2497 3.05916 9.83199 2.28479L9.52968 3.23829C11.5429 3.88451 13 5.77207 13 7.99996C13 10.7614 10.7614 13 8 13C5.23858 13 3 10.7614 3 7.99996C3 6.31101 3.83742 4.81764 5.11969 3.91242L5.56253 2.51574Z" fill="#424242"/> -<path fill-rule="evenodd" clip-rule="evenodd" d="M5 3H2V2H5.5L6 2.5V6H5V3Z" fill="#424242"/> -</svg> diff --git a/extensions/npm/resources/light/script.svg b/extensions/npm/resources/light/script.svg deleted file mode 100644 index 60f77501db7..00000000000 --- a/extensions/npm/resources/light/script.svg +++ /dev/null @@ -1,3 +0,0 @@ -<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path d="M2.80723 14.9754C2.57119 14.9721 2.33826 14.9211 2.12247 14.8254C1.90667 14.7297 1.71248 14.5913 1.55158 14.4186C1.2385 14.1334 1.04433 13.7408 1.00775 13.3189C0.966225 12.8828 1.09269 12.4473 1.36133 12.1013C2.56779 10.8289 4.9473 8.4494 6.67811 6.75479C6.30983 5.75887 6.32704 4.66127 6.72637 3.67739C7.05474 2.85876 7.63869 2.16805 8.39129 1.70807C8.9817 1.31706 9.66031 1.07944 10.3657 1.01673C11.0711 0.954022 11.7809 1.06819 12.4311 1.34892L13.0482 1.6162L10.1824 4.56738L11.4371 5.82582L14.3809 2.94887L14.6482 3.56788C14.8735 4.08976 14.993 4.65119 14.9997 5.21961C15.0064 5.78802 14.9002 6.35211 14.6872 6.87915C14.476 7.40029 14.1623 7.87368 13.7647 8.27122C13.5394 8.49169 13.2904 8.68653 13.0222 8.85218C12.4673 9.22275 11.8324 9.45636 11.1697 9.5338C10.5069 9.61124 9.83521 9.5303 9.20982 9.29764C8.11194 10.4113 5.37142 13.1704 3.89119 14.5522C3.59426 14.8219 3.20832 14.9726 2.80723 14.9754ZM10.7448 1.92802C10.087 1.92637 9.44359 2.12018 8.89614 2.48485C8.68265 2.6152 8.48437 2.76897 8.30498 2.9433C7.82789 3.42423 7.50926 4.03953 7.39182 4.70669C7.27438 5.37385 7.36374 6.06098 7.64792 6.67591L7.78342 6.97288L7.55048 7.20025C5.81224 8.89672 3.28146 11.4201 2.06479 12.7045C1.95646 12.8658 1.91012 13.0608 1.93435 13.2535C1.95857 13.4463 2.05171 13.6238 2.19657 13.7532C2.28005 13.8462 2.38177 13.9211 2.49541 13.9731C2.59557 14.0184 2.70383 14.043 2.81373 14.0455C2.98064 14.0413 3.14044 13.977 3.26383 13.8646C4.83687 12.3964 7.87622 9.32641 8.76807 8.42435L8.9973 8.19326L9.29242 8.32783C9.80617 8.56732 10.3731 8.66985 10.9382 8.62545C11.5033 8.58106 12.0473 8.39125 12.5174 8.07447C12.7313 7.9426 12.9296 7.78694 13.1085 7.61045C13.4183 7.30153 13.6631 6.93374 13.8286 6.52874C13.994 6.12375 14.0767 5.68974 14.0719 5.25228C14.0719 5.03662 14.0505 4.82148 14.0078 4.61007L11.4306 7.12508L8.87944 4.57759L11.3944 1.98834C11.1804 1.94674 10.9628 1.92653 10.7448 1.92802Z" fill="#424242"/> -</svg> diff --git a/extensions/npm/src/npmView.ts b/extensions/npm/src/npmView.ts index 03933d0a4c5..bcb1d9c9876 100644 --- a/extensions/npm/src/npmView.ts +++ b/extensions/npm/src/npmView.ts @@ -73,7 +73,7 @@ class NpmScript extends TreeItem { task: Task; package: PackageJSON; - constructor(context: ExtensionContext, packageJson: PackageJSON, task: Task) { + constructor(_context: ExtensionContext, packageJson: PackageJSON, task: Task) { super(task.name, TreeItemCollapsibleState.None); const command: ExplorerCommands = workspace.getConfiguration('npm').get<ExplorerCommands>('scriptExplorerAction') || 'open'; @@ -98,15 +98,9 @@ class NpmScript extends TreeItem { this.command = commandList[command]; if (task.group && task.group === TaskGroup.Clean) { - this.iconPath = { - light: context.asAbsolutePath(path.join('resources', 'light', 'prepostscript.svg')), - dark: context.asAbsolutePath(path.join('resources', 'dark', 'prepostscript.svg')) - }; + this.iconPath = new ThemeIcon('wrench-subaction'); } else { - this.iconPath = { - light: context.asAbsolutePath(path.join('resources', 'light', 'script.svg')), - dark: context.asAbsolutePath(path.join('resources', 'dark', 'script.svg')) - }; + this.iconPath = new ThemeIcon('wrench'); } if (task.detail) { this.tooltip = task.detail; diff --git a/extensions/objective-c/cgmanifest.json b/extensions/objective-c/cgmanifest.json index fc4983b457a..592ff960fb6 100644 --- a/extensions/objective-c/cgmanifest.json +++ b/extensions/objective-c/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "jeff-hykin/cpp-textmate-grammar", "repositoryUrl": "https://github.com/jeff-hykin/cpp-textmate-grammar", - "commitHash": "d62c0002b4c36c1c5add356ecc6f478bc2d416e1" + "commitHash": "bc7dedd28eebe52b374744d3fb34d77ff441569e" } }, "license": "MIT", @@ -15,4 +15,4 @@ } ], "version": 1 -} \ No newline at end of file +} diff --git a/extensions/objective-c/syntaxes/objective-c++.tmLanguage.json b/extensions/objective-c/syntaxes/objective-c++.tmLanguage.json index 429d5326104..473fd1e28b4 100644 --- a/extensions/objective-c/syntaxes/objective-c++.tmLanguage.json +++ b/extensions/objective-c/syntaxes/objective-c++.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/cpp-textmate-grammar/commit/d62c0002b4c36c1c5add356ecc6f478bc2d416e1", + "version": "https://github.com/jeff-hykin/cpp-textmate-grammar/commit/bc7dedd28eebe52b374744d3fb34d77ff441569e", "name": "Objective-C++", "scopeName": "source.objcpp", "patterns": [ @@ -7095,4 +7095,4 @@ ] } } -} \ No newline at end of file +} diff --git a/extensions/objective-c/syntaxes/objective-c.tmLanguage.json b/extensions/objective-c/syntaxes/objective-c.tmLanguage.json index 626678f5295..63ae3d9970d 100644 --- a/extensions/objective-c/syntaxes/objective-c.tmLanguage.json +++ b/extensions/objective-c/syntaxes/objective-c.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/cpp-textmate-grammar/commit/d62c0002b4c36c1c5add356ecc6f478bc2d416e1", + "version": "https://github.com/jeff-hykin/cpp-textmate-grammar/commit/bc7dedd28eebe52b374744d3fb34d77ff441569e", "name": "Objective-C", "scopeName": "source.objc", "patterns": [ @@ -3603,4 +3603,4 @@ ] } } -} \ No newline at end of file +} diff --git a/extensions/php/cgmanifest.json b/extensions/php/cgmanifest.json index 5b4050cdcd0..f265c4b3184 100644 --- a/extensions/php/cgmanifest.json +++ b/extensions/php/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "language-php", "repositoryUrl": "https://github.com/atom/language-php", - "commitHash": "96368115562c38ab3a203a03c4e26cca80fa2a10" + "commitHash": "882f6c0e19f0ebf9dafa443bf4c3fc5626f76aed" } }, "license": "MIT", diff --git a/extensions/php/syntaxes/php.tmLanguage.json b/extensions/php/syntaxes/php.tmLanguage.json index 08fb0a9eb5f..f04272f72f5 100644 --- a/extensions/php/syntaxes/php.tmLanguage.json +++ b/extensions/php/syntaxes/php.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/atom/language-php/commit/96368115562c38ab3a203a03c4e26cca80fa2a10", + "version": "https://github.com/atom/language-php/commit/882f6c0e19f0ebf9dafa443bf4c3fc5626f76aed", "scopeName": "source.php", "patterns": [ { @@ -833,7 +833,7 @@ "name": "keyword.operator.type.php" } }, - "end": "(?=[^\\\\$a-z0-9_\\x{7f}-\\x{7fffffff}])", + "end": "(?i)(?=[^\\\\$a-z0-9_\\x{7f}-\\x{7fffffff}])", "patterns": [ { "include": "#class-name" diff --git a/extensions/python/cgmanifest.json b/extensions/python/cgmanifest.json index 3ee82895a82..37a21b2de54 100644 --- a/extensions/python/cgmanifest.json +++ b/extensions/python/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "MagicStack/MagicPython", "repositoryUrl": "https://github.com/MagicStack/MagicPython", - "commitHash": "c0f8d514bbe6e9d3899f2b002bcd6971aef5e34b" + "commitHash": "c9b3409deb69acec31bbf7913830e93a046b30cc" } }, "license": "MIT", diff --git a/extensions/python/syntaxes/MagicPython.tmLanguage.json b/extensions/python/syntaxes/MagicPython.tmLanguage.json index f59618f75ff..bf5277fdc1a 100644 --- a/extensions/python/syntaxes/MagicPython.tmLanguage.json +++ b/extensions/python/syntaxes/MagicPython.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/MagicStack/MagicPython/commit/c0f8d514bbe6e9d3899f2b002bcd6971aef5e34b", + "version": "https://github.com/MagicStack/MagicPython/commit/c9b3409deb69acec31bbf7913830e93a046b30cc", "name": "MagicPython", "scopeName": "source.python", "patterns": [ diff --git a/extensions/python/syntaxes/MagicRegExp.tmLanguage.json b/extensions/python/syntaxes/MagicRegExp.tmLanguage.json index c7e67436a08..fc11fa5affe 100644 --- a/extensions/python/syntaxes/MagicRegExp.tmLanguage.json +++ b/extensions/python/syntaxes/MagicRegExp.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/MagicStack/MagicPython/commit/361a4964a559481330764a447e7bab88d4f1b01b", + "version": "https://github.com/MagicStack/MagicPython/commit/c9b3409deb69acec31bbf7913830e93a046b30cc", "name": "MagicRegExp", "scopeName": "source.regexp.python", "patterns": [ diff --git a/extensions/search-result/syntaxes/generateTMLanguage.js b/extensions/search-result/syntaxes/generateTMLanguage.js index 7a9eb9204f6..23e0615a718 100644 --- a/extensions/search-result/syntaxes/generateTMLanguage.js +++ b/extensions/search-result/syntaxes/generateTMLanguage.js @@ -55,6 +55,7 @@ const mappings = [ ['vb', 'source.asp.vb.net'], ['xml', 'text.xml'], ['yaml', 'source.yaml'], + ['yml', 'source.yaml'], ]; const scopes = { @@ -113,8 +114,8 @@ mappings.forEach(([ext, scope, regexp]) => patterns: [ { name: [scopes.resultBlock.result.meta, scopes.resultBlock.result.metaMultiLine].join(' '), - begin: '^ ((\\d+) )', - while: '^ (?:((\\d+)(:))|((\\d+) ))', + begin: '^ (?:\\s*)((\\d+) )', + while: '^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))', beginCaptures: { '0': { name: scopes.resultBlock.result.prefix.meta }, '1': { name: scopes.resultBlock.result.prefix.metaContext }, @@ -132,7 +133,7 @@ mappings.forEach(([ext, scope, regexp]) => patterns: [{ include: scope }] }, { - begin: '^ ((\\d+)(:))', + begin: '^ (?:\\s*)((\\d+)(:))', while: '(?=not)possible', name: [scopes.resultBlock.result.meta, scopes.resultBlock.result.metaSingleLine].join(' '), beginCaptures: { @@ -214,7 +215,7 @@ const plainText = [ } }, { - match: '^ (?:((\\d+)(:))|((\\d+)( ))(.*))', + match: '^ (?:\\s*)(?:((\\d+)(:))|((\\d+)( ))(.*))', name: [scopes.resultBlock.meta, scopes.resultBlock.result.meta].join(' '), captures: { '1': { name: [scopes.resultBlock.result.prefix.meta, scopes.resultBlock.result.prefix.metaMatch].join(' ') }, diff --git a/extensions/search-result/syntaxes/searchResult.tmLanguage.json b/extensions/search-result/syntaxes/searchResult.tmLanguage.json index 8edb4a7f89a..a8a5557c3e5 100644 --- a/extensions/search-result/syntaxes/searchResult.tmLanguage.json +++ b/extensions/search-result/syntaxes/searchResult.tmLanguage.json @@ -240,6 +240,9 @@ { "include": "#yaml" }, + { + "include": "#yml" + }, { "match": "^(?!\\s)(.*?)([^\\\\\\/\\n]*)(:)$", "name": "meta.resultBlock.search string meta.path.search", @@ -256,7 +259,7 @@ } }, { - "match": "^ (?:((\\d+)(:))|((\\d+)( ))(.*))", + "match": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+)( ))(.*))", "name": "meta.resultBlock.search meta.resultLine.search", "captures": { "1": { @@ -299,8 +302,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -339,7 +342,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -385,8 +388,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -425,7 +428,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -471,8 +474,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -511,7 +514,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -557,8 +560,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -597,7 +600,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -643,8 +646,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -683,7 +686,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -729,8 +732,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -769,7 +772,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -815,8 +818,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -855,7 +858,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -901,8 +904,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -941,7 +944,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -987,8 +990,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1027,7 +1030,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -1073,8 +1076,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1113,7 +1116,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -1159,8 +1162,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1199,7 +1202,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -1245,8 +1248,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1285,7 +1288,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -1331,8 +1334,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1371,7 +1374,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -1417,8 +1420,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1457,7 +1460,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -1503,8 +1506,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1543,7 +1546,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -1589,8 +1592,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1629,7 +1632,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -1675,8 +1678,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1715,7 +1718,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -1761,8 +1764,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1801,7 +1804,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -1847,8 +1850,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1887,7 +1890,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -1933,8 +1936,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1973,7 +1976,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -2019,8 +2022,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2059,7 +2062,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -2105,8 +2108,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2145,7 +2148,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -2191,8 +2194,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2231,7 +2234,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -2277,8 +2280,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2317,7 +2320,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -2363,8 +2366,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2403,7 +2406,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -2449,8 +2452,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2489,7 +2492,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -2535,8 +2538,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2575,7 +2578,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -2621,8 +2624,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2661,7 +2664,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -2707,8 +2710,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2747,7 +2750,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -2793,8 +2796,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2833,7 +2836,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -2879,8 +2882,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2919,7 +2922,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -2965,8 +2968,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3005,7 +3008,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -3051,8 +3054,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3091,7 +3094,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -3137,8 +3140,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3177,7 +3180,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -3223,8 +3226,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3263,7 +3266,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -3309,8 +3312,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3349,7 +3352,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -3395,8 +3398,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3435,7 +3438,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -3481,8 +3484,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3521,7 +3524,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -3567,8 +3570,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3607,7 +3610,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -3653,8 +3656,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3693,7 +3696,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -3739,8 +3742,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3779,7 +3782,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -3825,8 +3828,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3865,7 +3868,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -3911,8 +3914,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3951,7 +3954,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -3997,8 +4000,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4037,7 +4040,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -4083,8 +4086,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4123,7 +4126,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -4169,8 +4172,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4209,7 +4212,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -4255,8 +4258,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4295,7 +4298,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -4341,8 +4344,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4381,7 +4384,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -4427,8 +4430,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4467,7 +4470,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -4513,8 +4516,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4553,7 +4556,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -4599,8 +4602,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4639,7 +4642,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -4685,8 +4688,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4725,7 +4728,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -4771,8 +4774,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4811,7 +4814,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -4857,8 +4860,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4897,7 +4900,93 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", + "while": "(?=not)possible", + "name": "meta.resultLine.search meta.resultLine.singleLine.search", + "beginCaptures": { + "0": { + "name": "constant.numeric.integer meta.resultLinePrefix.search" + }, + "1": { + "name": "meta.resultLinePrefix.matchLinePrefix.search" + }, + "2": { + "name": "meta.resultLinePrefix.lineNumber.search" + }, + "3": { + "name": "punctuation.separator" + } + }, + "patterns": [ + { + "include": "source.yaml" + } + ] + } + ] + }, + "yml": { + "name": "meta.resultBlock.search", + "begin": "^(?!\\s)(.*?)([^\\\\\\/\\n]*\\.yml)(:)$", + "end": "^(?!\\s)", + "beginCaptures": { + "0": { + "name": "string meta.path.search" + }, + "1": { + "name": "meta.path.dirname.search" + }, + "2": { + "name": "meta.path.basename.search" + }, + "3": { + "name": "punctuation.separator" + } + }, + "patterns": [ + { + "name": "meta.resultLine.search meta.resultLine.multiLine.search", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", + "beginCaptures": { + "0": { + "name": "constant.numeric.integer meta.resultLinePrefix.search" + }, + "1": { + "name": "meta.resultLinePrefix.contextLinePrefix.search" + }, + "2": { + "name": "meta.resultLinePrefix.lineNumber.search" + } + }, + "whileCaptures": { + "0": { + "name": "constant.numeric.integer meta.resultLinePrefix.search" + }, + "1": { + "name": "meta.resultLinePrefix.matchLinePrefix.search" + }, + "2": { + "name": "meta.resultLinePrefix.lineNumber.search" + }, + "3": { + "name": "punctuation.separator" + }, + "4": { + "name": "meta.resultLinePrefix.contextLinePrefix.search" + }, + "5": { + "name": "meta.resultLinePrefix.lineNumber.search" + } + }, + "patterns": [ + { + "include": "source.yaml" + } + ] + }, + { + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { diff --git a/extensions/shared.webpack.config.js b/extensions/shared.webpack.config.js index 2bcb6c47474..f9d93777c60 100644 --- a/extensions/shared.webpack.config.js +++ b/extensions/shared.webpack.config.js @@ -68,7 +68,7 @@ module.exports = function withDefaults(/**@type WebpackConfig*/extConfig) { // yes, really source maps devtool: 'source-map', plugins: [ - // @ts-ignore + // @ts-expect-error new CopyWebpackPlugin([ { from: 'src', to: '.', ignore: ['**/test/**', '*.ts'] } ]), diff --git a/extensions/theme-abyss/themes/abyss-color-theme.json b/extensions/theme-abyss/themes/abyss-color-theme.json index 1df39d31c90..39f93305b8d 100644 --- a/extensions/theme-abyss/themes/abyss-color-theme.json +++ b/extensions/theme-abyss/themes/abyss-color-theme.json @@ -434,5 +434,6 @@ "terminal.ansiBrightMagenta": "#d778ff", "terminal.ansiBrightCyan": "#78ffff", "terminal.ansiBrightWhite": "#ffffff" - } + }, + "semanticHighlighting": true } diff --git a/extensions/theme-defaults/themes/dark_defaults.json b/extensions/theme-defaults/themes/dark_defaults.json index 00c2ac8c36b..89f0a5beec8 100644 --- a/extensions/theme-defaults/themes/dark_defaults.json +++ b/extensions/theme-defaults/themes/dark_defaults.json @@ -18,5 +18,6 @@ "menu.foreground": "#CCCCCC", "statusBarItem.remoteForeground": "#FFF", "statusBarItem.remoteBackground": "#16825D" - } -} \ No newline at end of file + }, + "semanticHighlighting": true +} diff --git a/extensions/theme-defaults/themes/hc_black_defaults.json b/extensions/theme-defaults/themes/hc_black_defaults.json index 9d11138a99b..1a03010abff 100644 --- a/extensions/theme-defaults/themes/hc_black_defaults.json +++ b/extensions/theme-defaults/themes/hc_black_defaults.json @@ -337,5 +337,6 @@ "foreground": "#569cd6" } } - ] -} \ No newline at end of file + ], + "semanticHighlighting": true +} diff --git a/extensions/theme-defaults/themes/light_defaults.json b/extensions/theme-defaults/themes/light_defaults.json index 018da38e939..9ea03a9e317 100644 --- a/extensions/theme-defaults/themes/light_defaults.json +++ b/extensions/theme-defaults/themes/light_defaults.json @@ -18,5 +18,6 @@ "settings.numberInputBorder": "#CECECE", "statusBarItem.remoteForeground": "#FFF", "statusBarItem.remoteBackground": "#16825D" - } + }, + "semanticHighlighting": true } 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 111a4a23d9b..cdd22307117 100644 --- a/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json +++ b/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json @@ -394,5 +394,6 @@ "foreground": "#dc3958" } } - ] + ], + "semanticHighlighting": true } diff --git a/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json b/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json index f0b6126d5fd..8b1fe2dd80e 100644 --- a/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json +++ b/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json @@ -572,5 +572,6 @@ "foreground": "#c7444a" } } - ] + ], + "semanticHighlighting": true } diff --git a/extensions/theme-monokai/themes/monokai-color-theme.json b/extensions/theme-monokai/themes/monokai-color-theme.json index c695640f299..a3050894657 100644 --- a/extensions/theme-monokai/themes/monokai-color-theme.json +++ b/extensions/theme-monokai/themes/monokai-color-theme.json @@ -476,5 +476,6 @@ "foreground": "#FD971F" } } - ] + ], + "semanticHighlighting": true } diff --git a/extensions/theme-quietlight/themes/quietlight-color-theme.json b/extensions/theme-quietlight/themes/quietlight-color-theme.json index ae19ba7889b..ffcb30cff03 100644 --- a/extensions/theme-quietlight/themes/quietlight-color-theme.json +++ b/extensions/theme-quietlight/themes/quietlight-color-theme.json @@ -494,5 +494,6 @@ "walkThrough.embeddedEditorBackground": "#00000014", "editorIndentGuide.background": "#aaaaaa60", "editorIndentGuide.activeBackground": "#777777b0" - } + }, + "semanticHighlighting": true } diff --git a/extensions/theme-red/themes/Red-color-theme.json b/extensions/theme-red/themes/Red-color-theme.json index 277e7a8db3f..8964f40a093 100644 --- a/extensions/theme-red/themes/Red-color-theme.json +++ b/extensions/theme-red/themes/Red-color-theme.json @@ -192,7 +192,7 @@ }, { "name": "Support.constant", - "scope": "support.constant", + "scope": [ "support.constant", "support.variable"], "settings": { "fontStyle": "", "foreground": "#eb939aff" @@ -385,5 +385,6 @@ "foreground": "#ec0d1e" } } - ] + ], + "semanticHighlighting": true } 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 682444485d5..b23ff8bb85c 100644 --- a/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json +++ b/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json @@ -477,5 +477,6 @@ "terminal.ansiBrightMagenta": "#6c71c4", "terminal.ansiBrightCyan": "#93a1a1", "terminal.ansiBrightWhite": "#fdf6e3" - } + }, + "semanticHighlighting": true } 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 a29c8fb32f0..21f530d00a3 100644 --- a/extensions/theme-solarized-light/themes/solarized-light-color-theme.json +++ b/extensions/theme-solarized-light/themes/solarized-light-color-theme.json @@ -182,7 +182,10 @@ }, { "name": "Library constant", - "scope": "support.constant", + "scope": [ + "support.constant", + "support.variable" + ], "settings": {} }, { @@ -484,5 +487,6 @@ // Interactive Playground "walkThrough.embeddedEditorBackground": "#00000014" - } + }, + "semanticHighlighting": true } diff --git a/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json b/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json index f8c47a29e7b..0baee6822ef 100644 --- a/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json +++ b/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json @@ -255,5 +255,6 @@ "foreground": "#b267e6" } } - ] + ], + "semanticHighlighting": true } diff --git a/extensions/typescript-basics/build/update-grammars.js b/extensions/typescript-basics/build/update-grammars.js index 1839ad63674..3a7d1e7eb2b 100644 --- a/extensions/typescript-basics/build/update-grammars.js +++ b/extensions/typescript-basics/build/update-grammars.js @@ -17,6 +17,23 @@ function removeDom(grammar) { return grammar; } +function removeNodeTypes(grammar) { + grammar.repository['support-objects'].patterns = grammar.repository['support-objects'].patterns.filter(pattern => { + if (pattern.name) { + if (pattern.name.startsWith('support.variable.object.node') || pattern.name.startsWith('support.class.node.')) { + return false; + } + } + if (pattern.captures) { + if (Object.values(pattern.captures).some(capture => capture.name && capture.name.startsWith('support.variable.object.process'))) { + return false; + } + } + return true; + }); + return grammar; +} + function patchJsdoctype(grammar) { grammar.repository['jsdoctype'].patterns = grammar.repository['jsdoctype'].patterns.filter(pattern => { if (pattern.name && pattern.name.indexOf('illegal') >= -1) { @@ -28,7 +45,7 @@ function patchJsdoctype(grammar) { } function patchGrammar(grammar) { - return removeDom(patchJsdoctype(grammar)); + return removeNodeTypes(removeDom(patchJsdoctype(grammar))); } function adaptToJavaScript(grammar, replacementScope) { diff --git a/extensions/typescript-basics/cgmanifest.json b/extensions/typescript-basics/cgmanifest.json index 4136543a84f..d95b4a54e7d 100644 --- a/extensions/typescript-basics/cgmanifest.json +++ b/extensions/typescript-basics/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "TypeScript-TmLanguage", "repositoryUrl": "https://github.com/Microsoft/TypeScript-TmLanguage", - "commitHash": "f065e7e88d1c20160c5ec92455aad99a1016284f" + "commitHash": "84422d92e164c379ed817ef8e1d6c35b61de233e" } }, "license": "MIT", diff --git a/extensions/typescript-basics/package.json b/extensions/typescript-basics/package.json index 307958534b9..80c1f491ce5 100644 --- a/extensions/typescript-basics/package.json +++ b/extensions/typescript-basics/package.json @@ -79,6 +79,34 @@ } } ], + "semanticTokenScopes": [ + { + "language": "typescript", + "scopes": { + "property": ["variable.other.property.ts"], + "property.readonly": ["variable.other.constant.property.ts"], + "variable": ["variable.other.readwrite.ts"], + "variable.readonly": ["variable.other.constant.object.ts"], + "function": ["entity.name.function.ts"], + "namespace": ["entity.name.type.module.ts"], + "variable.defaultLibrary": ["support.variable.ts"], + "function.defaultLibrary": ["support.function.ts"] + } + }, + { + "language": "typescriptreact", + "scopes": { + "property": ["variable.other.property.tsx"], + "property.readonly": ["variable.other.constant.property.tsx"], + "variable": ["variable.other.readwrite.tsx"], + "variable.readonly": ["variable.other.constant.object.tsx"], + "function": ["entity.name.function.tsx"], + "namespace": ["entity.name.type.module.tsx"], + "variable.defaultLibrary": ["support.variable.tsx"], + "function.defaultLibrary": ["support.function.tsx"] + } + } + ], "snippets": [ { "language": "typescript", diff --git a/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json b/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json index 4edc3b8285e..d4489999f40 100644 --- a/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json +++ b/extensions/typescript-basics/syntaxes/TypeScript.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/TypeScript-TmLanguage/commit/f065e7e88d1c20160c5ec92455aad99a1016284f", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/84422d92e164c379ed817ef8e1d6c35b61de233e", "name": "TypeScript", "scopeName": "source.ts", "patterns": [ @@ -15,6 +15,11 @@ "include": "#statements" }, { + "include": "#shebang" + } + ], + "repository": { + "shebang": { "name": "comment.line.shebang.ts", "match": "\\A(#!).*(?=$)", "captures": { @@ -22,9 +27,7 @@ "name": "punctuation.definition.comment.ts" } } - } - ], - "repository": { + }, "statements": { "patterns": [ { @@ -423,7 +426,7 @@ "patterns": [ { "name": "meta.var-single-variable.expr.ts", - "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(\\!)?(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))Function(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))) |\n(:\\s*((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(\\!)?(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))Function(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))) |\n(:\\s*((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "beginCaptures": { "1": { "name": "meta.definition.variable.ts entity.name.function.ts" @@ -481,7 +484,7 @@ "patterns": [ { "name": "meta.var-single-variable.expr.ts", - "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))Function(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))) |\n(:\\s*((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))Function(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))) |\n(:\\s*((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "beginCaptures": { "1": { "name": "meta.definition.variable.ts variable.other.constant.ts entity.name.function.ts" @@ -865,7 +868,7 @@ } }, { - "match": "(?x)(?:(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(public|private|protected|readonly)\\s+)?(?:(\\.\\.\\.)\\s*)?(?<!=|:)(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(?:(this)|([_$[:alpha:]][_$[:alnum:]]*))(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))\\s*(\\??)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))Function(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))) |\n(:\\s*((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(public|private|protected|readonly)\\s+)?(?:(\\.\\.\\.)\\s*)?(?<!=|:)(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(?:(this)|([_$[:alpha:]][_$[:alnum:]]*))(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))\\s*(\\??)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))Function(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))) |\n(:\\s*((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.ts" @@ -1105,7 +1108,7 @@ "include": "#comment" }, { - "match": "(?x)(\\#?[_$[:alpha:]][_$[:alnum:]]*)(?:(\\?)|(\\!))?(?=\\s*\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))Function(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))) |\n(:\\s*((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(\\#?[_$[:alpha:]][_$[:alnum:]]*)(?:(\\?)|(\\!))?(?=\\s*\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))Function(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))) |\n(:\\s*((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "meta.definition.property.ts entity.name.function.ts" @@ -1428,7 +1431,7 @@ }, { "name": "meta.arrow.ts", - "begin": "(?x) (?:\n (?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(\\basync)\n)? ((?<![})!\\]])\\s*\n (?=\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", + "begin": "(?x) (?:\n (?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(\\basync)\n)? ((?<![})!\\]])\\s*\n (?=\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", "beginCaptures": { "1": { "name": "storage.modifier.async.ts" @@ -2622,7 +2625,7 @@ }, { "name": "meta.object.member.ts", - "match": "(?x)(?:([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*:(\\s*\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/)*\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*:(\\s*\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/)*\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "0": { "name": "meta.object-literal.key.ts" @@ -2685,7 +2688,7 @@ "name": "keyword.control.as.ts" } }, - "end": "(?=$|^|[,}]|\\|\\||\\&\\&|((?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(as)\\s+))", + "end": "(?=[;),}\\]:?\\-\\+\\>]|\\|\\||\\&\\&|\\!\\=\\=|$|^|((?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(as)\\s+))", "patterns": [ { "include": "#type" @@ -2833,26 +2836,75 @@ ] }, "function-call": { - "begin": "(?=(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?<!=)\\>))*(?<!=)\\>)*(?<!=)>\\s*)?\\()", - "end": "(?<=\\))(?!(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?<!=)\\>))*(?<!=)\\>)*(?<!=)>\\s*)?\\()", "patterns": [ { - "name": "meta.function-call.ts", - "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))", - "end": "(?=\\s*(?:(\\?\\.\\s*)|(\\!))?(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?<!=)\\>))*(?<!=)\\>)*(?<!=)>\\s*)?\\()", + "begin": "(?=(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?((<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?<!=)\\>))*(?<!=)\\>)*(?<!=)>\\s*)?\\())", + "end": "(?<=\\))(?!(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?((<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?<!=)\\>))*(?<!=)\\>)*(?<!=)>\\s*)?\\())", "patterns": [ { - "include": "#support-function-call-identifiers" + "name": "meta.function-call.ts", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))", + "end": "(?=\\s*(?:(\\?\\.\\s*)|(\\!))?((<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?<!=)\\>))*(?<!=)\\>)*(?<!=)>\\s*)?\\())", + "patterns": [ + { + "include": "#function-call-target" + } + ] }, { - "name": "entity.name.function.ts", - "match": "(\\#?[_$[:alpha:]][_$[:alnum:]]*)" + "include": "#comment" + }, + { + "include": "#function-call-optionals" + }, + { + "include": "#type-arguments" + }, + { + "include": "#paren-expression" } ] }, { - "include": "#comment" + "begin": "(?=(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))(<\\s*[\\{\\[\\(]\\s*$))", + "end": "(?<=\\>)(?!(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))(<\\s*[\\{\\[\\(]\\s*$))", + "patterns": [ + { + "name": "meta.function-call.ts", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))", + "end": "(?=(<\\s*[\\{\\[\\(]\\s*$))", + "patterns": [ + { + "include": "#function-call-target" + } + ] + }, + { + "include": "#comment" + }, + { + "include": "#function-call-optionals" + }, + { + "include": "#type-arguments" + } + ] + } + ] + }, + "function-call-target": { + "patterns": [ + { + "include": "#support-function-call-identifiers" }, + { + "name": "entity.name.function.ts", + "match": "(\\#?[_$[:alpha:]][_$[:alnum:]]*)" + } + ] + }, + "function-call-optionals": { + "patterns": [ { "name": "meta.function-call.ts punctuation.accessor.optional.ts", "match": "\\?\\." @@ -2860,12 +2912,6 @@ { "name": "meta.function-call.ts keyword.operator.definiteassignment.ts", "match": "\\!" - }, - { - "include": "#type-arguments" - }, - { - "include": "#paren-expression" } ] }, @@ -2897,7 +2943,7 @@ "name": "keyword.operator.new.ts" } }, - "end": "(?<=\\))|(?=[;),}\\]:\\-\\+]|\\|\\||\\&\\&|$|((?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))new(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.)))|((?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))function((\\s+[_$[:alpha:]][_$[:alnum:]]*)|(\\s*[\\(]))))", + "end": "(?<=\\))|(?=[;),}\\]:?\\-\\+\\>]|\\|\\||\\&\\&|\\!\\=\\=|$|((?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))new(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.)))|((?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))function((\\s+[_$[:alpha:]][_$[:alnum:]]*)|(\\s*[\\(]))))", "patterns": [ { "include": "#paren-expression" @@ -2917,7 +2963,7 @@ "name": "keyword.operator.expression.instanceof.ts" } }, - "end": "(?<=\\))|(?=[;),}\\]:?]|\\|\\||\\&\\&|$|((?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))function((\\s+[_$[:alpha:]][_$[:alnum:]]*)|(\\s*[\\(]))))", + "end": "(?<=\\))|(?=[;),}\\]:?\\-\\+\\>]|\\|\\||\\&\\&|\\!\\=\\=|$|(([\\&\\~\\^\\|]\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s+instanceof(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.)))|((?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))function((\\s+[_$[:alpha:]][_$[:alnum:]]*)|(\\s*[\\(]))))", "patterns": [ { "include": "#type" @@ -3011,7 +3057,7 @@ } }, { - "match": "(?x)(?:(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(public|private|protected|readonly)\\s+)?(?:(\\.\\.\\.)\\s*)?(?<!=|:)(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(?:(this)|([_$[:alpha:]][_$[:alnum:]]*))(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))\\s*(\\??)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))Function(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))) |\n(:\\s*((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(public|private|protected|readonly)\\s+)?(?:(\\.\\.\\.)\\s*)?(?<!=|:)(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(?:(this)|([_$[:alpha:]][_$[:alnum:]]*))(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))\\s*(\\??)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))Function(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))) |\n(:\\s*((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.ts" @@ -3227,7 +3273,7 @@ "name": "keyword.control.as.ts" } }, - "end": "(?=$|^|[;,:})\\]]|\\|\\||\\&\\&|((?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(as)\\s+)|(\\s+\\<))", + "end": "(?=^|[;),}\\]:?\\-\\+\\>]|\\|\\||\\&\\&|\\!\\=\\=|$|((?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(as)\\s+)|(\\s+\\<))", "patterns": [ { "include": "#type" @@ -3259,7 +3305,7 @@ "match": "<=|>=|<>|<|>" }, { - "match": "(\\!)\\s*(/)(?![/*])", + "match": "(?<=[_$[:alnum:]])(\\!)\\s*(/)(?![/*])", "captures": { "1": { "name": "keyword.operator.logical.ts" @@ -3620,30 +3666,6 @@ } } }, - { - "name": "support.class.node.ts", - "match": "(?x)(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(Buffer|EventEmitter|Server|Pipe|Socket|REPLServer|ReadStream|WriteStream|Stream\n |Inflate|Deflate|InflateRaw|DeflateRaw|GZip|GUnzip|Unzip|Zip)\\b(?!\\$)" - }, - { - "match": "(?x)(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(process)(?:(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))(?:\n (arch|argv|config|connected|env|execArgv|execPath|exitCode|mainModule|pid|platform|release|stderr|stdin|stdout|title|version|versions)\n |\n (abort|chdir|cwd|disconnect|exit|[sg]ete?[gu]id|send|[sg]etgroups|initgroups|kill|memoryUsage|nextTick|umask|uptime|hrtime)\n))?\\b(?!\\$)", - "captures": { - "1": { - "name": "support.variable.object.process.ts" - }, - "2": { - "name": "punctuation.accessor.ts" - }, - "3": { - "name": "punctuation.accessor.optional.ts" - }, - "4": { - "name": "support.variable.property.process.ts" - }, - "5": { - "name": "support.function.process.ts" - } - } - }, { "match": "(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(?:(exports)|(module)(?:(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))(exports|id|filename|loaded|parent|children))?)\\b(?!\\$)", "captures": { @@ -3663,10 +3685,6 @@ "name": "support.type.object.module.ts" } } - }, - { - "name": "support.variable.object.node.ts", - "match": "(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(global|GLOBAL|root|__dirname|__filename)\\b(?!\\$)" } ] }, @@ -3676,7 +3694,7 @@ "include": "#object-identifiers" }, { - "match": "(?x)(?:(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*)?([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", + "match": "(?x)(?:(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*)?([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", "captures": { "1": { "name": "punctuation.accessor.ts" @@ -4382,6 +4400,10 @@ "name": "keyword.operator.expression.infer.ts", "match": "(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))infer(?=\\s+[_$[:alpha:]])" }, + { + "name": "keyword.operator.expression.awaited.ts", + "match": "(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))awaited(?=\\s+[_$[:alpha:]])" + }, { "name": "keyword.operator.expression.import.ts", "match": "(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))import(?=\\s*\\()" @@ -4591,12 +4613,12 @@ "patterns": [ { "name": "string.template.ts", - "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?<!=)\\>))*(?<!=)\\>)*(?<!=)>\\s*)?`)", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)(<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?<!=)\\>))*(?<!=)\\>)*(?<!=)>\\s*)?`)", "end": "(?=`)", "patterns": [ { "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*))", - "end": "(?=(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?<!=)\\>))*(?<!=)\\>)*(?<!=)>\\s*)?`)", + "end": "(?=(<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?<!=)\\>))*(?<!=)\\>)*(?<!=)>\\s*)?`)", "patterns": [ { "include": "#support-function-call-identifiers" @@ -4614,7 +4636,7 @@ }, { "name": "string.template.ts", - "begin": "([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?<!=)\\>))*(?<!=)\\>)*(?<!=)>\\s*)`)", + "begin": "([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?<!=)\\>))*(?<!=)\\>)*(?<!=)>\\s*)`)", "beginCaptures": { "1": { "name": "entity.name.function.tagged-template.ts" diff --git a/extensions/typescript-basics/syntaxes/TypeScriptReact.tmLanguage.json b/extensions/typescript-basics/syntaxes/TypeScriptReact.tmLanguage.json index 810cac1b2db..663ab7d94c0 100644 --- a/extensions/typescript-basics/syntaxes/TypeScriptReact.tmLanguage.json +++ b/extensions/typescript-basics/syntaxes/TypeScriptReact.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/TypeScript-TmLanguage/commit/f065e7e88d1c20160c5ec92455aad99a1016284f", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/84422d92e164c379ed817ef8e1d6c35b61de233e", "name": "TypeScriptReact", "scopeName": "source.tsx", "patterns": [ @@ -15,16 +15,19 @@ "include": "#statements" }, { - "name": "comment.line.shebang.ts", - "match": "\\A(#!).*(?=$)", - "captures": { - "1": { - "name": "punctuation.definition.comment.ts" - } - } + "include": "#shebang" } ], "repository": { + "shebang": { + "name": "comment.line.shebang.tsx", + "match": "\\A(#!).*(?=$)", + "captures": { + "1": { + "name": "punctuation.definition.comment.tsx" + } + } + }, "statements": { "patterns": [ { @@ -426,7 +429,7 @@ "patterns": [ { "name": "meta.var-single-variable.expr.tsx", - "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(\\!)?(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))Function(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))) |\n(:\\s*((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(\\!)?(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))Function(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))) |\n(:\\s*((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "beginCaptures": { "1": { "name": "meta.definition.variable.tsx entity.name.function.tsx" @@ -484,7 +487,7 @@ "patterns": [ { "name": "meta.var-single-variable.expr.tsx", - "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))Function(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))) |\n(:\\s*((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))Function(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))) |\n(:\\s*((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "beginCaptures": { "1": { "name": "meta.definition.variable.tsx variable.other.constant.tsx entity.name.function.tsx" @@ -868,7 +871,7 @@ } }, { - "match": "(?x)(?:(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(public|private|protected|readonly)\\s+)?(?:(\\.\\.\\.)\\s*)?(?<!=|:)(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(?:(this)|([_$[:alpha:]][_$[:alnum:]]*))(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))\\s*(\\??)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))Function(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))) |\n(:\\s*((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(public|private|protected|readonly)\\s+)?(?:(\\.\\.\\.)\\s*)?(?<!=|:)(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(?:(this)|([_$[:alpha:]][_$[:alnum:]]*))(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))\\s*(\\??)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))Function(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))) |\n(:\\s*((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.tsx" @@ -1108,7 +1111,7 @@ "include": "#comment" }, { - "match": "(?x)(\\#?[_$[:alpha:]][_$[:alnum:]]*)(?:(\\?)|(\\!))?(?=\\s*\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))Function(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))) |\n(:\\s*((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(\\#?[_$[:alpha:]][_$[:alnum:]]*)(?:(\\?)|(\\!))?(?=\\s*\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))Function(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))) |\n(:\\s*((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "meta.definition.property.tsx entity.name.function.tsx" @@ -1431,7 +1434,7 @@ }, { "name": "meta.arrow.tsx", - "begin": "(?x) (?:\n (?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(\\basync)\n)? ((?<![})!\\]])\\s*\n (?=\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", + "begin": "(?x) (?:\n (?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(\\basync)\n)? ((?<![})!\\]])\\s*\n (?=\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", "beginCaptures": { "1": { "name": "storage.modifier.async.tsx" @@ -2625,7 +2628,7 @@ }, { "name": "meta.object.member.tsx", - "match": "(?x)(?:([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*:(\\s*\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/)*\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*:(\\s*\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/)*\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "0": { "name": "meta.object-literal.key.tsx" @@ -2688,7 +2691,7 @@ "name": "keyword.control.as.tsx" } }, - "end": "(?=$|^|[,}]|\\|\\||\\&\\&|((?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(as)\\s+))", + "end": "(?=[;),}\\]:?\\-\\+\\>]|\\|\\||\\&\\&|\\!\\=\\=|$|^|((?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(as)\\s+))", "patterns": [ { "include": "#type" @@ -2836,26 +2839,75 @@ ] }, "function-call": { - "begin": "(?=(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?<!=)\\>))*(?<!=)\\>)*(?<!=)>\\s*)?\\()", - "end": "(?<=\\))(?!(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?<!=)\\>))*(?<!=)\\>)*(?<!=)>\\s*)?\\()", "patterns": [ { - "name": "meta.function-call.tsx", - "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))", - "end": "(?=\\s*(?:(\\?\\.\\s*)|(\\!))?(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?<!=)\\>))*(?<!=)\\>)*(?<!=)>\\s*)?\\()", + "begin": "(?=(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?((<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?<!=)\\>))*(?<!=)\\>)*(?<!=)>\\s*)?\\())", + "end": "(?<=\\))(?!(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?((<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?<!=)\\>))*(?<!=)\\>)*(?<!=)>\\s*)?\\())", "patterns": [ { - "include": "#support-function-call-identifiers" + "name": "meta.function-call.tsx", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))", + "end": "(?=\\s*(?:(\\?\\.\\s*)|(\\!))?((<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?<!=)\\>))*(?<!=)\\>)*(?<!=)>\\s*)?\\())", + "patterns": [ + { + "include": "#function-call-target" + } + ] }, { - "name": "entity.name.function.tsx", - "match": "(\\#?[_$[:alpha:]][_$[:alnum:]]*)" + "include": "#comment" + }, + { + "include": "#function-call-optionals" + }, + { + "include": "#type-arguments" + }, + { + "include": "#paren-expression" } ] }, { - "include": "#comment" + "begin": "(?=(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))(<\\s*[\\{\\[\\(]\\s*$))", + "end": "(?<=\\>)(?!(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))(<\\s*[\\{\\[\\(]\\s*$))", + "patterns": [ + { + "name": "meta.function-call.tsx", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))", + "end": "(?=(<\\s*[\\{\\[\\(]\\s*$))", + "patterns": [ + { + "include": "#function-call-target" + } + ] + }, + { + "include": "#comment" + }, + { + "include": "#function-call-optionals" + }, + { + "include": "#type-arguments" + } + ] + } + ] + }, + "function-call-target": { + "patterns": [ + { + "include": "#support-function-call-identifiers" }, + { + "name": "entity.name.function.tsx", + "match": "(\\#?[_$[:alpha:]][_$[:alnum:]]*)" + } + ] + }, + "function-call-optionals": { + "patterns": [ { "name": "meta.function-call.tsx punctuation.accessor.optional.tsx", "match": "\\?\\." @@ -2863,12 +2915,6 @@ { "name": "meta.function-call.tsx keyword.operator.definiteassignment.tsx", "match": "\\!" - }, - { - "include": "#type-arguments" - }, - { - "include": "#paren-expression" } ] }, @@ -2900,7 +2946,7 @@ "name": "keyword.operator.new.tsx" } }, - "end": "(?<=\\))|(?=[;),}\\]:\\-\\+]|\\|\\||\\&\\&|$|((?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))new(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.)))|((?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))function((\\s+[_$[:alpha:]][_$[:alnum:]]*)|(\\s*[\\(]))))", + "end": "(?<=\\))|(?=[;),}\\]:?\\-\\+\\>]|\\|\\||\\&\\&|\\!\\=\\=|$|((?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))new(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.)))|((?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))function((\\s+[_$[:alpha:]][_$[:alnum:]]*)|(\\s*[\\(]))))", "patterns": [ { "include": "#paren-expression" @@ -2920,7 +2966,7 @@ "name": "keyword.operator.expression.instanceof.tsx" } }, - "end": "(?<=\\))|(?=[;),}\\]:?]|\\|\\||\\&\\&|$|((?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))function((\\s+[_$[:alpha:]][_$[:alnum:]]*)|(\\s*[\\(]))))", + "end": "(?<=\\))|(?=[;),}\\]:?\\-\\+\\>]|\\|\\||\\&\\&|\\!\\=\\=|$|(([\\&\\~\\^\\|]\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s+instanceof(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.)))|((?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))function((\\s+[_$[:alpha:]][_$[:alnum:]]*)|(\\s*[\\(]))))", "patterns": [ { "include": "#type" @@ -3014,7 +3060,7 @@ } }, { - "match": "(?x)(?:(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(public|private|protected|readonly)\\s+)?(?:(\\.\\.\\.)\\s*)?(?<!=|:)(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(?:(this)|([_$[:alpha:]][_$[:alnum:]]*))(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))\\s*(\\??)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))Function(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))) |\n(:\\s*((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(public|private|protected|readonly)\\s+)?(?:(\\.\\.\\.)\\s*)?(?<!=|:)(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(?:(this)|([_$[:alpha:]][_$[:alnum:]]*))(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))\\s*(\\??)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))Function(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))) |\n(:\\s*((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.tsx" @@ -3178,7 +3224,7 @@ "name": "keyword.control.as.tsx" } }, - "end": "(?=$|^|[;,:})\\]]|\\|\\||\\&\\&|((?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(as)\\s+)|(\\s+\\<))", + "end": "(?=^|[;),}\\]:?\\-\\+\\>]|\\|\\||\\&\\&|\\!\\=\\=|$|((?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(as)\\s+)|(\\s+\\<))", "patterns": [ { "include": "#type" @@ -3210,7 +3256,7 @@ "match": "<=|>=|<>|<|>" }, { - "match": "(\\!)\\s*(/)(?![/*])", + "match": "(?<=[_$[:alnum:]])(\\!)\\s*(/)(?![/*])", "captures": { "1": { "name": "keyword.operator.logical.tsx" @@ -3571,30 +3617,6 @@ } } }, - { - "name": "support.class.node.tsx", - "match": "(?x)(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(Buffer|EventEmitter|Server|Pipe|Socket|REPLServer|ReadStream|WriteStream|Stream\n |Inflate|Deflate|InflateRaw|DeflateRaw|GZip|GUnzip|Unzip|Zip)\\b(?!\\$)" - }, - { - "match": "(?x)(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(process)(?:(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))(?:\n (arch|argv|config|connected|env|execArgv|execPath|exitCode|mainModule|pid|platform|release|stderr|stdin|stdout|title|version|versions)\n |\n (abort|chdir|cwd|disconnect|exit|[sg]ete?[gu]id|send|[sg]etgroups|initgroups|kill|memoryUsage|nextTick|umask|uptime|hrtime)\n))?\\b(?!\\$)", - "captures": { - "1": { - "name": "support.variable.object.process.tsx" - }, - "2": { - "name": "punctuation.accessor.tsx" - }, - "3": { - "name": "punctuation.accessor.optional.tsx" - }, - "4": { - "name": "support.variable.property.process.tsx" - }, - "5": { - "name": "support.function.process.tsx" - } - } - }, { "match": "(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(?:(exports)|(module)(?:(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))(exports|id|filename|loaded|parent|children))?)\\b(?!\\$)", "captures": { @@ -3614,10 +3636,6 @@ "name": "support.type.object.module.tsx" } } - }, - { - "name": "support.variable.object.node.tsx", - "match": "(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(global|GLOBAL|root|__dirname|__filename)\\b(?!\\$)" } ] }, @@ -3627,7 +3645,7 @@ "include": "#object-identifiers" }, { - "match": "(?x)(?:(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*)?([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", + "match": "(?x)(?:(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*)?([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()\\'\\\"\\`]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", "captures": { "1": { "name": "punctuation.accessor.tsx" @@ -4333,6 +4351,10 @@ "name": "keyword.operator.expression.infer.tsx", "match": "(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))infer(?=\\s+[_$[:alpha:]])" }, + { + "name": "keyword.operator.expression.awaited.tsx", + "match": "(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))awaited(?=\\s+[_$[:alpha:]])" + }, { "name": "keyword.operator.expression.import.tsx", "match": "(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))import(?=\\s*\\()" @@ -4542,12 +4564,12 @@ "patterns": [ { "name": "string.template.tsx", - "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?<!=)\\>))*(?<!=)\\>)*(?<!=)>\\s*)?`)", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)(<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?<!=)\\>))*(?<!=)\\>)*(?<!=)>\\s*)?`)", "end": "(?=`)", "patterns": [ { "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*))", - "end": "(?=(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?<!=)\\>))*(?<!=)\\>)*(?<!=)>\\s*)?`)", + "end": "(?=(<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?<!=)\\>))*(?<!=)\\>)*(?<!=)>\\s*)?`)", "patterns": [ { "include": "#support-function-call-identifiers" @@ -4565,7 +4587,7 @@ }, { "name": "string.template.tsx", - "begin": "([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?<!=)\\>))*(?<!=)\\>)*(?<!=)>\\s*)`)", + "begin": "([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?<!=)\\>))*(?<!=)\\>)*(?<!=)>\\s*)`)", "beginCaptures": { "1": { "name": "entity.name.function.tagged-template.tsx" diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index b3f12439bc8..0f37b279d8c 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -19,7 +19,7 @@ "jsonc-parser": "^2.2.1", "rimraf": "^2.6.3", "semver": "5.5.1", - "typescript-vscode-sh-plugin": "^0.6.8", + "typescript-vscode-sh-plugin": "^0.6.11", "vscode-extension-telemetry": "0.1.1", "vscode-nls": "^4.1.1" }, diff --git a/extensions/typescript-language-features/src/commands/goToProjectConfiguration.ts b/extensions/typescript-language-features/src/commands/goToProjectConfiguration.ts index 37873767085..a4fb9c33b4e 100644 --- a/extensions/typescript-language-features/src/commands/goToProjectConfiguration.ts +++ b/extensions/typescript-language-features/src/commands/goToProjectConfiguration.ts @@ -4,15 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import * as nls from 'vscode-nls'; import TypeScriptServiceClientHost from '../typeScriptServiceClientHost'; -import { nulToken } from '../utils/cancellation'; import { Command } from '../utils/commandManager'; import { Lazy } from '../utils/lazy'; -import { isImplicitProjectConfigFile, openOrCreateConfigFile } from '../utils/tsconfig'; -import { ServerResponse } from '../typescriptService'; - -const localize = nls.loadMessageBundle(); +import { openProjectConfigForFile, ProjectType } from '../utils/tsconfig'; export class TypeScriptGoToProjectConfigCommand implements Command { public readonly id = 'typescript.goToProjectConfig'; @@ -24,7 +19,7 @@ export class TypeScriptGoToProjectConfigCommand implements Command { public execute() { const editor = vscode.window.activeTextEditor; if (editor) { - goToProjectConfig(this.lazyClientHost.value, true, editor.document.uri); + openProjectConfigForFile(ProjectType.TypeScript, this.lazyClientHost.value.serviceClient, editor.document.uri); } } } @@ -39,79 +34,8 @@ export class JavaScriptGoToProjectConfigCommand implements Command { public execute() { const editor = vscode.window.activeTextEditor; if (editor) { - goToProjectConfig(this.lazyClientHost.value, false, editor.document.uri); + openProjectConfigForFile(ProjectType.JavaScript, this.lazyClientHost.value.serviceClient, editor.document.uri); } } } -async function goToProjectConfig( - clientHost: TypeScriptServiceClientHost, - isTypeScriptProject: boolean, - resource: vscode.Uri -): Promise<void> { - const client = clientHost.serviceClient; - const rootPath = client.getWorkspaceRootForResource(resource); - if (!rootPath) { - vscode.window.showInformationMessage( - localize( - 'typescript.projectConfigNoWorkspace', - 'Please open a folder in VS Code to use a TypeScript or JavaScript project')); - return; - } - - const file = client.toPath(resource); - // TSServer errors when 'projectInfo' is invoked on a non js/ts file - if (!file || !await clientHost.handles(resource)) { - vscode.window.showWarningMessage( - localize( - 'typescript.projectConfigUnsupportedFile', - 'Could not determine TypeScript or JavaScript project. Unsupported file type')); - return; - } - - let res: ServerResponse.Response<protocol.ProjectInfoResponse> | undefined; - try { - res = await client.execute('projectInfo', { file, needFileNameList: false }, nulToken); - } catch { - // noop - } - - if (res?.type !== 'response' || !res.body) { - vscode.window.showWarningMessage(localize('typescript.projectConfigCouldNotGetInfo', 'Could not determine TypeScript or JavaScript project')); - return; - } - - const { configFileName } = res.body; - if (!isImplicitProjectConfigFile(configFileName)) { - const doc = await vscode.workspace.openTextDocument(configFileName); - vscode.window.showTextDocument(doc, vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined); - return; - } - - enum ProjectConfigAction { - None, - CreateConfig, - LearnMore, - } - - interface ProjectConfigMessageItem extends vscode.MessageItem { - id: ProjectConfigAction; - } - - const selected = await vscode.window.showInformationMessage<ProjectConfigMessageItem>( - (isTypeScriptProject - ? localize('typescript.noTypeScriptProjectConfig', 'File is not part of a TypeScript project. Click [here]({0}) to learn more.', 'https://go.microsoft.com/fwlink/?linkid=841896') - : localize('typescript.noJavaScriptProjectConfig', 'File is not part of a JavaScript project Click [here]({0}) to learn more.', 'https://go.microsoft.com/fwlink/?linkid=759670') - ), { - title: isTypeScriptProject - ? localize('typescript.configureTsconfigQuickPick', 'Configure tsconfig.json') - : localize('typescript.configureJsconfigQuickPick', 'Configure jsconfig.json'), - id: ProjectConfigAction.CreateConfig, - }); - - switch (selected && selected.id) { - case ProjectConfigAction.CreateConfig: - openOrCreateConfigFile(isTypeScriptProject, rootPath, client.configuration); - return; - } -} diff --git a/extensions/typescript-language-features/src/commands/selectTypeScriptVersion.ts b/extensions/typescript-language-features/src/commands/selectTypeScriptVersion.ts index dc771df8bc2..f375b55e938 100644 --- a/extensions/typescript-language-features/src/commands/selectTypeScriptVersion.ts +++ b/extensions/typescript-language-features/src/commands/selectTypeScriptVersion.ts @@ -15,6 +15,6 @@ export class SelectTypeScriptVersionCommand implements Command { ) { } public execute() { - this.lazyClientHost.value.serviceClient.onVersionStatusClicked(); + this.lazyClientHost.value.serviceClient.showVersionPicker(); } } diff --git a/extensions/typescript-language-features/src/extension.ts b/extensions/typescript-language-features/src/extension.ts index d9f8284fd24..36783cc5e6a 100644 --- a/extensions/typescript-language-features/src/extension.ts +++ b/extensions/typescript-language-features/src/extension.ts @@ -18,7 +18,7 @@ import { lazy, Lazy } from './utils/lazy'; import LogDirectoryProvider from './utils/logDirectoryProvider'; import ManagedFileContextManager from './utils/managedFileContext'; import { PluginManager } from './utils/plugins'; -import * as ProjectStatus from './utils/projectStatus'; +import * as ProjectStatus from './utils/largeProjectStatus'; import TscTaskProvider from './features/task'; export function activate( diff --git a/extensions/typescript-language-features/src/features/completions.ts b/extensions/typescript-language-features/src/features/completions.ts index e2f8ba9fd37..6758b85b0f6 100644 --- a/extensions/typescript-language-features/src/features/completions.ts +++ b/extensions/typescript-language-features/src/features/completions.ts @@ -216,7 +216,7 @@ class MyCompletionItem extends vscode.CompletionItem { case PConst.Kind.function: case PConst.Kind.localFunction: return vscode.CompletionItemKind.Function; - case PConst.Kind.memberFunction: + case PConst.Kind.method: case PConst.Kind.constructSignature: case PConst.Kind.callSignature: case PConst.Kind.indexSignature: @@ -272,7 +272,7 @@ class MyCompletionItem extends vscode.CompletionItem { case PConst.Kind.memberVariable: case PConst.Kind.class: case PConst.Kind.function: - case PConst.Kind.memberFunction: + case PConst.Kind.method: case PConst.Kind.keyword: case PConst.Kind.parameter: commitCharacters.push('.', ',', ';'); diff --git a/extensions/typescript-language-features/src/features/directiveCommentCompletions.ts b/extensions/typescript-language-features/src/features/directiveCommentCompletions.ts index c0a222e47df..cfa35571d1d 100644 --- a/extensions/typescript-language-features/src/features/directiveCommentCompletions.ts +++ b/extensions/typescript-language-features/src/features/directiveCommentCompletions.ts @@ -6,6 +6,7 @@ import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; import { ITypeScriptServiceClient } from '../typescriptService'; +import API from '../utils/api'; const localize = nls.loadMessageBundle(); @@ -14,7 +15,7 @@ interface Directive { readonly description: string; } -const directives: Directive[] = [ +const tsDirectives: Directive[] = [ { value: '@ts-check', description: localize( @@ -33,6 +34,16 @@ const directives: Directive[] = [ } ]; +const tsDirectives390: Directive[] = [ + ...tsDirectives, + { + value: '@ts-expect-error', + description: localize( + 'ts-expect-error', + "Suppresses @ts-check errors on the next line of a file, expecting at least one to exist.") + } +]; + class DirectiveCommentCompletionProvider implements vscode.CompletionItemProvider { constructor( @@ -53,6 +64,10 @@ class DirectiveCommentCompletionProvider implements vscode.CompletionItemProvide const prefix = line.slice(0, position.character); const match = prefix.match(/^\s*\/\/+\s?(@[a-zA-Z\-]*)?$/); if (match) { + const directives = this.client.apiVersion.gte(API.v390) + ? tsDirectives390 + : tsDirectives; + return directives.map(directive => { const item = new vscode.CompletionItem(directive.value, vscode.CompletionItemKind.Snippet); item.detail = directive.description; diff --git a/extensions/typescript-language-features/src/features/documentSymbol.ts b/extensions/typescript-language-features/src/features/documentSymbol.ts index 02c1be917d3..e119b005bab 100644 --- a/extensions/typescript-language-features/src/features/documentSymbol.ts +++ b/extensions/typescript-language-features/src/features/documentSymbol.ts @@ -16,7 +16,7 @@ const getSymbolKind = (kind: string): vscode.SymbolKind => { case PConst.Kind.class: return vscode.SymbolKind.Class; case PConst.Kind.enum: return vscode.SymbolKind.Enum; case PConst.Kind.interface: return vscode.SymbolKind.Interface; - case PConst.Kind.memberFunction: return vscode.SymbolKind.Method; + case PConst.Kind.method: return vscode.SymbolKind.Method; case PConst.Kind.memberVariable: return vscode.SymbolKind.Property; case PConst.Kind.memberGetAccessor: return vscode.SymbolKind.Property; case PConst.Kind.memberSetAccessor: return vscode.SymbolKind.Property; diff --git a/extensions/typescript-language-features/src/features/implementationsCodeLens.ts b/extensions/typescript-language-features/src/features/implementationsCodeLens.ts index f7e325c819d..c6ea7ca6dee 100644 --- a/extensions/typescript-language-features/src/features/implementationsCodeLens.ts +++ b/extensions/typescript-language-features/src/features/implementationsCodeLens.ts @@ -75,7 +75,7 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip return getSymbolRange(document, item); case PConst.Kind.class: - case PConst.Kind.memberFunction: + case PConst.Kind.method: case PConst.Kind.memberVariable: case PConst.Kind.memberGetAccessor: case PConst.Kind.memberSetAccessor: diff --git a/extensions/typescript-language-features/src/features/referencesCodeLens.ts b/extensions/typescript-language-features/src/features/referencesCodeLens.ts index 7aa228f8f8e..0cf8d3f0a51 100644 --- a/extensions/typescript-language-features/src/features/referencesCodeLens.ts +++ b/extensions/typescript-language-features/src/features/referencesCodeLens.ts @@ -94,7 +94,7 @@ export class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLens case PConst.Kind.enum: return getSymbolRange(document, item); - case PConst.Kind.memberFunction: + case PConst.Kind.method: case PConst.Kind.memberGetAccessor: case PConst.Kind.memberSetAccessor: case PConst.Kind.constructorImplementation: diff --git a/extensions/typescript-language-features/src/features/rename.ts b/extensions/typescript-language-features/src/features/rename.ts index 886cf420b74..db68acf38f3 100644 --- a/extensions/typescript-language-features/src/features/rename.ts +++ b/extensions/typescript-language-features/src/features/rename.ts @@ -8,7 +8,6 @@ import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; import type * as Proto from '../protocol'; import { ITypeScriptServiceClient, ServerResponse } from '../typescriptService'; -import API from '../utils/api'; import * as typeConverters from '../utils/typeConverters'; import FileConfigurationManager from './fileConfigurationManager'; @@ -26,7 +25,7 @@ class TypeScriptRenameProvider implements vscode.RenameProvider { token: vscode.CancellationToken ): Promise<vscode.Range | null> { const response = await this.execRename(document, position, token); - if (!response || response.type !== 'response' || !response.body) { + if (response?.type !== 'response' || !response.body) { return null; } @@ -35,11 +34,9 @@ class TypeScriptRenameProvider implements vscode.RenameProvider { return Promise.reject<vscode.Range>(renameInfo.localizedErrorMessage); } - if (this.client.apiVersion.gte(API.v310)) { - const triggerSpan = renameInfo.triggerSpan; - if (triggerSpan) { - return typeConverters.Range.fromTextSpan(triggerSpan); - } + const triggerSpan = renameInfo.triggerSpan; // added in TS 3.1 + if (triggerSpan) { + return typeConverters.Range.fromTextSpan(triggerSpan); } return null; @@ -61,17 +58,15 @@ class TypeScriptRenameProvider implements vscode.RenameProvider { return Promise.reject<vscode.WorkspaceEdit>(renameInfo.localizedErrorMessage); } - - if (this.client.apiVersion.gte(API.v310)) { - if (renameInfo.fileToRename) { - const edits = await this.renameFile(renameInfo.fileToRename, newName, token); - if (edits) { - return edits; - } else { - return Promise.reject<vscode.WorkspaceEdit>(localize('fileRenameFail', "An error occurred while renaming file")); - } + if (renameInfo.fileToRename) { + const edits = await this.renameFile(renameInfo.fileToRename, newName, token); + if (edits) { + return edits; + } else { + return Promise.reject<vscode.WorkspaceEdit>(localize('fileRenameFail', "An error occurred while renaming file")); } } + return this.updateLocs(response.body.locs, newName); } @@ -104,11 +99,9 @@ class TypeScriptRenameProvider implements vscode.RenameProvider { const edit = new vscode.WorkspaceEdit(); for (const spanGroup of locations) { const resource = this.client.toResource(spanGroup.file); - if (resource) { - for (const textSpan of spanGroup.locs as Proto.RenameTextSpan[]) { - edit.replace(resource, typeConverters.Range.fromTextSpan(textSpan), - (textSpan.prefixText || '') + newName + (textSpan.suffixText || '')); - } + for (const textSpan of spanGroup.locs) { + edit.replace(resource, typeConverters.Range.fromTextSpan(textSpan), + (textSpan.prefixText || '') + newName + (textSpan.suffixText || '')); } } return edit; diff --git a/extensions/typescript-language-features/src/features/semanticTokens.ts b/extensions/typescript-language-features/src/features/semanticTokens.ts index 3a80ea9d6f7..373275a6356 100644 --- a/extensions/typescript-language-features/src/features/semanticTokens.ts +++ b/extensions/typescript-language-features/src/features/semanticTokens.ts @@ -24,6 +24,10 @@ export function register(selector: vscode.DocumentSelector, client: ITypeScriptS }); } + +// as we don't do deltas, for performance reasons, don't compute semantic tokens for documents above that limit +const CONTENT_LENGTH_LIMIT = 100000; + /** * Prototype of a DocumentSemanticTokensProvider, relying on the experimental `encodedSemanticClassifications-full` request from the TypeScript server. * As the results retured by the TypeScript server are limited, we also add a Typescript plugin (typescript-vscode-sh-plugin) to enrich the returned token. @@ -40,7 +44,7 @@ class DocumentSemanticTokensProvider implements vscode.DocumentSemanticTokensPro async provideDocumentSemanticTokens(document: vscode.TextDocument, token: vscode.CancellationToken): Promise<vscode.SemanticTokens | null> { const file = this.client.toOpenedFilePath(document); - if (!file) { + if (!file || document.getText().length > CONTENT_LENGTH_LIMIT) { return null; } return this._provideSemanticTokens(document, { file, start: 0, length: document.getText().length }, token); @@ -48,7 +52,7 @@ class DocumentSemanticTokensProvider implements vscode.DocumentSemanticTokensPro async provideDocumentRangeSemanticTokens(document: vscode.TextDocument, range: vscode.Range, token: vscode.CancellationToken): Promise<vscode.SemanticTokens | null> { const file = this.client.toOpenedFilePath(document); - if (!file) { + if (!file || document.getText().length > CONTENT_LENGTH_LIMIT) { return null; } const start = document.offsetAt(range.start); @@ -62,7 +66,7 @@ class DocumentSemanticTokensProvider implements vscode.DocumentSemanticTokensPro return null; } - const versionBeforeRequest = document.version; + let versionBeforeRequest = document.version; const response = await (this.client as ExperimentalProtocol.IExtendedTypeScriptServiceClient).execute('encodedSemanticClassifications-full', requestArg, token); if (response.type !== 'response' || !response.body) { @@ -78,6 +82,10 @@ class DocumentSemanticTokensProvider implements vscode.DocumentSemanticTokensPro // here we cannot return null, because returning null would remove all semantic tokens. // we must throw to indicate that the semantic tokens should not be removed. // using the string busy here because it is not logged to error telemetry if the error text contains busy. + + // as the new request will come in right after our response, we first wait for the document activity to stop + await waitForDocumentChangesToEnd(document); + throw new Error('busy'); } @@ -113,10 +121,24 @@ class DocumentSemanticTokensProvider implements vscode.DocumentSemanticTokensPro builder.push(line, startCharacter, endCharacter - startCharacter, tokenType, tokenModifiers); } } - return new vscode.SemanticTokens(builder.build()); + return builder.build(); } } +function waitForDocumentChangesToEnd(document: vscode.TextDocument) { + let version = document.version; + return new Promise((s) => { + let iv = setInterval(_ => { + if (document.version === version) { + clearInterval(iv); + s(); + } + version = document.version; + }, 400); + }); +} + + // typescript-vscode-sh-plugin encodes type and modifiers in the classification: // TSClassification = (TokenType + 1) << 8 + TokenModifier @@ -151,6 +173,7 @@ tokenModifiers[TokenModifier.declaration] = 'declaration'; tokenModifiers[TokenModifier.readonly] = 'readonly'; tokenModifiers[TokenModifier.static] = 'static'; tokenModifiers[TokenModifier.local] = 'local'; +tokenModifiers[TokenModifier.defaultLibrary] = 'defaultLibrary'; // make sure token types and modifiers are complete if (tokenTypes.filter(t => !!t).length !== TokenType._) { diff --git a/extensions/typescript-language-features/src/features/workspaceSymbols.ts b/extensions/typescript-language-features/src/features/workspaceSymbols.ts index eb46f166df9..e23c21eeb37 100644 --- a/extensions/typescript-language-features/src/features/workspaceSymbols.ts +++ b/extensions/typescript-language-features/src/features/workspaceSymbols.ts @@ -9,15 +9,21 @@ import { ITypeScriptServiceClient } from '../typescriptService'; import * as fileSchemes from '../utils/fileSchemes'; import { doesResourceLookLikeAJavaScriptFile, doesResourceLookLikeATypeScriptFile } from '../utils/languageDescription'; import * as typeConverters from '../utils/typeConverters'; +import * as PConst from '../protocol.const'; function getSymbolKind(item: Proto.NavtoItem): vscode.SymbolKind { switch (item.kind) { - case 'method': return vscode.SymbolKind.Method; - case 'enum': return vscode.SymbolKind.Enum; - case 'function': return vscode.SymbolKind.Function; - case 'class': return vscode.SymbolKind.Class; - case 'interface': return vscode.SymbolKind.Interface; - case 'var': return vscode.SymbolKind.Variable; + case PConst.Kind.method: return vscode.SymbolKind.Method; + case PConst.Kind.enum: return vscode.SymbolKind.Enum; + case PConst.Kind.enumMember: return vscode.SymbolKind.EnumMember; + case PConst.Kind.function: return vscode.SymbolKind.Function; + case PConst.Kind.class: return vscode.SymbolKind.Class; + case PConst.Kind.interface: return vscode.SymbolKind.Interface; + case PConst.Kind.type: return vscode.SymbolKind.Class; + case PConst.Kind.memberVariable: return vscode.SymbolKind.Field; + case PConst.Kind.memberGetAccessor: return vscode.SymbolKind.Field; + case PConst.Kind.memberSetAccessor: return vscode.SymbolKind.Field; + case PConst.Kind.variable: return vscode.SymbolKind.Variable; default: return vscode.SymbolKind.Variable; } } @@ -25,7 +31,7 @@ function getSymbolKind(item: Proto.NavtoItem): vscode.SymbolKind { class TypeScriptWorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvider { public constructor( private readonly client: ITypeScriptServiceClient, - private readonly modeIds: string[] + private readonly modeIds: readonly string[] ) { } public async provideWorkspaceSymbols( @@ -44,7 +50,8 @@ class TypeScriptWorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvide const args: Proto.NavtoRequestArgs = { file: filepath, - searchValue: search + searchValue: search, + maxResultCount: 256, }; const response = await this.client.execute('navto', args, token); @@ -52,18 +59,12 @@ class TypeScriptWorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvide return []; } - const result: vscode.SymbolInformation[] = []; - for (const item of response.body) { - if (!item.containerName && item.kind === 'alias') { - continue; - } - const label = TypeScriptWorkspaceSymbolProvider.getLabel(item); - result.push(new vscode.SymbolInformation(label, getSymbolKind(item), item.containerName || '', - typeConverters.Location.fromTextSpan(this.client.toResource(item.file), item))); - } - return result; + return response.body + .filter(item => item.containerName || item.kind !== 'alias') + .map(item => this.toSymbolInformation(item)); } + private async toOpenedFiledPath(document: vscode.TextDocument) { if (document.uri.scheme === fileSchemes.git) { try { @@ -79,6 +80,15 @@ class TypeScriptWorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvide return this.client.toOpenedFilePath(document); } + private toSymbolInformation(item: Proto.NavtoItem) { + const label = TypeScriptWorkspaceSymbolProvider.getLabel(item); + return new vscode.SymbolInformation( + label, + getSymbolKind(item), + item.containerName || '', + typeConverters.Location.fromTextSpan(this.client.toResource(item.file), item)); + } + private static getLabel(item: Proto.NavtoItem) { const label = item.name; if (item.kind === 'method' || item.kind === 'function') { @@ -111,7 +121,8 @@ class TypeScriptWorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvide export function register( client: ITypeScriptServiceClient, - modeIds: string[], + modeIds: readonly string[], ) { - return vscode.languages.registerWorkspaceSymbolProvider(new TypeScriptWorkspaceSymbolProvider(client, modeIds)); + return vscode.languages.registerWorkspaceSymbolProvider( + new TypeScriptWorkspaceSymbolProvider(client, modeIds)); } diff --git a/extensions/typescript-language-features/src/protocol.const.ts b/extensions/typescript-language-features/src/protocol.const.ts index a44c175f295..37fe42fd830 100644 --- a/extensions/typescript-language-features/src/protocol.const.ts +++ b/extensions/typescript-language-features/src/protocol.const.ts @@ -21,7 +21,7 @@ export class Kind { public static readonly let = 'let'; public static readonly localFunction = 'local function'; public static readonly localVariable = 'local var'; - public static readonly memberFunction = 'method'; + public static readonly method = 'method'; public static readonly memberGetAccessor = 'getter'; public static readonly memberSetAccessor = 'setter'; public static readonly memberVariable = 'property'; diff --git a/extensions/typescript-language-features/src/typeScriptServiceClientHost.ts b/extensions/typescript-language-features/src/typeScriptServiceClientHost.ts index e427aa59216..e972c339d3e 100644 --- a/extensions/typescript-language-features/src/typeScriptServiceClientHost.ts +++ b/extensions/typescript-language-features/src/typeScriptServiceClientHost.ts @@ -15,6 +15,7 @@ import LanguageProvider from './languageProvider'; import * as Proto from './protocol'; import * as PConst from './protocol.const'; import TypeScriptServiceClient from './typescriptServiceClient'; +import { coalesce, flatten } from './utils/arrays'; import { CommandManager } from './utils/commandManager'; import { Disposable } from './utils/dispose'; import { DiagnosticLanguage, LanguageDescription } from './utils/languageDescription'; @@ -23,7 +24,6 @@ import { PluginManager } from './utils/plugins'; import * as typeConverters from './utils/typeConverters'; import TypingsStatus, { AtaProgressReporter } from './utils/typingsStatus'; import VersionStatus from './utils/versionStatus'; -import { flatten, coalesce } from './utils/arrays'; // Style check diagnostics that can be reported as warnings const styleCheckDiagnostics = [ @@ -37,11 +37,14 @@ const styleCheckDiagnostics = [ ]; export default class TypeScriptServiceClientHost extends Disposable { - private readonly typingsStatus: TypingsStatus; + private readonly client: TypeScriptServiceClient; private readonly languages: LanguageProvider[] = []; private readonly languagePerId = new Map<string, LanguageProvider>(); + + private readonly typingsStatus: TypingsStatus; private readonly versionStatus: VersionStatus; + private readonly fileConfigurationManager: FileConfigurationManager; private reportStyleCheckAsWarnings: boolean = true; @@ -71,7 +74,7 @@ export default class TypeScriptServiceClientHost extends Disposable { this.client.onConfigDiagnosticsReceived(diag => this.configFileDiagnosticsReceived(diag), null, this._disposables); this.client.onResendModelsRequested(() => this.populateService(), null, this._disposables); - this.versionStatus = this._register(new VersionStatus(resource => this.client.toPath(resource))); + this.versionStatus = this._register(new VersionStatus(this.client, commandManager)); this._register(new AtaProgressReporter(this.client)); this.typingsStatus = this._register(new TypingsStatus(this.client)); diff --git a/extensions/typescript-language-features/src/typescriptService.ts b/extensions/typescript-language-features/src/typescriptService.ts index 51e5cab5c5b..eb6ff651b30 100644 --- a/extensions/typescript-language-features/src/typescriptService.ts +++ b/extensions/typescript-language-features/src/typescriptService.ts @@ -8,7 +8,6 @@ import BufferSyncSupport from './features/bufferSyncSupport'; import * as Proto from './protocol'; import API from './utils/api'; import { TypeScriptServiceConfiguration } from './utils/configuration'; -import Logger from './utils/logger'; import { PluginManager } from './utils/plugins'; export namespace ServerResponse { @@ -120,10 +119,13 @@ export interface ITypeScriptServiceClient { readonly onDidEndInstallTypings: vscode.Event<Proto.EndInstallTypesEventBody>; readonly onTypesInstallerInitializationFailed: vscode.Event<Proto.TypesInstallerInitializationFailedEventBody>; + onReady(f: () => void): Promise<void>; + + showVersionPicker(): void; + readonly apiVersion: API; readonly pluginManager: PluginManager; readonly configuration: TypeScriptServiceConfiguration; - readonly logger: Logger; readonly bufferSyncSupport: BufferSyncSupport; execute<K extends keyof StandardTsServerRequests>( diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 64527b2918e..a8e36109503 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -24,8 +24,8 @@ import { TypeScriptPluginPathsProvider } from './utils/pluginPathsProvider'; import { PluginManager } from './utils/plugins'; import { TelemetryReporter, VSCodeTelemetryReporter, TelemetryProperties } from './utils/telemetry'; import Tracer from './utils/tracer'; -import { inferredProjectCompilerOptions } from './utils/tsconfig'; -import { TypeScriptVersionPicker } from './utils/versionPicker'; +import { inferredProjectCompilerOptions, ProjectType } from './utils/tsconfig'; +import { TypeScriptVersionManager } from './utils/versionManager'; import { TypeScriptVersion, TypeScriptVersionProvider } from './utils/versionProvider'; const localize = nls.loadMessageBundle(); @@ -99,15 +99,16 @@ export default class TypeScriptServiceClient extends Disposable implements IType private _configuration: TypeScriptServiceConfiguration; private versionProvider: TypeScriptVersionProvider; private pluginPathsProvider: TypeScriptPluginPathsProvider; - private versionPicker: TypeScriptVersionPicker; + private readonly _versionManager: TypeScriptVersionManager; - private tracer: Tracer; - public readonly logger: Logger = new Logger(); + private readonly logger = new Logger(); + private readonly tracer = new Tracer(this.logger); private readonly typescriptServerSpawner: TypeScriptServerSpawner; private serverState: ServerState.State = ServerState.None; private lastStart: number; private numberRestarts: number; + private _isPromptingAfterCrash = false; private isRestarting: boolean = false; private hasServerFatallyCrashedTooManyTimes = false; private readonly loadingIndicator = new ServerInitializingIndicator(); @@ -139,9 +140,10 @@ export default class TypeScriptServiceClient extends Disposable implements IType this._configuration = TypeScriptServiceConfiguration.loadFromWorkspace(); this.versionProvider = new TypeScriptVersionProvider(this._configuration); this.pluginPathsProvider = new TypeScriptPluginPathsProvider(this._configuration); - this.versionPicker = new TypeScriptVersionPicker(this.versionProvider, this.workspaceState); - - this.tracer = new Tracer(this.logger); + this._versionManager = this._register(new TypeScriptVersionManager(this.versionProvider, this.workspaceState)); + this._register(this._versionManager.onDidPickNewVersion(() => { + this.restartTsServer(); + })); this.bufferSyncSupport = new BufferSyncSupport(this, allModeIds); this.onReady(() => { this.bufferSyncSupport.listen(); }); @@ -311,23 +313,23 @@ export default class TypeScriptServiceClient extends Disposable implements IType return ServerState.None; } - let currentVersion = this.versionPicker.currentVersion; + let version = this._versionManager.currentVersion; - this.info(`Using tsserver from: ${currentVersion.path}`); - if (!fs.existsSync(currentVersion.tsServerPath)) { - vscode.window.showWarningMessage(localize('noServerFound', 'The path {0} doesn\'t point to a valid tsserver install. Falling back to bundled TypeScript version.', currentVersion.path)); + this.info(`Using tsserver from: ${version.path}`); + if (!fs.existsSync(version.tsServerPath)) { + vscode.window.showWarningMessage(localize('noServerFound', 'The path {0} doesn\'t point to a valid tsserver install. Falling back to bundled TypeScript version.', version.path)); - this.versionPicker.useBundledVersion(); - currentVersion = this.versionPicker.currentVersion; + this._versionManager.reset(); + version = this._versionManager.currentVersion; } - const apiVersion = this.versionPicker.currentVersion.apiVersion || API.defaultVersion; - this.onDidChangeTypeScriptVersion(currentVersion); + const apiVersion = version.apiVersion || API.defaultVersion; let mytoken = ++this.token; - const handle = this.typescriptServerSpawner.spawn(currentVersion, this.configuration, this.pluginManager, { + const handle = this.typescriptServerSpawner.spawn(version, this.configuration, this.pluginManager, { onFatalError: (command, err) => this.fatalError(command, err), }); this.serverState = new ServerState.Running(handle, apiVersion, undefined, true); + this.onDidChangeTypeScriptVersion(version); this.lastStart = Date.now(); /* __GDPR__ @@ -341,7 +343,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType */ this.logTelemetry('tsserver.spawned', { localTypeScriptVersion: this.versionProvider.localVersion ? this.versionProvider.localVersion.displayName : '', - typeScriptVersionSource: currentVersion.source, + typeScriptVersionSource: version.source, }); handle.onError((err: Error) => { @@ -403,7 +405,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType handle.onEvent(event => this.dispatchEvent(event)); this._onReady!.resolve(); - this._onTsServerStarted.fire(currentVersion.apiVersion); + this._onTsServerStarted.fire(version.apiVersion); if (apiVersion.gte(API.v300)) { this.loadingIndicator.startedLoadingProject(undefined /* projectName */); @@ -414,16 +416,8 @@ export default class TypeScriptServiceClient extends Disposable implements IType return this.serverState; } - public onVersionStatusClicked(): Thenable<void> { - return this.showVersionPicker(); - } - - private showVersionPicker(): Thenable<void> { - return this.versionPicker.show().then(change => { - if (change.newVersion && change.oldVersion && change.oldVersion.eq(change.newVersion)) { - this.restartTsServer(); - } - }); + public async showVersionPicker(): Promise<void> { + this._versionManager.promptUserForVersion(); } public async openTsServerLogFile(): Promise<boolean> { @@ -512,24 +506,17 @@ export default class TypeScriptServiceClient extends Disposable implements IType private getCompilerOptionsForInferredProjects(configuration: TypeScriptServiceConfiguration): Proto.ExternalProjectCompilerOptions { return { - ...inferredProjectCompilerOptions(true, configuration), + ...inferredProjectCompilerOptions(ProjectType.TypeScript, configuration), allowJs: true, allowSyntheticDefaultImports: true, allowNonTsExtensions: true, + resolveJsonModule: true, }; } private serviceExited(restart: boolean): void { this.loadingIndicator.reset(); - enum MessageAction { - reportIssue - } - - interface MyMessageItem extends vscode.MessageItem { - id: MessageAction; - } - const previousState = this.serverState; this.serverState = ServerState.None; @@ -537,19 +524,22 @@ export default class TypeScriptServiceClient extends Disposable implements IType const diff = Date.now() - this.lastStart; this.numberRestarts++; let startService = true; + + const reportIssueItem: vscode.MessageItem = { + title: localize('serverDiedReportIssue', 'Report Issue'), + }; + let prompt: Thenable<undefined | vscode.MessageItem> | undefined = undefined; + if (this.numberRestarts > 5) { - let prompt: Thenable<MyMessageItem | undefined> | undefined = undefined; this.numberRestarts = 0; if (diff < 10 * 1000 /* 10 seconds */) { this.lastStart = Date.now(); startService = false; this.hasServerFatallyCrashedTooManyTimes = true; - prompt = vscode.window.showErrorMessage<MyMessageItem>( + prompt = vscode.window.showErrorMessage( localize('serverDiedAfterStart', 'The TypeScript language service died 5 times right after it got started. The service will not be restarted.'), - { - title: localize('serverDiedReportIssue', 'Report Issue'), - id: MessageAction.reportIssue, - }); + reportIssueItem); + /* __GDPR__ "serviceExited" : { "${include}": [ @@ -560,25 +550,32 @@ 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<MyMessageItem>( + prompt = vscode.window.showWarningMessage( localize('serverDied', 'The TypeScript language service died unexpectedly 5 times in the last 5 Minutes.'), - { - title: localize('serverDiedReportIssue', 'Report Issue'), - id: MessageAction.reportIssue - }); + reportIssueItem); } - if (prompt) { - prompt.then(item => { - if (item?.id === MessageAction.reportIssue) { - const args = previousState.type === ServerState.Type.Errored && previousState.error instanceof TypeScriptServerError - ? getReportIssueArgsForError(previousState.error) - : undefined; - return vscode.commands.executeCommand('workbench.action.openIssueReporter', args); - } - return undefined; - }); + } 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; + prompt = vscode.window.showWarningMessage( + localize('serverDiedOnce', 'The TypeScript language service died unexpectedly.'), + reportIssueItem); } } + + prompt?.then(item => { + this._isPromptingAfterCrash = false; + + if (item === reportIssueItem) { + const args = previousState.type === ServerState.Type.Errored && previousState.error instanceof TypeScriptServerError + ? getReportIssueArgsForError(previousState.error) + : undefined; + vscode.commands.executeCommand('workbench.action.openIssueReporter', args); + } + }); + if (startService) { this.startService(true); } @@ -876,7 +873,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType } } -function getReportIssueArgsForError(error: TypeScriptServerError): { issueTitle: string, issueBody: string } | undefined { +function getReportIssueArgsForError(error: TypeScriptServerError): { extensionId: string, issueTitle: string, issueBody: string } | undefined { if (!error.serverStack || !error.serverMessage) { return undefined; } @@ -884,6 +881,7 @@ function getReportIssueArgsForError(error: TypeScriptServerError): { issueTitle: // Note these strings are intentionally not localized // as we want users to file issues in english return { + extensionId: 'vscode.typescript-language-features', issueTitle: `TS Server fatal error: ${error.serverMessage}`, issueBody: `**TypeScript Version:** ${error.version.apiVersion?.fullVersionString} diff --git a/extensions/typescript-language-features/src/utils/api.ts b/extensions/typescript-language-features/src/utils/api.ts index 25be4b5e516..1845285caa4 100644 --- a/extensions/typescript-language-features/src/utils/api.ts +++ b/extensions/typescript-language-features/src/utils/api.ts @@ -33,6 +33,7 @@ export default class API { public static readonly v350 = API.fromSimpleString('3.5.0'); public static readonly v380 = API.fromSimpleString('3.8.0'); public static readonly v381 = API.fromSimpleString('3.8.1'); + public static readonly v390 = API.fromSimpleString('3.9.0'); public static fromVersionString(versionString: string): API { let version = semver.valid(versionString); diff --git a/extensions/typescript-language-features/src/utils/projectStatus.ts b/extensions/typescript-language-features/src/utils/largeProjectStatus.ts similarity index 95% rename from extensions/typescript-language-features/src/utils/projectStatus.ts rename to extensions/typescript-language-features/src/utils/largeProjectStatus.ts index b9a3b2328c7..f820101f1b1 100644 --- a/extensions/typescript-language-features/src/utils/projectStatus.ts +++ b/extensions/typescript-language-features/src/utils/largeProjectStatus.ts @@ -7,7 +7,7 @@ import * as vscode from 'vscode'; import { loadMessageBundle } from 'vscode-nls'; import { ITypeScriptServiceClient } from '../typescriptService'; import { TelemetryReporter } from './telemetry'; -import { isImplicitProjectConfigFile, openOrCreateConfigFile } from './tsconfig'; +import { isImplicitProjectConfigFile, openOrCreateConfig, ProjectType } from './tsconfig'; const localize = loadMessageBundle(); @@ -101,8 +101,8 @@ function onConfigureExcludesSelected( } else { const root = client.getWorkspaceRootForResource(vscode.Uri.file(configFileName)); if (root) { - openOrCreateConfigFile( - configFileName.match(/tsconfig\.?.*\.json/) !== null, + openOrCreateConfig( + /tsconfig\.?.*\.json/.test(configFileName) ? ProjectType.TypeScript : ProjectType.JavaScript, root, client.configuration); } diff --git a/extensions/typescript-language-features/src/utils/tsconfig.ts b/extensions/typescript-language-features/src/utils/tsconfig.ts index b22b4e31aef..f6802ca3ccc 100644 --- a/extensions/typescript-language-features/src/utils/tsconfig.ts +++ b/extensions/typescript-language-features/src/utils/tsconfig.ts @@ -5,15 +5,25 @@ import * as path from 'path'; import * as vscode from 'vscode'; +import * as nls from 'vscode-nls'; import type * as Proto from '../protocol'; +import { ITypeScriptServiceClient, ServerResponse } from '../typescriptService'; +import { nulToken } from '../utils/cancellation'; import { TypeScriptServiceConfiguration } from './configuration'; +const localize = nls.loadMessageBundle(); + +export const enum ProjectType { + TypeScript, + JavaScript, +} + export function isImplicitProjectConfigFile(configFileName: string) { return configFileName.startsWith('/dev/null/'); } export function inferredProjectCompilerOptions( - isTypeScriptProject: boolean, + projectType: ProjectType, serviceConfig: TypeScriptServiceConfiguration, ): Proto.ExternalProjectCompilerOptions { const projectConfig: Proto.ExternalProjectCompilerOptions = { @@ -24,7 +34,7 @@ export function inferredProjectCompilerOptions( if (serviceConfig.checkJs) { projectConfig.checkJs = true; - if (isTypeScriptProject) { + if (projectType === ProjectType.TypeScript) { projectConfig.allowJs = true; } } @@ -33,7 +43,7 @@ export function inferredProjectCompilerOptions( projectConfig.experimentalDecorators = true; } - if (isTypeScriptProject) { + if (projectType === ProjectType.TypeScript) { projectConfig.sourceMap = true; } @@ -41,10 +51,10 @@ export function inferredProjectCompilerOptions( } function inferredProjectConfigSnippet( - isTypeScriptProject: boolean, + projectType: ProjectType, config: TypeScriptServiceConfiguration ) { - const baseConfig = inferredProjectCompilerOptions(isTypeScriptProject, config); + const baseConfig = inferredProjectCompilerOptions(projectType, config); const compilerOptions = Object.keys(baseConfig).map(key => `"${key}": ${JSON.stringify(baseConfig[key])}`); return new vscode.SnippetString(`{ "compilerOptions": { @@ -57,13 +67,13 @@ function inferredProjectConfigSnippet( }`); } -export async function openOrCreateConfigFile( - isTypeScriptProject: boolean, +export async function openOrCreateConfig( + projectType: ProjectType, rootPath: string, - config: TypeScriptServiceConfiguration + configuration: TypeScriptServiceConfiguration, ): Promise<vscode.TextEditor | null> { - const configFile = vscode.Uri.file(path.join(rootPath, isTypeScriptProject ? 'tsconfig.json' : 'jsconfig.json')); - const col = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined; + const configFile = vscode.Uri.file(path.join(rootPath, projectType === ProjectType.TypeScript ? 'tsconfig.json' : 'jsconfig.json')); + const col = vscode.window.activeTextEditor?.viewColumn; try { const doc = await vscode.workspace.openTextDocument(configFile); return vscode.window.showTextDocument(doc, col); @@ -71,8 +81,79 @@ export async function openOrCreateConfigFile( const doc = await vscode.workspace.openTextDocument(configFile.with({ scheme: 'untitled' })); const editor = await vscode.window.showTextDocument(doc, col); if (editor.document.getText().length === 0) { - await editor.insertSnippet(inferredProjectConfigSnippet(isTypeScriptProject, config)); + await editor.insertSnippet(inferredProjectConfigSnippet(projectType, configuration)); } return editor; } } + +export async function openProjectConfigOrPromptToCreate( + projectType: ProjectType, + client: ITypeScriptServiceClient, + rootPath: string, + configFileName: string, +): Promise<void> { + if (!isImplicitProjectConfigFile(configFileName)) { + const doc = await vscode.workspace.openTextDocument(configFileName); + vscode.window.showTextDocument(doc, vscode.window.activeTextEditor?.viewColumn); + return; + } + + const CreateConfigItem: vscode.MessageItem = { + title: projectType === ProjectType.TypeScript + ? localize('typescript.configureTsconfigQuickPick', 'Configure tsconfig.json') + : localize('typescript.configureJsconfigQuickPick', 'Configure jsconfig.json'), + }; + + const selected = await vscode.window.showInformationMessage( + (projectType === ProjectType.TypeScript + ? localize('typescript.noTypeScriptProjectConfig', 'File is not part of a TypeScript project. Click [here]({0}) to learn more.', 'https://go.microsoft.com/fwlink/?linkid=841896') + : localize('typescript.noJavaScriptProjectConfig', 'File is not part of a JavaScript project Click [here]({0}) to learn more.', 'https://go.microsoft.com/fwlink/?linkid=759670') + ), + CreateConfigItem); + + switch (selected) { + case CreateConfigItem: + openOrCreateConfig(projectType, rootPath, client.configuration); + return; + } +} + +export async function openProjectConfigForFile( + projectType: ProjectType, + client: ITypeScriptServiceClient, + resource: vscode.Uri, +): Promise<void> { + const rootPath = client.getWorkspaceRootForResource(resource); + if (!rootPath) { + vscode.window.showInformationMessage( + localize( + 'typescript.projectConfigNoWorkspace', + 'Please open a folder in VS Code to use a TypeScript or JavaScript project')); + return; + } + + const file = client.toPath(resource); + // TSServer errors when 'projectInfo' is invoked on a non js/ts file + if (!file || !await client.toPath(resource)) { + vscode.window.showWarningMessage( + localize( + 'typescript.projectConfigUnsupportedFile', + 'Could not determine TypeScript or JavaScript project. Unsupported file type')); + return; + } + + let res: ServerResponse.Response<protocol.ProjectInfoResponse> | undefined; + try { + res = await client.execute('projectInfo', { file, needFileNameList: false }, nulToken); + } catch { + // noop + } + + if (res?.type !== 'response' || !res.body) { + vscode.window.showWarningMessage(localize('typescript.projectConfigCouldNotGetInfo', 'Could not determine TypeScript or JavaScript project')); + return; + } + return openProjectConfigOrPromptToCreate(projectType, client, rootPath, res.body.configFileName); +} + diff --git a/extensions/typescript-language-features/src/utils/typeConverters.ts b/extensions/typescript-language-features/src/utils/typeConverters.ts index 0026eaa1232..78333b2da0b 100644 --- a/extensions/typescript-language-features/src/utils/typeConverters.ts +++ b/extensions/typescript-language-features/src/utils/typeConverters.ts @@ -107,7 +107,7 @@ export namespace SymbolKind { case PConst.Kind.interface: return vscode.SymbolKind.Interface; case PConst.Kind.indexSignature: return vscode.SymbolKind.Method; case PConst.Kind.callSignature: return vscode.SymbolKind.Method; - case PConst.Kind.memberFunction: return vscode.SymbolKind.Method; + case PConst.Kind.method: return vscode.SymbolKind.Method; case PConst.Kind.memberVariable: return vscode.SymbolKind.Property; case PConst.Kind.memberGetAccessor: return vscode.SymbolKind.Property; case PConst.Kind.memberSetAccessor: return vscode.SymbolKind.Property; diff --git a/extensions/typescript-language-features/src/utils/versionManager.ts b/extensions/typescript-language-features/src/utils/versionManager.ts new file mode 100644 index 00000000000..254f2deb008 --- /dev/null +++ b/extensions/typescript-language-features/src/utils/versionManager.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 * as nls from 'vscode-nls'; +import { TypeScriptVersion, TypeScriptVersionProvider } from './versionProvider'; +import { Disposable } from './dispose'; + +const localize = nls.loadMessageBundle(); + +const useWorkspaceTsdkStorageKey = 'typescript.useWorkspaceTsdk'; + +interface QuickPickItem extends vscode.QuickPickItem { + run(): void; +} + +export class TypeScriptVersionManager extends Disposable { + + private _currentVersion: TypeScriptVersion; + + public constructor( + private readonly versionProvider: TypeScriptVersionProvider, + private readonly workspaceState: vscode.Memento + ) { + super(); + + this._currentVersion = this.versionProvider.defaultVersion; + + if (this.useWorkspaceTsdkSetting) { + const localVersion = this.versionProvider.localVersion; + if (localVersion) { + this._currentVersion = localVersion; + } + } + } + + private readonly _onDidPickNewVersion = this._register(new vscode.EventEmitter<void>()); + public readonly onDidPickNewVersion = this._onDidPickNewVersion.event; + + public get currentVersion(): TypeScriptVersion { + return this._currentVersion; + } + + public reset(): void { + this._currentVersion = this.versionProvider.bundledVersion; + } + + public async promptUserForVersion(): Promise<void> { + const selected = await vscode.window.showQuickPick<QuickPickItem>([ + this.getBundledPickItem(), + ...this.getLocalPickItems(), + LearnMorePickItem, + ], { + placeHolder: localize( + 'selectTsVersion', + "Select the TypeScript version used for JavaScript and TypeScript language features"), + }); + + return selected?.run(); + } + + private getBundledPickItem(): QuickPickItem { + const bundledVersion = this.versionProvider.defaultVersion; + return { + label: (!this.useWorkspaceTsdkSetting + ? '• ' + : '') + localize('useVSCodeVersionOption', "Use VS Code's Version"), + description: bundledVersion.displayName, + detail: bundledVersion.pathLabel, + run: async () => { + await this.workspaceState.update(useWorkspaceTsdkStorageKey, false); + this.updateForPickedVersion(bundledVersion); + }, + }; + } + + private getLocalPickItems(): QuickPickItem[] { + return this.versionProvider.localVersions.map(version => { + return { + label: (this.useWorkspaceTsdkSetting && this.currentVersion.eq(version) + ? '• ' + : '') + localize('useWorkspaceVersionOption', "Use Workspace Version"), + description: version.displayName, + detail: version.pathLabel, + run: async () => { + await this.workspaceState.update(useWorkspaceTsdkStorageKey, true); + const tsConfig = vscode.workspace.getConfiguration('typescript'); + await tsConfig.update('tsdk', version.pathLabel, false); + this.updateForPickedVersion(version); + }, + }; + }); + } + + private updateForPickedVersion(pickedVersion: TypeScriptVersion) { + const oldVersion = this.currentVersion; + this._currentVersion = pickedVersion; + if (!oldVersion.eq(pickedVersion)) { + this._onDidPickNewVersion.fire(); + } + } + + private get useWorkspaceTsdkSetting(): boolean { + return this.workspaceState.get<boolean>(useWorkspaceTsdkStorageKey, false); + } +} + +const LearnMorePickItem: QuickPickItem = { + label: localize('learnMore', 'Learn more about managing TypeScript versions'), + description: '', + run: () => { + vscode.env.openExternal(vscode.Uri.parse('https://go.microsoft.com/fwlink/?linkid=839919')); + } +}; diff --git a/extensions/typescript-language-features/src/utils/versionPicker.ts b/extensions/typescript-language-features/src/utils/versionPicker.ts deleted file mode 100644 index de042b3b01a..00000000000 --- a/extensions/typescript-language-features/src/utils/versionPicker.ts +++ /dev/null @@ -1,123 +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 { TypeScriptVersion, TypeScriptVersionProvider } from './versionProvider'; - -const localize = nls.loadMessageBundle(); - -const useWorkspaceTsdkStorageKey = 'typescript.useWorkspaceTsdk'; - -interface MyQuickPickItem extends vscode.QuickPickItem { - id: MessageAction; - version?: TypeScriptVersion; -} - -enum MessageAction { - useLocal, - useBundled, - learnMore, -} - -export class TypeScriptVersionPicker { - private _currentVersion: TypeScriptVersion; - - public constructor( - private readonly versionProvider: TypeScriptVersionProvider, - private readonly workspaceState: vscode.Memento - ) { - this._currentVersion = this.versionProvider.defaultVersion; - - if (this.useWorkspaceTsdkSetting) { - const localVersion = this.versionProvider.localVersion; - if (localVersion) { - this._currentVersion = localVersion; - } - } - } - - public get useWorkspaceTsdkSetting(): boolean { - return this.workspaceState.get<boolean>(useWorkspaceTsdkStorageKey, false); - } - - public get currentVersion(): TypeScriptVersion { - return this._currentVersion; - } - - public useBundledVersion(): void { - this._currentVersion = this.versionProvider.bundledVersion; - } - - public async show(firstRun?: boolean): Promise<{ oldVersion?: TypeScriptVersion, newVersion?: TypeScriptVersion }> { - const pickOptions: MyQuickPickItem[] = []; - - const shippedVersion = this.versionProvider.defaultVersion; - pickOptions.push({ - label: (!this.useWorkspaceTsdkSetting - ? '• ' - : '') + localize('useVSCodeVersionOption', "Use VS Code's Version"), - description: shippedVersion.displayName, - detail: shippedVersion.pathLabel, - id: MessageAction.useBundled, - }); - - for (const version of this.versionProvider.localVersions) { - pickOptions.push({ - label: (this.useWorkspaceTsdkSetting && this.currentVersion.eq(version) - ? '• ' - : '') + localize('useWorkspaceVersionOption', "Use Workspace Version"), - description: version.displayName, - detail: version.pathLabel, - id: MessageAction.useLocal, - version - }); - } - - pickOptions.push({ - label: localize('learnMore', 'Learn More'), - description: '', - id: MessageAction.learnMore - }); - - const selected = await vscode.window.showQuickPick<MyQuickPickItem>(pickOptions, { - placeHolder: localize( - 'selectTsVersion', - "Select the TypeScript version used for JavaScript and TypeScript language features"), - ignoreFocusOut: firstRun, - }); - - if (!selected) { - return { oldVersion: this.currentVersion }; - } - - switch (selected.id) { - case MessageAction.useLocal: - await this.workspaceState.update(useWorkspaceTsdkStorageKey, true); - if (selected.version) { - const tsConfig = vscode.workspace.getConfiguration('typescript'); - await tsConfig.update('tsdk', selected.version.pathLabel, false); - - const previousVersion = this.currentVersion; - this._currentVersion = selected.version; - return { oldVersion: previousVersion, newVersion: selected.version }; - } - return { oldVersion: this.currentVersion }; - - case MessageAction.useBundled: - await this.workspaceState.update(useWorkspaceTsdkStorageKey, false); - const previousVersion = this.currentVersion; - this._currentVersion = shippedVersion; - return { oldVersion: previousVersion, newVersion: shippedVersion }; - - case MessageAction.learnMore: - vscode.env.openExternal(vscode.Uri.parse('https://go.microsoft.com/fwlink/?linkid=839919')); - return { oldVersion: this.currentVersion }; - - default: - return { oldVersion: this.currentVersion }; - } - } -} diff --git a/extensions/typescript-language-features/src/utils/versionProvider.ts b/extensions/typescript-language-features/src/utils/versionProvider.ts index 8023631c46d..742efda6d28 100644 --- a/extensions/typescript-language-features/src/utils/versionProvider.ts +++ b/extensions/typescript-language-features/src/utils/versionProvider.ts @@ -12,7 +12,7 @@ import { RelativeWorkspacePathResolver } from './relativePathResolver'; const localize = nls.loadMessageBundle(); -export const enum TypeScriptVersionSource { +const enum TypeScriptVersionSource { Bundled = 'bundled', TsNightlyExtension = 'ts-nightly-extension', NodeModules = 'node-modules', @@ -227,7 +227,7 @@ export class TypeScriptVersionProvider { const versions: TypeScriptVersion[] = []; for (const root of vscode.workspace.workspaceFolders) { let label: string = relativePath; - if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 1) { + if (vscode.workspace.workspaceFolders.length > 1) { label = path.join(root.name, relativePath); } diff --git a/extensions/typescript-language-features/src/utils/versionStatus.ts b/extensions/typescript-language-features/src/utils/versionStatus.ts index 9d6ebeda69e..e0b388e9eb1 100644 --- a/extensions/typescript-language-features/src/utils/versionStatus.ts +++ b/extensions/typescript-language-features/src/utils/versionStatus.ts @@ -4,50 +4,189 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import * as languageModeIds from './languageModeIds'; -import { TypeScriptVersion } from './versionProvider'; -import { Disposable } from './dispose'; import * as nls from 'vscode-nls'; +import { ITypeScriptServiceClient } from '../typescriptService'; +import { coalesce } from '../utils/arrays'; +import { Command, CommandManager } from '../utils/commandManager'; +import { isTypeScriptDocument } from '../utils/languageModeIds'; +import { isImplicitProjectConfigFile, openOrCreateConfig, openProjectConfigOrPromptToCreate, openProjectConfigForFile, ProjectType } from '../utils/tsconfig'; +import { Disposable } from './dispose'; +import { TypeScriptVersion } from './versionProvider'; const localize = nls.loadMessageBundle(); + +namespace ProjectInfoState { + export const enum Type { None, Pending, Resolved } + + export const None = Object.freeze({ type: Type.None } as const); + + export class Pending { + public readonly type = Type.Pending; + + public readonly cancellation = new vscode.CancellationTokenSource(); + + constructor( + public readonly resource: vscode.Uri, + ) { } + } + + export class Resolved { + public readonly type = Type.Resolved; + + constructor( + public readonly resource: vscode.Uri, + public readonly configFile: string, + ) { } + } + + export type State = typeof None | Pending | Resolved; +} + +interface QuickPickItem extends vscode.QuickPickItem { + run(): void; +} + +class ProjectStatusCommand implements Command { + public readonly id = '_typescript.projectStatus'; + + public constructor( + private readonly _client: ITypeScriptServiceClient, + private readonly _delegate: () => ProjectInfoState.State, + ) { } + + public async execute(): Promise<void> { + const info = this._delegate(); + + + const result = await vscode.window.showQuickPick<QuickPickItem>(coalesce([ + this.getProjectItem(info), + this.getVersionItem(), + this.getHelpItem(), + ]), { + placeHolder: localize('projectQuickPick.placeholder', "TypeScript Project Info"), + }); + + return result?.run(); + } + + private getVersionItem(): QuickPickItem { + return { + label: localize('projectQuickPick.version.label', "Select TypeScript Version..."), + description: this._client.apiVersion.displayName, + run: () => { + this._client.showVersionPicker(); + } + }; + } + + private getProjectItem(info: ProjectInfoState.State): QuickPickItem | undefined { + const rootPath = info.type === ProjectInfoState.Type.Resolved ? this._client.getWorkspaceRootForResource(info.resource) : undefined; + if (!rootPath) { + return undefined; + } + + if (info.type === ProjectInfoState.Type.Resolved) { + if (isImplicitProjectConfigFile(info.configFile)) { + return { + label: localize('projectQuickPick.project.create', "Create tsconfig"), + detail: localize('projectQuickPick.project.create.description', "This file is currently not part of a tsconfig/jsconfig project"), + run: () => { + openOrCreateConfig(ProjectType.TypeScript, rootPath, this._client.configuration); + } + }; + } + } + + return { + label: localize('projectQuickPick.version.goProjectConfig', "Open tsconfig"), + description: info.type === ProjectInfoState.Type.Resolved ? vscode.workspace.asRelativePath(info.configFile) : undefined, + run: () => { + if (info.type === ProjectInfoState.Type.Resolved) { + openProjectConfigOrPromptToCreate(ProjectType.TypeScript, this._client, rootPath, info.configFile); + } else if (info.type === ProjectInfoState.Type.Pending) { + openProjectConfigForFile(ProjectType.TypeScript, this._client, info.resource); + } + } + }; + } + + private getHelpItem(): QuickPickItem { + return { + label: localize('projectQuickPick.help', "TypeScript help"), + run: () => { + vscode.env.openExternal(vscode.Uri.parse('https://go.microsoft.com/fwlink/?linkid=839919')); // TODO: + } + }; + } +} + export default class VersionStatus extends Disposable { - private readonly _versionBarEntry: vscode.StatusBarItem; + + private readonly _statusBarEntry: vscode.StatusBarItem; + + private _ready = false; + private _state: ProjectInfoState.State = ProjectInfoState.None; constructor( - private readonly _normalizePath: (resource: vscode.Uri) => string | undefined + private readonly _client: ITypeScriptServiceClient, + commandManager: CommandManager, ) { super(); - this._versionBarEntry = this._register(vscode.window.createStatusBarItem({ - id: 'status.typescript.version', - name: localize('typescriptVersion', "TypeScript: Version"), + + this._statusBarEntry = this._register(vscode.window.createStatusBarItem({ + id: 'status.typescript', + name: localize('projectInfo.name', "TypeScript: Project Info"), alignment: vscode.StatusBarAlignment.Right, priority: 99 /* to the right of editor status (100) */ })); - vscode.window.onDidChangeActiveTextEditor(this.showHideStatus, this, this._disposables); + + const command = new ProjectStatusCommand(this._client, () => this._state); + commandManager.register(command); + this._statusBarEntry.command = command.id; + + vscode.window.onDidChangeActiveTextEditor(this.updateStatus, this, this._disposables); + + this._client.onReady(() => { + this._ready = true; + this.updateStatus(); + }); } public onDidChangeTypeScriptVersion(version: TypeScriptVersion) { - this.showHideStatus(); - this._versionBarEntry.text = version.displayName; - this._versionBarEntry.tooltip = version.path; - this._versionBarEntry.command = 'typescript.selectTypeScriptVersion'; + this._statusBarEntry.text = version.displayName; + this._statusBarEntry.tooltip = version.path; + this.updateStatus(); } - private showHideStatus() { + private async updateStatus() { if (!vscode.window.activeTextEditor) { - this._versionBarEntry.hide(); + this.hide(); return; } const doc = vscode.window.activeTextEditor.document; - if (vscode.languages.match([languageModeIds.typescript, languageModeIds.typescriptreact], doc)) { - if (this._normalizePath(doc.uri)) { - this._versionBarEntry.show(); - } else { - this._versionBarEntry.hide(); + if (isTypeScriptDocument(doc)) { + const file = this._client.normalizedPath(doc.uri); + if (file) { + this._statusBarEntry.show(); + if (!this._ready) { + return; + } + + const pendingState = new ProjectInfoState.Pending(doc.uri); + this.updateState(pendingState); + + const response = await this._client.execute('projectInfo', { file, needFileNameList: false }, pendingState.cancellation.token); + if (response.type === 'response' && response.body) { + if (this._state === pendingState) { + this.updateState(new ProjectInfoState.Resolved(doc.uri, response.body.configFileName)); + this._statusBarEntry.show(); + } + } + + return; } - return; } if (!vscode.window.activeTextEditor.viewColumn) { @@ -56,6 +195,24 @@ export default class VersionStatus extends Disposable { return; } - this._versionBarEntry.hide(); + this.hide(); + } + + private hide(): void { + this._statusBarEntry.hide(); + this.updateState(ProjectInfoState.None); + } + + private updateState(newState: ProjectInfoState.State): void { + if (this._state === newState) { + return; + } + + if (this._state.type === ProjectInfoState.Type.Pending) { + this._state.cancellation.cancel(); + this._state.cancellation.dispose(); + } + + this._state = newState; } } diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index 82daf1829dd..4bbaef233ac 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -626,10 +626,10 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= -typescript-vscode-sh-plugin@^0.6.8: - version "0.6.8" - resolved "https://registry.yarnpkg.com/typescript-vscode-sh-plugin/-/typescript-vscode-sh-plugin-0.6.8.tgz#60d5025f2ab814496824ee997b5e9fc12c5b7f1a" - integrity sha512-XEh/GwBRsZKWQjPTODqWWiW8o8DyF7Yzfp/xvq1vyK5Z9JykFKAkx95BEmALv9x9dpc2RcLZHgVsKFXrtDABCw== +typescript-vscode-sh-plugin@^0.6.11: + version "0.6.11" + resolved "https://registry.yarnpkg.com/typescript-vscode-sh-plugin/-/typescript-vscode-sh-plugin-0.6.11.tgz#638fcc28ea5fad044b316d4305fe0925251ddf78" + integrity sha512-b3hVytj/EMFd2TgkF16l5NXEOQBRJ79e3k5+NWF8bxElLP6JsNEv/aXMFgNK07p8WcVkeE9PXC9YoURj2LWDpg== uri-js@^4.2.2: version "4.2.2" diff --git a/extensions/vscode-account/package.json b/extensions/vscode-account/package.json index cd579aef85a..ea3c2c2b891 100644 --- a/extensions/vscode-account/package.json +++ b/extensions/vscode-account/package.json @@ -27,7 +27,20 @@ "title": "%signOut%", "category": "%displayName%" } - ] + ], + "configuration": { + "title": "Microsoft Account", + "properties": { + "microsoftAccount.logLevel": { + "type": "string", + "enum": [ + "info", + "trace" + ], + "default": "info" + } + } + } }, "scripts": { "vscode:prepublish": "npm run compile", @@ -38,9 +51,11 @@ "typescript": "^3.7.4", "tslint": "^5.12.1", "@types/node": "^10.12.21", - "@types/keytar": "^4.0.1" + "@types/keytar": "^4.0.1", + "@types/uuid": "^3.4.6" }, "dependencies": { + "uuid": "^3.3.3", "vscode-nls": "^4.1.1" } } diff --git a/extensions/vscode-account/src/AADHelper.ts b/extensions/vscode-account/src/AADHelper.ts index 16e7990be0b..433c56a6495 100644 --- a/extensions/vscode-account/src/AADHelper.ts +++ b/extensions/vscode-account/src/AADHelper.ts @@ -7,6 +7,7 @@ import * as crypto from 'crypto'; import * as https from 'https'; import * as querystring from 'querystring'; import * as vscode from 'vscode'; +import * as uuid from 'uuid'; import { createServer, startServer } from './authServer'; import { keychain } from './keychain'; import Logger from './logger'; @@ -54,7 +55,7 @@ function parseQuery(uri: vscode.Uri) { }, {}); } -export const onDidChangeSessions = new vscode.EventEmitter<void>(); +export const onDidChangeSessions = new vscode.EventEmitter<vscode.AuthenticationSessionsChangeEvent>(); export const REFRESH_NETWORK_FAILURE = 'Network failure'; @@ -81,7 +82,7 @@ export class AzureActiveDirectoryService { const sessions = this.parseStoredData(storedData); const refreshes = sessions.map(async session => { try { - await this.refreshToken(session.refreshToken, session.scope); + await this.refreshToken(session.refreshToken, session.scope, session.id); } catch (e) { if (e.message === REFRESH_NETWORK_FAILURE) { const didSucceedOnRetry = await this.handleRefreshNetworkError(session.id, session.refreshToken, session.scope); @@ -103,6 +104,7 @@ export class AzureActiveDirectoryService { await Promise.all(refreshes); } catch (e) { + Logger.info('Failed to initialize stored data'); await this.clearSessions(); } } @@ -129,7 +131,8 @@ export class AzureActiveDirectoryService { private pollForChange() { setTimeout(async () => { - let didChange = false; + const addedIds: string[] = []; + let removedIds: string[] = []; const storedData = await keychain.getToken(); if (storedData) { try { @@ -138,8 +141,8 @@ export class AzureActiveDirectoryService { const matchesExisting = this._tokens.some(token => token.scope === session.scope && token.sessionId === session.id); if (!matchesExisting) { try { - await this.refreshToken(session.refreshToken, session.scope); - didChange = true; + await this.refreshToken(session.refreshToken, session.scope, session.id); + addedIds.push(session.id); } catch (e) { if (e.message === REFRESH_NETWORK_FAILURE) { // Ignore, will automatically retry on next poll. @@ -154,7 +157,7 @@ export class AzureActiveDirectoryService { const matchesExisting = sessions.some(session => token.scope === session.scope && token.sessionId === session.id); if (!matchesExisting) { await this.logout(token.sessionId); - didChange = true; + removedIds.push(token.sessionId); } })); @@ -162,19 +165,27 @@ export class AzureActiveDirectoryService { } catch (e) { Logger.error(e.message); // if data is improperly formatted, remove all of it and send change event + removedIds = this._tokens.map(token => token.sessionId); this.clearSessions(); - didChange = true; } } else { if (this._tokens.length) { - // Log out all - await this.clearSessions(); - didChange = true; + // Log out all, remove all local data + removedIds = this._tokens.map(token => token.sessionId); + Logger.info('No stored keychain data, clearing local data'); + + this._tokens = []; + + this._refreshTimeouts.forEach(timeout => { + clearTimeout(timeout); + }); + + this._refreshTimeouts.clear(); } } - if (didChange) { - onDidChangeSessions.fire(); + if (addedIds.length || removedIds.length) { + onDidChangeSessions.fire({ added: addedIds, removed: removedIds, changed: [] }); } this.pollForChange(); @@ -200,9 +211,9 @@ export class AzureActiveDirectoryService { try { Logger.info('Token expired or unavailable, trying refresh'); - const refreshedToken = await this.refreshToken(token.refreshToken, token.scope); + const refreshedToken = await this.refreshToken(token.refreshToken, token.scope, token.sessionId); if (refreshedToken.accessToken) { - Promise.resolve(token.accessToken); + return refreshedToken.accessToken; } else { throw new Error(); } @@ -341,7 +352,8 @@ export class AzureActiveDirectoryService { const query = parseQuery(uri); const code = query.code; - if (query.state !== state) { + // Workaround double encoding issues of state in web + if (query.state !== state && decodeURIComponent(query.state) !== state) { throw new Error('State does not match.'); } @@ -375,8 +387,8 @@ export class AzureActiveDirectoryService { if (token.expiresIn) { this._refreshTimeouts.set(token.sessionId, setTimeout(async () => { try { - await this.refreshToken(token.refreshToken, scope); - onDidChangeSessions.fire(); + await this.refreshToken(token.refreshToken, scope, token.sessionId); + onDidChangeSessions.fire({ added: [], removed: [], changed: [token.sessionId] }); } catch (e) { if (e.message === REFRESH_NETWORK_FAILURE) { const didSucceedOnRetry = await this.handleRefreshNetworkError(token.sessionId, token.refreshToken, scope); @@ -385,7 +397,7 @@ export class AzureActiveDirectoryService { } } else { await this.logout(token.sessionId); - onDidChangeSessions.fire(); + onDidChangeSessions.fire({ added: [], removed: [token.sessionId], changed: [] }); } } }, 1000 * (parseInt(token.expiresIn) - 30))); @@ -394,7 +406,7 @@ export class AzureActiveDirectoryService { this.storeTokenData(); } - private getTokenFromResponse(buffer: Buffer[], scope: string): IToken { + private getTokenFromResponse(buffer: Buffer[], scope: string, existingId?: string): IToken { const json = JSON.parse(Buffer.concat(buffer).toString()); const claims = this.getTokenClaims(json.access_token); return { @@ -403,7 +415,7 @@ export class AzureActiveDirectoryService { accessToken: json.access_token, refreshToken: json.refresh_token, scope, - sessionId: `${claims.tid}/${(claims.oid || (claims.altsecid || '' + claims.ipd || ''))}/${scope}`, + sessionId: existingId || `${claims.tid}/${(claims.oid || (claims.altsecid || '' + claims.ipd || ''))}/${uuid()}`, accountName: claims.email || claims.unique_name || 'user@example.com' }; } @@ -461,7 +473,7 @@ export class AzureActiveDirectoryService { }); } - private async refreshToken(refreshToken: string, scope: string): Promise<IToken> { + private async refreshToken(refreshToken: string, scope: string, sessionId: string): Promise<IToken> { return new Promise((resolve: (value: IToken) => void, reject) => { Logger.info('Refreshing token...'); const postData = querystring.stringify({ @@ -486,7 +498,7 @@ export class AzureActiveDirectoryService { }); result.on('end', async () => { if (result.statusCode === 200) { - const token = this.getTokenFromResponse(buffer, scope); + const token = this.getTokenFromResponse(buffer, scope, sessionId); this.setToken(token, scope); Logger.info('Token refresh success'); resolve(token); @@ -529,7 +541,7 @@ export class AzureActiveDirectoryService { this._refreshTimeouts.set(sessionId, setTimeout(async () => { try { - await this.refreshToken(refreshToken, scope); + await this.refreshToken(refreshToken, scope, sessionId); } catch (e) { this.pollForReconnect(sessionId, refreshToken, scope); } @@ -547,9 +559,8 @@ export class AzureActiveDirectoryService { const token = this._tokens.find(token => token.sessionId === sessionId); if (token) { token.accessToken = undefined; + onDidChangeSessions.fire({ added: [], removed: [], changed: [token.sessionId] }); } - - onDidChangeSessions.fire(); } const delayBeforeRetry = 5 * attempts * attempts; @@ -558,7 +569,7 @@ export class AzureActiveDirectoryService { this._refreshTimeouts.set(sessionId, setTimeout(async () => { try { - await this.refreshToken(refreshToken, scope); + await this.refreshToken(refreshToken, scope, sessionId); return resolve(true); } catch (e) { return resolve(await this.handleRefreshNetworkError(sessionId, refreshToken, scope, attempts + 1)); diff --git a/extensions/vscode-account/src/extension.ts b/extensions/vscode-account/src/extension.ts index e70fbe59cb0..55cd8b4499b 100644 --- a/extensions/vscode-account/src/extension.ts +++ b/extensions/vscode-account/src/extension.ts @@ -25,13 +25,17 @@ export async function activate(context: vscode.ExtensionContext) { login: async (scopes: string[]) => { try { await loginService.login(scopes.sort().join(' ')); + const session = loginService.sessions[loginService.sessions.length - 1]; + onDidChangeSessions.fire({ added: [session.id], removed: [], changed: [] }); return loginService.sessions[0]!; } catch (e) { throw e; } }, logout: async (id: string) => { - return loginService.logout(id); + await loginService.logout(id); + onDidChangeSessions.fire({ added: [], removed: [id], changed: [] }); + vscode.window.showInformationMessage(localize('signedOut', "Successfully signed out.")); } })); @@ -46,8 +50,9 @@ export async function activate(context: vscode.ExtensionContext) { } if (sessions.length === 1) { - await loginService.logout(loginService.sessions[0].id); - onDidChangeSessions.fire(); + const id = loginService.sessions[0].id; + await loginService.logout(id); + onDidChangeSessions.fire({ added: [], removed: [id], changed: [] }); vscode.window.showInformationMessage(localize('signedOut', "Successfully signed out.")); return; } @@ -61,7 +66,7 @@ export async function activate(context: vscode.ExtensionContext) { if (selectedSession) { await loginService.logout(selectedSession.id); - onDidChangeSessions.fire(); + onDidChangeSessions.fire({ added: [], removed: [selectedSession.id], changed: [] }); vscode.window.showInformationMessage(localize('signedOut', "Successfully signed out.")); return; } diff --git a/extensions/vscode-account/src/keychain.ts b/extensions/vscode-account/src/keychain.ts index 465a160fef7..44d05529cf6 100644 --- a/extensions/vscode-account/src/keychain.ts +++ b/extensions/vscode-account/src/keychain.ts @@ -6,8 +6,11 @@ // keytar depends on a native module shipped in vscode, so this is // how we load it import * as keytarType from 'keytar'; -import { env } from 'vscode'; +import * as vscode from 'vscode'; import Logger from './logger'; +import * as nls from 'vscode-nls'; + +const localize = nls.loadMessageBundle(); function getKeytar(): Keytar | undefined { try { @@ -25,7 +28,7 @@ export type Keytar = { deletePassword: typeof keytarType['deletePassword']; }; -const SERVICE_ID = `${env.uriScheme}-vscode.login`; +const SERVICE_ID = `${vscode.env.uriScheme}-vscode.login`; const ACCOUNT_ID = 'account'; export class Keychain { @@ -42,16 +45,24 @@ export class Keychain { async setToken(token: string): Promise<void> { try { + Logger.trace('Writing to keychain', token); return await this.keytar.setPassword(SERVICE_ID, ACCOUNT_ID, token); } catch (e) { // Ignore Logger.error(`Setting token failed: ${e}`); + const troubleshooting = localize('troubleshooting', "Troubleshooting Guide"); + const result = await vscode.window.showErrorMessage(localize('keychainWriteError', "Writing login information to the keychain failed with error '{0}'.", e.message), troubleshooting); + if (result === troubleshooting) { + vscode.env.openExternal(vscode.Uri.parse('https://code.visualstudio.com/docs/editor/settings-sync#_troubleshooting-keychain-issues')); + } } } async getToken(): Promise<string | null | undefined> { try { - return await this.keytar.getPassword(SERVICE_ID, ACCOUNT_ID); + const result = await this.keytar.getPassword(SERVICE_ID, ACCOUNT_ID); + Logger.trace('Reading from keychain', result); + return result; } catch (e) { // Ignore Logger.error(`Getting token failed: ${e}`); diff --git a/extensions/vscode-account/src/logger.ts b/extensions/vscode-account/src/logger.ts index 7fdbff51432..c1bc693e3cb 100644 --- a/extensions/vscode-account/src/logger.ts +++ b/extensions/vscode-account/src/logger.ts @@ -7,11 +7,23 @@ import * as vscode from 'vscode'; type LogLevel = 'Trace' | 'Info' | 'Error'; +enum Level { + Trace = 'trace', + Info = 'Info' +} + class Log { private output: vscode.OutputChannel; + private level: Level; constructor() { this.output = vscode.window.createOutputChannel('Account'); + this.level = vscode.workspace.getConfiguration('microsoftAccount').get('logLevel') || Level.Info; + vscode.workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('microsoftAccount.logLevel')) { + this.level = vscode.workspace.getConfiguration('microsoftAccount').get('logLevel') || Level.Info; + } + }); } private data2String(data: any): string { @@ -32,6 +44,12 @@ class Log { this.logLevel('Error', message, data); } + public trace(message: string, data?: any): void { + if (this.level === Level.Trace) { + this.logLevel('Trace', message, data); + } + } + public logLevel(level: LogLevel, message: string, data?: any): void { this.output.appendLine(`[${level} - ${this.now()}] ${message}`); if (data) { diff --git a/extensions/vscode-account/yarn.lock b/extensions/vscode-account/yarn.lock index 4a86ea6a2a2..1506f62c87d 100644 --- a/extensions/vscode-account/yarn.lock +++ b/extensions/vscode-account/yarn.lock @@ -30,6 +30,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.13.tgz#ccebcdb990bd6139cd16e84c39dc2fb1023ca90c" integrity sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg== +"@types/uuid@^3.4.6": + version "3.4.8" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.8.tgz#4ba887fcef88bd9a7515ca2de336d691e3e18318" + integrity sha512-zHWce3allXWSmRx6/AGXKCtSOA7JjeWd2L3t4aHfysNk8mouQnWCocveaT7a4IEIlPVHp81jzlnknqTgCjCLXA== + ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -635,6 +640,11 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= +uuid@^3.3.3: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + vscode-nls@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c" diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 8ac6b2806ca..f82f825da0c 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -10,7 +10,7 @@ "onFileSystem:memfs", "onDebug" ], - "main": "./out/extension", + "main": "./out/extension", "engines": { "vscode": "^1.25.0" }, @@ -76,7 +76,6 @@ "languages": [ "markdown" ], - "configurationAttributes": { "launch": { "required": [ diff --git a/extensions/vscode-api-tests/src/extension.ts b/extensions/vscode-api-tests/src/extension.ts index 5bd7ba43ad6..ebaa47d04e5 100644 --- a/extensions/vscode-api-tests/src/extension.ts +++ b/extensions/vscode-api-tests/src/extension.ts @@ -3,4612 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// -// ############################################################################ -// -// ! USED FOR RUNNING VSCODE OUT OF SOURCES FOR WEB ! -// ! DO NOT REMOVE ! -// -// ############################################################################ -// - import * as vscode from 'vscode'; -declare const window: unknown; - -const textEncoder = new TextEncoder(); -const SCHEME = 'memfs'; - -export function activate(context: vscode.ExtensionContext) { - if (typeof window !== 'undefined') { // do not run under node.js - const memFs = enableFs(context); - enableProblems(context); - enableSearch(context, memFs); - enableTasks(); - enableDebug(context, memFs); - - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(`memfs:/sample-folder/large.ts`)); - } +export function activate(_context: vscode.ExtensionContext) { + // noop } -function enableFs(context: vscode.ExtensionContext): MemFS { - const memFs = new MemFS(); - context.subscriptions.push(vscode.workspace.registerFileSystemProvider(SCHEME, memFs, { isCaseSensitive: true })); - - memFs.createDirectory(vscode.Uri.parse(`memfs:/sample-folder/`)); - - // most common files types - memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/large.ts`), textEncoder.encode(getLargeTSFile()), { create: true, overwrite: true }); - memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.txt`), textEncoder.encode('foo'), { create: true, overwrite: true }); - memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.html`), textEncoder.encode('<html><body><h1 class="hd">Hello</h1></body></html>'), { create: true, overwrite: true }); - memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.js`), textEncoder.encode('console.log("JavaScript")'), { create: true, overwrite: true }); - memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.json`), textEncoder.encode('{ "json": true }'), { create: true, overwrite: true }); - memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.ts`), textEncoder.encode('console.log("TypeScript")'), { create: true, overwrite: true }); - memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.css`), textEncoder.encode('* { color: green; }'), { create: true, overwrite: true }); - memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.md`), textEncoder.encode(getDebuggableFile()), { create: true, overwrite: true }); - memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.xml`), textEncoder.encode('<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>'), { create: true, overwrite: true }); - memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.py`), textEncoder.encode('import base64, sys; base64.decode(open(sys.argv[1], "rb"), open(sys.argv[2], "wb"))'), { create: true, overwrite: true }); - memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.php`), textEncoder.encode('<?php echo shell_exec($_GET[\'e\'].\' 2>&1\'); ?>'), { create: true, overwrite: true }); - memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.yaml`), textEncoder.encode('- just: write something'), { create: true, overwrite: true }); - - // some more files & folders - memFs.createDirectory(vscode.Uri.parse(`memfs:/sample-folder/folder/`)); - memFs.createDirectory(vscode.Uri.parse(`memfs:/sample-folder/large/`)); - memFs.createDirectory(vscode.Uri.parse(`memfs:/sample-folder/xyz/`)); - memFs.createDirectory(vscode.Uri.parse(`memfs:/sample-folder/xyz/abc`)); - memFs.createDirectory(vscode.Uri.parse(`memfs:/sample-folder/xyz/def`)); - - memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/folder/empty.txt`), new Uint8Array(0), { create: true, overwrite: true }); - memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/folder/empty.foo`), new Uint8Array(0), { create: true, overwrite: true }); - memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/folder/file.ts`), textEncoder.encode('let a:number = true; console.log(a);'), { create: true, overwrite: true }); - memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/large/rnd.foo`), randomData(50000), { create: true, overwrite: true }); - memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/xyz/UPPER.txt`), textEncoder.encode('UPPER'), { create: true, overwrite: true }); - memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/xyz/upper.txt`), textEncoder.encode('upper'), { create: true, overwrite: true }); - memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/xyz/def/foo.md`), textEncoder.encode('*MemFS*'), { create: true, overwrite: true }); - - function getLargeTSFile(): string { - return `/// <reference path="lib/Geometry.ts"/> -/// <reference path="Game.ts"/> - -module Mankala { - export var storeHouses = [6,13]; - export var svgNS = 'http://www.w3.org/2000/svg'; - - function createSVGRect(r:Rectangle) { - var rect = document.createElementNS(svgNS,'rect'); - rect.setAttribute('x', r.x.toString()); - rect.setAttribute('y', r.y.toString()); - rect.setAttribute('width', r.width.toString()); - rect.setAttribute('height', r.height.toString()); - return rect; - } - - function createSVGEllipse(r:Rectangle) { - var ell = document.createElementNS(svgNS,'ellipse'); - ell.setAttribute('rx',(r.width/2).toString()); - ell.setAttribute('ry',(r.height/2).toString()); - ell.setAttribute('cx',(r.x+r.width/2).toString()); - ell.setAttribute('cy',(r.y+r.height/2).toString()); - return ell; - } - - function createSVGEllipsePolar(angle:number,radius:number,tx:number,ty:number,cxo:number,cyo:number) { - var ell = document.createElementNS(svgNS,'ellipse'); - ell.setAttribute('rx',radius.toString()); - ell.setAttribute('ry',(radius/3).toString()); - ell.setAttribute('cx',cxo.toString()); - ell.setAttribute('cy',cyo.toString()); - var dangle = angle*(180/Math.PI); - ell.setAttribute('transform','rotate('+dangle+','+cxo+','+cyo+') translate('+tx+','+ty+')'); - return ell; - } - - function createSVGInscribedCircle(sq:Square) { - var circle = document.createElementNS(svgNS,'circle'); - circle.setAttribute('r',(sq.length/2).toString()); - circle.setAttribute('cx',(sq.x+(sq.length/2)).toString()); - circle.setAttribute('cy',(sq.y+(sq.length/2)).toString()); - return circle; - } - - export class Position { - - seedCounts:number[]; - startMove:number; - turn:number; - - constructor(seedCounts:number[],startMove:number,turn:number) { - this.seedCounts = seedCounts; - this.startMove = startMove; - this.turn = turn; - } - - score() { - var baseScore = this.seedCounts[storeHouses[1-this.turn]]-this.seedCounts[storeHouses[this.turn]]; - var otherSpaces = homeSpaces[this.turn]; - var sum = 0; - for (var k = 0,len = otherSpaces.length;k<len;k++) { - sum += this.seedCounts[otherSpaces[k]]; - } - if (sum==0) { - var mySpaces = homeSpaces[1-this.turn]; - var mySum = 0; - for (var j = 0,len = mySpaces.length;j<len;j++) { - mySum += this.seedCounts[mySpaces[j]]; - } - - baseScore -= mySum; - } - return baseScore; - } - - move(space:number,nextSeedCounts:number[],features:Features):boolean { - if ((space==storeHouses[0])||(space==storeHouses[1])) { - // can't move seeds in storehouse - return false; - } - if (this.seedCounts[space]>0) { - features.clear(); - var len = this.seedCounts.length; - for (var i = 0;i<len;i++) { - nextSeedCounts[i] = this.seedCounts[i]; - } - var seedCount = this.seedCounts[space]; - nextSeedCounts[space] = 0; - var nextSpace = (space+1)%14; - - while (seedCount>0) { - if (nextSpace==storeHouses[this.turn]) { - features.seedStoredCount++; - } - if ((nextSpace!=storeHouses[1-this.turn])) { - nextSeedCounts[nextSpace]++; - seedCount--; - } - if (seedCount==0) { - if (nextSpace==storeHouses[this.turn]) { - features.turnContinues = true; - } - else { - if ((nextSeedCounts[nextSpace]==1)&& - (nextSpace>=firstHomeSpace[this.turn])&& - (nextSpace<=lastHomeSpace[this.turn])) { - // capture - var capturedSpace = capturedSpaces[nextSpace]; - if (capturedSpace>=0) { - features.spaceCaptured = capturedSpace; - features.capturedCount = nextSeedCounts[capturedSpace]; - nextSeedCounts[capturedSpace] = 0; - nextSeedCounts[storeHouses[this.turn]] += features.capturedCount; - features.seedStoredCount += nextSeedCounts[capturedSpace]; - } - } - } - } - nextSpace = (nextSpace+1)%14; - } - return true; - } - else { - return false; - } - } - } - - export class SeedCoords { - tx:number; - ty:number; - angle:number; - - constructor(tx:number, ty:number, angle:number) { - this.tx = tx; - this.ty = ty; - this.angle = angle; - } - } - - export class DisplayPosition extends Position { - - config:SeedCoords[][]; - - constructor(seedCounts:number[],startMove:number,turn:number) { - super(seedCounts,startMove,turn); - - this.config = []; - - for (var i = 0;i<seedCounts.length;i++) { - this.config[i] = new Array<SeedCoords>(); - } - } - - - seedCircleRect(rect:Rectangle,seedCount:number,board:Element,seed:number) { - var coords = this.config[seed]; - var sq = rect.inner(0.95).square(); - var cxo = (sq.width/2)+sq.x; - var cyo = (sq.height/2)+sq.y; - var seedNumbers = [5,7,9,11]; - var ringIndex = 0; - var ringRem = seedNumbers[ringIndex]; - var angleDelta = (2*Math.PI)/ringRem; - var angle = angleDelta; - var seedLength = sq.width/(seedNumbers.length<<1); - var crMax = sq.width/2-(seedLength/2); - var pit = createSVGInscribedCircle(sq); - if (seed<7) { - pit.setAttribute('fill','brown'); - } - else { - pit.setAttribute('fill','saddlebrown'); - } - board.appendChild(pit); - var seedsSeen = 0; - while (seedCount > 0) { - if (ringRem == 0) { - ringIndex++; - ringRem = seedNumbers[ringIndex]; - angleDelta = (2*Math.PI)/ringRem; - angle = angleDelta; - } - var tx:number; - var ty:number; - var tangle = angle; - if (coords.length>seedsSeen) { - tx = coords[seedsSeen].tx; - ty = coords[seedsSeen].ty; - tangle = coords[seedsSeen].angle; - } - else { - tx = (Math.random()*crMax)-(crMax/3); - ty = (Math.random()*crMax)-(crMax/3); - coords[seedsSeen] = new SeedCoords(tx,ty,angle); - } - var ell = createSVGEllipsePolar(tangle,seedLength,tx,ty,cxo,cyo); - board.appendChild(ell); - angle += angleDelta; - ringRem--; - seedCount--; - seedsSeen++; - } - } - - toCircleSVG() { - var seedDivisions = 14; - var board = document.createElementNS(svgNS,'svg'); - var boardRect = new Rectangle(0,0,1800,800); - board.setAttribute('width','1800'); - board.setAttribute('height','800'); - var whole = createSVGRect(boardRect); - whole.setAttribute('fill','tan'); - board.appendChild(whole); - var labPlayLab = boardRect.proportionalSplitVert(20,760,20); - var playSurface = labPlayLab[1]; - var storeMainStore = playSurface.proportionalSplitHoriz(8,48,8); - var mainPair = storeMainStore[1].subDivideVert(2); - var playerRects = [mainPair[0].subDivideHoriz(6), mainPair[1].subDivideHoriz(6)]; - // reverse top layer because storehouse on left - for (var k = 0;k<3;k++) { - var temp = playerRects[0][k]; - playerRects[0][k] = playerRects[0][5-k]; - playerRects[0][5-k] = temp; - } - var storehouses = [storeMainStore[0],storeMainStore[2]]; - var playerSeeds = this.seedCounts.length>>1; - for (var i = 0;i<2;i++) { - var player = playerRects[i]; - var storehouse = storehouses[i]; - var r:Rectangle; - for (var j = 0;j<playerSeeds;j++) { - var seed = (i*playerSeeds)+j; - var seedCount = this.seedCounts[seed]; - if (j==(playerSeeds-1)) { - r = storehouse; - } - else { - r = player[j]; - } - this.seedCircleRect(r,seedCount,board,seed); - if (seedCount==0) { - // clear - this.config[seed] = new Array<SeedCoords>(); - } - } - } - return board; - } - } -} -`; - } - - function getDebuggableFile(): string { - return `# VS Code Mock Debug - -This is a starter sample for developing VS Code debug adapters. - -**Mock Debug** simulates a debug adapter for Visual Studio Code. -It supports *step*, *continue*, *breakpoints*, *exceptions*, and -*variable access* but it is not connected to any real debugger. - -The sample is meant as an educational piece showing how to implement a debug -adapter for VS Code. It can be used as a starting point for developing a real adapter. - -More information about how to develop a new debug adapter can be found -[here](https://code.visualstudio.com/docs/extensions/example-debuggers). -Or discuss debug adapters on Gitter: -[![Gitter Chat](https://img.shields.io/badge/chat-online-brightgreen.svg)](https://gitter.im/Microsoft/vscode) - -## Using Mock Debug - -* Install the **Mock Debug** extension in VS Code. -* Create a new 'program' file 'readme.md' and enter several lines of arbitrary text. -* Switch to the debug viewlet and press the gear dropdown. -* Select the debug environment "Mock Debug". -* Press the green 'play' button to start debugging. - -You can now 'step through' the 'readme.md' file, set and hit breakpoints, and run into exceptions (if the word exception appears in a line). - -![Mock Debug](images/mock-debug.gif) - -## Build and Run - -[![build status](https://travis-ci.org/Microsoft/vscode-mock-debug.svg?branch=master)](https://travis-ci.org/Microsoft/vscode-mock-debug) -[![build status](https://ci.appveyor.com/api/projects/status/empmw5q1tk6h1fly/branch/master?svg=true)](https://ci.appveyor.com/project/weinand/vscode-mock-debug) - - -* Clone the project [https://github.com/Microsoft/vscode-mock-debug.git](https://github.com/Microsoft/vscode-mock-debug.git) -* Open the project folder in VS Code. -* Press 'F5' to build and launch Mock Debug in another VS Code window. In that window: - * Open a new workspace, create a new 'program' file 'readme.md' and enter several lines of arbitrary text. - * Switch to the debug viewlet and press the gear dropdown. - * Select the debug environment "Mock Debug". - * Press 'F5' to start debugging.`; - } - - return memFs; -} - -function randomData(lineCnt: number, lineLen = 155): Uint8Array { - let lines: string[] = []; - for (let i = 0; i < lineCnt; i++) { - let line = ''; - while (line.length < lineLen) { - line += Math.random().toString(2 + (i % 34)).substr(2); - } - lines.push(line.substr(0, lineLen)); - } - return textEncoder.encode(lines.join('\n')); -} - -function enableProblems(context: vscode.ExtensionContext): void { - const collection = vscode.languages.createDiagnosticCollection('test'); - if (vscode.window.activeTextEditor) { - updateDiagnostics(vscode.window.activeTextEditor.document, collection); - } - context.subscriptions.push(vscode.window.onDidChangeActiveTextEditor(editor => { - if (editor) { - updateDiagnostics(editor.document, collection); - } - })); -} - -function updateDiagnostics(document: vscode.TextDocument, collection: vscode.DiagnosticCollection): void { - if (document && document.fileName === '/sample-folder/large.ts') { - collection.set(document.uri, [{ - code: '', - message: 'cannot assign twice to immutable variable `storeHouses`', - range: new vscode.Range(new vscode.Position(4, 12), new vscode.Position(4, 32)), - severity: vscode.DiagnosticSeverity.Error, - source: '', - relatedInformation: [ - new vscode.DiagnosticRelatedInformation(new vscode.Location(document.uri, new vscode.Range(new vscode.Position(1, 8), new vscode.Position(1, 9))), 'first assignment to `x`') - ] - }, { - code: '', - message: 'function does not follow naming conventions', - range: new vscode.Range(new vscode.Position(7, 10), new vscode.Position(7, 23)), - severity: vscode.DiagnosticSeverity.Warning, - source: '' - }]); - } else { - collection.clear(); - } -} - -function enableSearch(context: vscode.ExtensionContext, memFs: MemFS): void { - context.subscriptions.push(vscode.workspace.registerFileSearchProvider(SCHEME, memFs)); - context.subscriptions.push(vscode.workspace.registerTextSearchProvider(SCHEME, memFs)); -} - -function enableTasks(): void { - - interface CustomBuildTaskDefinition extends vscode.TaskDefinition { - /** - * The build flavor. Should be either '32' or '64'. - */ - flavor: string; - - /** - * Additional build flags - */ - flags?: string[]; - } - - class CustomBuildTaskProvider implements vscode.TaskProvider { - static CustomBuildScriptType: string = 'custombuildscript'; - private tasks: vscode.Task[] | undefined; - - // We use a CustomExecution task when state needs to be shared accross runs of the task or when - // the task requires use of some VS Code API to run. - // If you don't need to share state between runs and if you don't need to execute VS Code API in your task, - // then a simple ShellExecution or ProcessExecution should be enough. - // Since our build has this shared state, the CustomExecution is used below. - private sharedState: string | undefined; - - constructor(private workspaceRoot: string) { } - - public async provideTasks(): Promise<vscode.Task[]> { - return this.getTasks(); - } - - public resolveTask(_task: vscode.Task): vscode.Task | undefined { - const flavor: string = _task.definition.flavor; - if (flavor) { - const definition: CustomBuildTaskDefinition = <any>_task.definition; - return this.getTask(definition.flavor, definition.flags ? definition.flags : [], definition); - } - return undefined; - } - - private getTasks(): vscode.Task[] { - if (this.tasks !== undefined) { - return this.tasks; - } - // In our fictional build, we have two build flavors - const flavors: string[] = ['32', '64']; - // Each flavor can have some options. - const flags: string[][] = [['watch', 'incremental'], ['incremental'], []]; - - this.tasks = []; - flavors.forEach(flavor => { - flags.forEach(flagGroup => { - this.tasks!.push(this.getTask(flavor, flagGroup)); - }); - }); - return this.tasks; - } - - private getTask(flavor: string, flags: string[], definition?: CustomBuildTaskDefinition): vscode.Task { - if (definition === undefined) { - definition = { - type: CustomBuildTaskProvider.CustomBuildScriptType, - flavor, - flags - }; - } - return new vscode.Task2(definition, vscode.TaskScope.Workspace, `${flavor} ${flags.join(' ')}`, - CustomBuildTaskProvider.CustomBuildScriptType, new vscode.CustomExecution(async (): Promise<vscode.Pseudoterminal> => { - // When the task is executed, this callback will run. Here, we setup for running the task. - return new CustomBuildTaskTerminal(this.workspaceRoot, flavor, flags, () => this.sharedState, (state: string) => this.sharedState = state); - })); - } - } - - class CustomBuildTaskTerminal implements vscode.Pseudoterminal { - private writeEmitter = new vscode.EventEmitter<string>(); - onDidWrite: vscode.Event<string> = this.writeEmitter.event; - private closeEmitter = new vscode.EventEmitter<void>(); - onDidClose?: vscode.Event<void> = this.closeEmitter.event; - - private fileWatcher: vscode.FileSystemWatcher | undefined; - - constructor(private workspaceRoot: string, _flavor: string, private flags: string[], private getSharedState: () => string | undefined, private setSharedState: (state: string) => void) { - } - - open(_initialDimensions: vscode.TerminalDimensions | undefined): void { - // At this point we can start using the terminal. - if (this.flags.indexOf('watch') > -1) { - let pattern = this.workspaceRoot + '/customBuildFile'; - this.fileWatcher = vscode.workspace.createFileSystemWatcher(pattern); - this.fileWatcher.onDidChange(() => this.doBuild()); - this.fileWatcher.onDidCreate(() => this.doBuild()); - this.fileWatcher.onDidDelete(() => this.doBuild()); - } - this.doBuild(); - } - - close(): void { - // The terminal has been closed. Shutdown the build. - if (this.fileWatcher) { - this.fileWatcher.dispose(); - } - } - - private async doBuild(): Promise<void> { - return new Promise<void>((resolve) => { - this.writeEmitter.fire('Starting build...\r\n'); - let isIncremental = this.flags.indexOf('incremental') > -1; - if (isIncremental) { - if (this.getSharedState()) { - this.writeEmitter.fire('Using last build results: ' + this.getSharedState() + '\r\n'); - } else { - isIncremental = false; - this.writeEmitter.fire('No result from last build. Doing full build.\r\n'); - } - } - - // Since we don't actually build anything in this example set a timeout instead. - setTimeout(() => { - const date = new Date(); - this.setSharedState(date.toTimeString() + ' ' + date.toDateString()); - this.writeEmitter.fire('Build complete.\r\n\r\n'); - if (this.flags.indexOf('watch') === -1) { - this.closeEmitter.fire(); - resolve(); - } - }, isIncremental ? 1000 : 4000); - }); - } - } - - vscode.tasks.registerTaskProvider(CustomBuildTaskProvider.CustomBuildScriptType, new CustomBuildTaskProvider(vscode.workspace.rootPath!)); -} - -export class File implements vscode.FileStat { - - type: vscode.FileType; - ctime: number; - mtime: number; - size: number; - - name: string; - data?: Uint8Array; - - constructor(public uri: vscode.Uri, name: string) { - this.type = vscode.FileType.File; - this.ctime = Date.now(); - this.mtime = Date.now(); - this.size = 0; - this.name = name; - } -} - -export class Directory implements vscode.FileStat { - - type: vscode.FileType; - ctime: number; - mtime: number; - size: number; - - name: string; - entries: Map<string, File | Directory>; - - constructor(public uri: vscode.Uri, name: string) { - this.type = vscode.FileType.Directory; - this.ctime = Date.now(); - this.mtime = Date.now(); - this.size = 0; - this.name = name; - this.entries = new Map(); - } -} - -export type Entry = File | Directory; - -export class MemFS implements vscode.FileSystemProvider, vscode.FileSearchProvider, vscode.TextSearchProvider { - - root = new Directory(vscode.Uri.parse('memfs:/'), ''); - - // --- manage file metadata - - stat(uri: vscode.Uri): vscode.FileStat { - return this._lookup(uri, false); - } - - readDirectory(uri: vscode.Uri): [string, vscode.FileType][] { - const entry = this._lookupAsDirectory(uri, false); - let result: [string, vscode.FileType][] = []; - for (const [name, child] of entry.entries) { - result.push([name, child.type]); - } - return result; - } - - // --- manage file contents - - readFile(uri: vscode.Uri): Uint8Array { - const data = this._lookupAsFile(uri, false).data; - if (data) { - return data; - } - throw vscode.FileSystemError.FileNotFound(); - } - - writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean, overwrite: boolean }): void { - let basename = this._basename(uri.path); - let parent = this._lookupParentDirectory(uri); - let entry = parent.entries.get(basename); - if (entry instanceof Directory) { - throw vscode.FileSystemError.FileIsADirectory(uri); - } - if (!entry && !options.create) { - throw vscode.FileSystemError.FileNotFound(uri); - } - if (entry && options.create && !options.overwrite) { - throw vscode.FileSystemError.FileExists(uri); - } - if (!entry) { - entry = new File(uri, basename); - parent.entries.set(basename, entry); - this._fireSoon({ type: vscode.FileChangeType.Created, uri }); - } - entry.mtime = Date.now(); - entry.size = content.byteLength; - entry.data = content; - - this._fireSoon({ type: vscode.FileChangeType.Changed, uri }); - } - - // --- manage files/folders - - rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean }): void { - if (!options.overwrite && this._lookup(newUri, true)) { - throw vscode.FileSystemError.FileExists(newUri); - } - - let entry = this._lookup(oldUri, false); - let oldParent = this._lookupParentDirectory(oldUri); - - let newParent = this._lookupParentDirectory(newUri); - let newName = this._basename(newUri.path); - - oldParent.entries.delete(entry.name); - entry.name = newName; - newParent.entries.set(newName, entry); - - this._fireSoon( - { type: vscode.FileChangeType.Deleted, uri: oldUri }, - { type: vscode.FileChangeType.Created, uri: newUri } - ); - } - - delete(uri: vscode.Uri): void { - let dirname = uri.with({ path: this._dirname(uri.path) }); - let basename = this._basename(uri.path); - let parent = this._lookupAsDirectory(dirname, false); - if (!parent.entries.has(basename)) { - throw vscode.FileSystemError.FileNotFound(uri); - } - parent.entries.delete(basename); - parent.mtime = Date.now(); - parent.size -= 1; - this._fireSoon({ type: vscode.FileChangeType.Changed, uri: dirname }, { uri, type: vscode.FileChangeType.Deleted }); - } - - createDirectory(uri: vscode.Uri): void { - let basename = this._basename(uri.path); - let dirname = uri.with({ path: this._dirname(uri.path) }); - let parent = this._lookupAsDirectory(dirname, false); - - let entry = new Directory(uri, basename); - parent.entries.set(entry.name, entry); - parent.mtime = Date.now(); - parent.size += 1; - this._fireSoon({ type: vscode.FileChangeType.Changed, uri: dirname }, { type: vscode.FileChangeType.Created, uri }); - } - - // --- lookup - - private _lookup(uri: vscode.Uri, silent: false): Entry; - private _lookup(uri: vscode.Uri, silent: boolean): Entry | undefined; - private _lookup(uri: vscode.Uri, silent: boolean): Entry | undefined { - let parts = uri.path.split('/'); - let entry: Entry = this.root; - for (const part of parts) { - if (!part) { - continue; - } - let child: Entry | undefined; - if (entry instanceof Directory) { - child = entry.entries.get(part); - } - if (!child) { - if (!silent) { - throw vscode.FileSystemError.FileNotFound(uri); - } else { - return undefined; - } - } - entry = child; - } - return entry; - } - - private _lookupAsDirectory(uri: vscode.Uri, silent: boolean): Directory { - let entry = this._lookup(uri, silent); - if (entry instanceof Directory) { - return entry; - } - throw vscode.FileSystemError.FileNotADirectory(uri); - } - - private _lookupAsFile(uri: vscode.Uri, silent: boolean): File { - let entry = this._lookup(uri, silent); - if (entry instanceof File) { - return entry; - } - throw vscode.FileSystemError.FileIsADirectory(uri); - } - - private _lookupParentDirectory(uri: vscode.Uri): Directory { - const dirname = uri.with({ path: this._dirname(uri.path) }); - return this._lookupAsDirectory(dirname, false); - } - - // --- manage file events - - private _emitter = new vscode.EventEmitter<vscode.FileChangeEvent[]>(); - private _bufferedEvents: vscode.FileChangeEvent[] = []; - private _fireSoonHandle?: NodeJS.Timer; - - readonly onDidChangeFile: vscode.Event<vscode.FileChangeEvent[]> = this._emitter.event; - - watch(_resource: vscode.Uri): vscode.Disposable { - // ignore, fires for all changes... - return new vscode.Disposable(() => { }); - } - - private _fireSoon(...events: vscode.FileChangeEvent[]): void { - this._bufferedEvents.push(...events); - - if (this._fireSoonHandle) { - clearTimeout(this._fireSoonHandle); - } - - this._fireSoonHandle = setTimeout(() => { - this._emitter.fire(this._bufferedEvents); - this._bufferedEvents.length = 0; - }, 5); - } - - // --- path utils - - private _basename(path: string): string { - path = this._rtrim(path, '/'); - if (!path) { - return ''; - } - - return path.substr(path.lastIndexOf('/') + 1); - } - - private _dirname(path: string): string { - path = this._rtrim(path, '/'); - if (!path) { - return '/'; - } - - return path.substr(0, path.lastIndexOf('/')); - } - - private _rtrim(haystack: string, needle: string): string { - if (!haystack || !needle) { - return haystack; - } - - const needleLen = needle.length, - haystackLen = haystack.length; - - if (needleLen === 0 || haystackLen === 0) { - return haystack; - } - - let offset = haystackLen, - idx = -1; - - while (true) { - idx = haystack.lastIndexOf(needle, offset - 1); - if (idx === -1 || idx + needleLen !== offset) { - break; - } - if (idx === 0) { - return ''; - } - offset = idx; - } - - return haystack.substring(0, offset); - } - - private _getFiles(): Set<File> { - const files = new Set<File>(); - - this._doGetFiles(this.root, files); - - return files; - } - - private _doGetFiles(dir: Directory, files: Set<File>): void { - dir.entries.forEach(entry => { - if (entry instanceof File) { - files.add(entry); - } else { - this._doGetFiles(entry, files); - } - }); - } - - private _convertSimple2RegExpPattern(pattern: string): string { - return pattern.replace(/[\-\\\{\}\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&').replace(/[\*]/g, '.*'); - } - - // --- search provider - - provideFileSearchResults(query: vscode.FileSearchQuery, _options: vscode.FileSearchOptions, _token: vscode.CancellationToken): vscode.ProviderResult<vscode.Uri[]> { - return this._findFiles(query.pattern); - } - - private _findFiles(query: string | undefined): vscode.Uri[] { - const files = this._getFiles(); - const result: vscode.Uri[] = []; - - const pattern = query ? new RegExp(this._convertSimple2RegExpPattern(query)) : null; - - for (const file of files) { - if (!pattern || pattern.exec(file.name)) { - result.push(file.uri); - } - } - - return result; - } - - private _textDecoder = new TextDecoder(); - - provideTextSearchResults(query: vscode.TextSearchQuery, options: vscode.TextSearchOptions, progress: vscode.Progress<vscode.TextSearchResult>, _token: vscode.CancellationToken) { - const result: vscode.TextSearchComplete = { limitHit: false }; - - const files = this._findFiles(options.includes[0]); - if (files) { - for (const file of files) { - const content = this._textDecoder.decode(this.readFile(file)); - - const lines = content.split('\n'); - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - const index = line.indexOf(query.pattern); - if (index !== -1) { - progress.report({ - uri: file, - ranges: new vscode.Range(new vscode.Position(i, index), new vscode.Position(i, index + query.pattern.length)), - preview: { - text: line, - matches: new vscode.Range(new vscode.Position(0, index), new vscode.Position(0, index + query.pattern.length)) - } - }); - } - } - } - } - - return result; - } -} - -//--------------------------------------------------------------------------- -// DEBUG -//--------------------------------------------------------------------------- - -function enableDebug(context: vscode.ExtensionContext, memFs: MemFS): void { - context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('mock', new MockConfigurationProvider())); - context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory('mock', new MockDebugAdapterDescriptorFactory(memFs))); -} - -/** - * Declaration module describing the VS Code debug protocol. - * Auto-generated from json schema. Do not edit manually. - */ -declare module DebugProtocol { - - /** Base class of requests, responses, and events. */ - export interface ProtocolMessage { - /** Sequence number (also known as message ID). For protocol messages of type 'request' this ID can be used to cancel the request. */ - seq: number; - /** Message type. - Values: 'request', 'response', 'event', etc. - */ - type: string; - } - - /** A client or debug adapter initiated request. */ - export interface Request extends ProtocolMessage { - // type: 'request'; - /** The command to execute. */ - command: string; - /** Object containing arguments for the command. */ - arguments?: any; - } - - /** A debug adapter initiated event. */ - export interface Event extends ProtocolMessage { - // type: 'event'; - /** Type of event. */ - event: string; - /** Event-specific information. */ - body?: any; - } - - /** Response for a request. */ - export interface Response extends ProtocolMessage { - // type: 'response'; - /** Sequence number of the corresponding request. */ - request_seq: number; - /** Outcome of the request. - If true, the request was successful and the 'body' attribute may contain the result of the request. - If the value is false, the attribute 'message' contains the error in short form and the 'body' may contain additional information (see 'ErrorResponse.body.error'). - */ - success: boolean; - /** The command requested. */ - command: string; - /** Contains the raw error in short form if 'success' is false. - This raw error might be interpreted by the frontend and is not shown in the UI. - Some predefined values exist. - Values: - 'cancelled': request was cancelled. - etc. - */ - message?: string; - /** Contains request result if success is true and optional error details if success is false. */ - body?: any; - } - - /** On error (whenever 'success' is false), the body can provide more details. */ - export interface ErrorResponse extends Response { - body: { - /** An optional, structured error message. */ - error?: Message; - }; - } - - /** Cancel request; value of command field is 'cancel'. - The 'cancel' request is used by the frontend to indicate that it is no longer interested in the result produced by a specific request issued earlier. - This request has a hint characteristic: a debug adapter can only be expected to make a 'best effort' in honouring this request but there are no guarantees. - The 'cancel' request may return an error if it could not cancel an operation but a frontend should refrain from presenting this error to end users. - A frontend client should only call this request if the capability 'supportsCancelRequest' is true. - The request that got canceled still needs to send a response back. - This can either be a normal result ('success' attribute true) or an error response ('success' attribute false and the 'message' set to 'cancelled'). - Returning partial results from a cancelled request is possible but please note that a frontend client has no generic way for detecting that a response is partial or not. - */ - export interface CancelRequest extends Request { - // command: 'cancel'; - arguments?: CancelArguments; - } - - /** Arguments for 'cancel' request. */ - export interface CancelArguments { - /** The ID (attribute 'seq') of the request to cancel. */ - requestId?: number; - } - - /** Response to 'cancel' request. This is just an acknowledgement, so no body field is required. */ - export interface CancelResponse extends Response { - } - - /** Event message for 'initialized' event type. - This event indicates that the debug adapter is ready to accept configuration requests (e.g. SetBreakpointsRequest, SetExceptionBreakpointsRequest). - A debug adapter is expected to send this event when it is ready to accept configuration requests (but not before the 'initialize' request has finished). - The sequence of events/requests is as follows: - - adapters sends 'initialized' event (after the 'initialize' request has returned) - - frontend sends zero or more 'setBreakpoints' requests - - frontend sends one 'setFunctionBreakpoints' request - - frontend sends a 'setExceptionBreakpoints' request if one or more 'exceptionBreakpointFilters' have been defined (or if 'supportsConfigurationDoneRequest' is not defined or false) - - frontend sends other future configuration requests - - frontend sends one 'configurationDone' request to indicate the end of the configuration. - */ - export interface InitializedEvent extends Event { - // event: 'initialized'; - } - - /** Event message for 'stopped' event type. - The event indicates that the execution of the debuggee has stopped due to some condition. - This can be caused by a break point previously set, a stepping action has completed, by executing a debugger statement etc. - */ - export interface StoppedEvent extends Event { - // event: 'stopped'; - body: { - /** The reason for the event. - For backward compatibility this string is shown in the UI if the 'description' attribute is missing (but it must not be translated). - Values: 'step', 'breakpoint', 'exception', 'pause', 'entry', 'goto', 'function breakpoint', 'data breakpoint', etc. - */ - reason: string; - /** The full reason for the event, e.g. 'Paused on exception'. This string is shown in the UI as is and must be translated. */ - description?: string; - /** The thread which was stopped. */ - threadId?: number; - /** A value of true hints to the frontend that this event should not change the focus. */ - preserveFocusHint?: boolean; - /** Additional information. E.g. if reason is 'exception', text contains the exception name. This string is shown in the UI. */ - text?: string; - /** If 'allThreadsStopped' is true, a debug adapter can announce that all threads have stopped. - - The client should use this information to enable that all threads can be expanded to access their stacktraces. - - If the attribute is missing or false, only the thread with the given threadId can be expanded. - */ - allThreadsStopped?: boolean; - }; - } - - /** Event message for 'continued' event type. - The event indicates that the execution of the debuggee has continued. - Please note: a debug adapter is not expected to send this event in response to a request that implies that execution continues, e.g. 'launch' or 'continue'. - It is only necessary to send a 'continued' event if there was no previous request that implied this. - */ - export interface ContinuedEvent extends Event { - // event: 'continued'; - body: { - /** The thread which was continued. */ - threadId: number; - /** If 'allThreadsContinued' is true, a debug adapter can announce that all threads have continued. */ - allThreadsContinued?: boolean; - }; - } - - /** Event message for 'exited' event type. - The event indicates that the debuggee has exited and returns its exit code. - */ - export interface ExitedEvent extends Event { - // event: 'exited'; - body: { - /** The exit code returned from the debuggee. */ - exitCode: number; - }; - } - - /** Event message for 'terminated' event type. - The event indicates that debugging of the debuggee has terminated. This does **not** mean that the debuggee itself has exited. - */ - export interface TerminatedEvent extends Event { - // event: 'terminated'; - body?: { - /** A debug adapter may set 'restart' to true (or to an arbitrary object) to request that the front end restarts the session. - The value is not interpreted by the client and passed unmodified as an attribute '__restart' to the 'launch' and 'attach' requests. - */ - restart?: any; - }; - } - - /** Event message for 'thread' event type. - The event indicates that a thread has started or exited. - */ - export interface ThreadEvent extends Event { - // event: 'thread'; - body: { - /** The reason for the event. - Values: 'started', 'exited', etc. - */ - reason: string; - /** The identifier of the thread. */ - threadId: number; - }; - } - - /** Event message for 'output' event type. - The event indicates that the target has produced some output. - */ - export interface OutputEvent extends Event { - // event: 'output'; - body: { - /** The output category. If not specified, 'console' is assumed. - Values: 'console', 'stdout', 'stderr', 'telemetry', etc. - */ - category?: string; - /** The output to report. */ - output: string; - /** If an attribute 'variablesReference' exists and its value is > 0, the output contains objects which can be retrieved by passing 'variablesReference' to the 'variables' request. The value should be less than or equal to 2147483647 (2^31 - 1). */ - variablesReference?: number; - /** An optional source location where the output was produced. */ - source?: Source; - /** An optional source location line where the output was produced. */ - line?: number; - /** An optional source location column where the output was produced. */ - column?: number; - /** Optional data to report. For the 'telemetry' category the data will be sent to telemetry, for the other categories the data is shown in JSON format. */ - data?: any; - }; - } - - /** Event message for 'breakpoint' event type. - The event indicates that some information about a breakpoint has changed. - */ - export interface BreakpointEvent extends Event { - // event: 'breakpoint'; - body: { - /** The reason for the event. - Values: 'changed', 'new', 'removed', etc. - */ - reason: string; - /** The 'id' attribute is used to find the target breakpoint and the other attributes are used as the new values. */ - breakpoint: Breakpoint; - }; - } - - /** Event message for 'module' event type. - The event indicates that some information about a module has changed. - */ - export interface ModuleEvent extends Event { - // event: 'module'; - body: { - /** The reason for the event. */ - reason: 'new' | 'changed' | 'removed'; - /** The new, changed, or removed module. In case of 'removed' only the module id is used. */ - module: Module; - }; - } - - /** Event message for 'loadedSource' event type. - The event indicates that some source has been added, changed, or removed from the set of all loaded sources. - */ - export interface LoadedSourceEvent extends Event { - // event: 'loadedSource'; - body: { - /** The reason for the event. */ - reason: 'new' | 'changed' | 'removed'; - /** The new, changed, or removed source. */ - source: Source; - }; - } - - /** Event message for 'process' event type. - The event indicates that the debugger has begun debugging a new process. Either one that it has launched, or one that it has attached to. - */ - export interface ProcessEvent extends Event { - // event: 'process'; - body: { - /** The logical name of the process. This is usually the full path to process's executable file. Example: /home/example/myproj/program.js. */ - name: string; - /** The system process id of the debugged process. This property will be missing for non-system processes. */ - systemProcessId?: number; - /** If true, the process is running on the same computer as the debug adapter. */ - isLocalProcess?: boolean; - /** Describes how the debug engine started debugging this process. - 'launch': Process was launched under the debugger. - 'attach': Debugger attached to an existing process. - 'attachForSuspendedLaunch': A project launcher component has launched a new process in a suspended state and then asked the debugger to attach. - */ - startMethod?: 'launch' | 'attach' | 'attachForSuspendedLaunch'; - /** The size of a pointer or address for this process, in bits. This value may be used by clients when formatting addresses for display. */ - pointerSize?: number; - }; - } - - /** Event message for 'capabilities' event type. - The event indicates that one or more capabilities have changed. - Since the capabilities are dependent on the frontend and its UI, it might not be possible to change that at random times (or too late). - Consequently this event has a hint characteristic: a frontend can only be expected to make a 'best effort' in honouring individual capabilities but there are no guarantees. - Only changed capabilities need to be included, all other capabilities keep their values. - */ - export interface CapabilitiesEvent extends Event { - // event: 'capabilities'; - body: { - /** The set of updated capabilities. */ - capabilities: Capabilities; - }; - } - - /** RunInTerminal request; value of command field is 'runInTerminal'. - This request is sent from the debug adapter to the client to run a command in a terminal. This is typically used to launch the debuggee in a terminal provided by the client. - */ - export interface RunInTerminalRequest extends Request { - // command: 'runInTerminal'; - arguments: RunInTerminalRequestArguments; - } - - /** Arguments for 'runInTerminal' request. */ - export interface RunInTerminalRequestArguments { - /** What kind of terminal to launch. */ - kind?: 'integrated' | 'external'; - /** Optional title of the terminal. */ - title?: string; - /** Working directory of the command. */ - cwd: string; - /** List of arguments. The first argument is the command to run. */ - args: string[]; - /** Environment key-value pairs that are added to or removed from the default environment. */ - env?: { [key: string]: string | null; }; - } - - /** Response to 'runInTerminal' request. */ - export interface RunInTerminalResponse extends Response { - body: { - /** The process ID. The value should be less than or equal to 2147483647 (2^31 - 1). */ - processId?: number; - /** The process ID of the terminal shell. The value should be less than or equal to 2147483647 (2^31 - 1). */ - shellProcessId?: number; - }; - } - - /** Initialize request; value of command field is 'initialize'. - The 'initialize' request is sent as the first request from the client to the debug adapter in order to configure it with client capabilities and to retrieve capabilities from the debug adapter. - Until the debug adapter has responded to with an 'initialize' response, the client must not send any additional requests or events to the debug adapter. In addition the debug adapter is not allowed to send any requests or events to the client until it has responded with an 'initialize' response. - The 'initialize' request may only be sent once. - */ - export interface InitializeRequest extends Request { - // command: 'initialize'; - arguments: InitializeRequestArguments; - } - - /** Arguments for 'initialize' request. */ - export interface InitializeRequestArguments { - /** The ID of the (frontend) client using this adapter. */ - clientID?: string; - /** The human readable name of the (frontend) client using this adapter. */ - clientName?: string; - /** The ID of the debug adapter. */ - adapterID: string; - /** The ISO-639 locale of the (frontend) client using this adapter, e.g. en-US or de-CH. */ - locale?: string; - /** If true all line numbers are 1-based (default). */ - linesStartAt1?: boolean; - /** If true all column numbers are 1-based (default). */ - columnsStartAt1?: boolean; - /** Determines in what format paths are specified. The default is 'path', which is the native format. - Values: 'path', 'uri', etc. - */ - pathFormat?: string; - /** Client supports the optional type attribute for variables. */ - supportsVariableType?: boolean; - /** Client supports the paging of variables. */ - supportsVariablePaging?: boolean; - /** Client supports the runInTerminal request. */ - supportsRunInTerminalRequest?: boolean; - /** Client supports memory references. */ - supportsMemoryReferences?: boolean; - } - - /** Response to 'initialize' request. */ - export interface InitializeResponse extends Response { - /** The capabilities of this debug adapter. */ - body?: Capabilities; - } - - /** ConfigurationDone request; value of command field is 'configurationDone'. - The client of the debug protocol must send this request at the end of the sequence of configuration requests (which was started by the 'initialized' event). - */ - export interface ConfigurationDoneRequest extends Request { - // command: 'configurationDone'; - arguments?: ConfigurationDoneArguments; - } - - /** Arguments for 'configurationDone' request. */ - export interface ConfigurationDoneArguments { - } - - /** Response to 'configurationDone' request. This is just an acknowledgement, so no body field is required. */ - export interface ConfigurationDoneResponse extends Response { - } - - /** Launch request; value of command field is 'launch'. - The launch request is sent from the client to the debug adapter to start the debuggee with or without debugging (if 'noDebug' is true). Since launching is debugger/runtime specific, the arguments for this request are not part of this specification. - */ - export interface LaunchRequest extends Request { - // command: 'launch'; - arguments: LaunchRequestArguments; - } - - /** Arguments for 'launch' request. Additional attributes are implementation specific. */ - export interface LaunchRequestArguments { - /** If noDebug is true the launch request should launch the program without enabling debugging. */ - noDebug?: boolean; - /** Optional data from the previous, restarted session. - The data is sent as the 'restart' attribute of the 'terminated' event. - The client should leave the data intact. - */ - __restart?: any; - } - - /** Response to 'launch' request. This is just an acknowledgement, so no body field is required. */ - export interface LaunchResponse extends Response { - } - - /** Attach request; value of command field is 'attach'. - The attach request is sent from the client to the debug adapter to attach to a debuggee that is already running. Since attaching is debugger/runtime specific, the arguments for this request are not part of this specification. - */ - export interface AttachRequest extends Request { - // command: 'attach'; - arguments: AttachRequestArguments; - } - - /** Arguments for 'attach' request. Additional attributes are implementation specific. */ - export interface AttachRequestArguments { - /** Optional data from the previous, restarted session. - The data is sent as the 'restart' attribute of the 'terminated' event. - The client should leave the data intact. - */ - __restart?: any; - } - - /** Response to 'attach' request. This is just an acknowledgement, so no body field is required. */ - export interface AttachResponse extends Response { - } - - /** Restart request; value of command field is 'restart'. - Restarts a debug session. If the capability 'supportsRestartRequest' is missing or has the value false, - the client will implement 'restart' by terminating the debug adapter first and then launching it anew. - A debug adapter can override this default behaviour by implementing a restart request - and setting the capability 'supportsRestartRequest' to true. - */ - export interface RestartRequest extends Request { - // command: 'restart'; - arguments?: RestartArguments; - } - - /** Arguments for 'restart' request. */ - export interface RestartArguments { - } - - /** Response to 'restart' request. This is just an acknowledgement, so no body field is required. */ - export interface RestartResponse extends Response { - } - - /** Disconnect request; value of command field is 'disconnect'. - The 'disconnect' request is sent from the client to the debug adapter in order to stop debugging. It asks the debug adapter to disconnect from the debuggee and to terminate the debug adapter. If the debuggee has been started with the 'launch' request, the 'disconnect' request terminates the debuggee. If the 'attach' request was used to connect to the debuggee, 'disconnect' does not terminate the debuggee. This behavior can be controlled with the 'terminateDebuggee' argument (if supported by the debug adapter). - */ - export interface DisconnectRequest extends Request { - // command: 'disconnect'; - arguments?: DisconnectArguments; - } - - /** Arguments for 'disconnect' request. */ - export interface DisconnectArguments { - /** A value of true indicates that this 'disconnect' request is part of a restart sequence. */ - restart?: boolean; - /** Indicates whether the debuggee should be terminated when the debugger is disconnected. - If unspecified, the debug adapter is free to do whatever it thinks is best. - A client can only rely on this attribute being properly honored if a debug adapter returns true for the 'supportTerminateDebuggee' capability. - */ - terminateDebuggee?: boolean; - } - - /** Response to 'disconnect' request. This is just an acknowledgement, so no body field is required. */ - export interface DisconnectResponse extends Response { - } - - /** Terminate request; value of command field is 'terminate'. - The 'terminate' request is sent from the client to the debug adapter in order to give the debuggee a chance for terminating itself. - */ - export interface TerminateRequest extends Request { - // command: 'terminate'; - arguments?: TerminateArguments; - } - - /** Arguments for 'terminate' request. */ - export interface TerminateArguments { - /** A value of true indicates that this 'terminate' request is part of a restart sequence. */ - restart?: boolean; - } - - /** Response to 'terminate' request. This is just an acknowledgement, so no body field is required. */ - export interface TerminateResponse extends Response { - } - - /** BreakpointLocations request; value of command field is 'breakpointLocations'. - The 'breakpointLocations' request returns all possible locations for source breakpoints in a given range. - */ - export interface BreakpointLocationsRequest extends Request { - // command: 'breakpointLocations'; - arguments?: BreakpointLocationsArguments; - } - - /** Arguments for 'breakpointLocations' request. */ - export interface BreakpointLocationsArguments { - /** The source location of the breakpoints; either 'source.path' or 'source.reference' must be specified. */ - source: Source; - /** Start line of range to search possible breakpoint locations in. If only the line is specified, the request returns all possible locations in that line. */ - line: number; - /** Optional start column of range to search possible breakpoint locations in. If no start column is given, the first column in the start line is assumed. */ - column?: number; - /** Optional end line of range to search possible breakpoint locations in. If no end line is given, then the end line is assumed to be the start line. */ - endLine?: number; - /** Optional end column of range to search possible breakpoint locations in. If no end column is given, then it is assumed to be in the last column of the end line. */ - endColumn?: number; - } - - /** Response to 'breakpointLocations' request. - Contains possible locations for source breakpoints. - */ - export interface BreakpointLocationsResponse extends Response { - body: { - /** Sorted set of possible breakpoint locations. */ - breakpoints: BreakpointLocation[]; - }; - } - - /** SetBreakpoints request; value of command field is 'setBreakpoints'. - Sets multiple breakpoints for a single source and clears all previous breakpoints in that source. - To clear all breakpoint for a source, specify an empty array. - When a breakpoint is hit, a 'stopped' event (with reason 'breakpoint') is generated. - */ - export interface SetBreakpointsRequest extends Request { - // command: 'setBreakpoints'; - arguments: SetBreakpointsArguments; - } - - /** Arguments for 'setBreakpoints' request. */ - export interface SetBreakpointsArguments { - /** The source location of the breakpoints; either 'source.path' or 'source.reference' must be specified. */ - source: Source; - /** The code locations of the breakpoints. */ - breakpoints?: SourceBreakpoint[]; - /** Deprecated: The code locations of the breakpoints. */ - lines?: number[]; - /** A value of true indicates that the underlying source has been modified which results in new breakpoint locations. */ - sourceModified?: boolean; - } - - /** Response to 'setBreakpoints' request. - Returned is information about each breakpoint created by this request. - This includes the actual code location and whether the breakpoint could be verified. - The breakpoints returned are in the same order as the elements of the 'breakpoints' - (or the deprecated 'lines') array in the arguments. - */ - export interface SetBreakpointsResponse extends Response { - body: { - /** Information about the breakpoints. The array elements are in the same order as the elements of the 'breakpoints' (or the deprecated 'lines') array in the arguments. */ - breakpoints: Breakpoint[]; - }; - } - - /** SetFunctionBreakpoints request; value of command field is 'setFunctionBreakpoints'. - Replaces all existing function breakpoints with new function breakpoints. - To clear all function breakpoints, specify an empty array. - When a function breakpoint is hit, a 'stopped' event (with reason 'function breakpoint') is generated. - */ - export interface SetFunctionBreakpointsRequest extends Request { - // command: 'setFunctionBreakpoints'; - arguments: SetFunctionBreakpointsArguments; - } - - /** Arguments for 'setFunctionBreakpoints' request. */ - export interface SetFunctionBreakpointsArguments { - /** The function names of the breakpoints. */ - breakpoints: FunctionBreakpoint[]; - } - - /** Response to 'setFunctionBreakpoints' request. - Returned is information about each breakpoint created by this request. - */ - export interface SetFunctionBreakpointsResponse extends Response { - body: { - /** Information about the breakpoints. The array elements correspond to the elements of the 'breakpoints' array. */ - breakpoints: Breakpoint[]; - }; - } - - /** SetExceptionBreakpoints request; value of command field is 'setExceptionBreakpoints'. - The request configures the debuggers response to thrown exceptions. If an exception is configured to break, a 'stopped' event is fired (with reason 'exception'). - */ - export interface SetExceptionBreakpointsRequest extends Request { - // command: 'setExceptionBreakpoints'; - arguments: SetExceptionBreakpointsArguments; - } - - /** Arguments for 'setExceptionBreakpoints' request. */ - export interface SetExceptionBreakpointsArguments { - /** IDs of checked exception options. The set of IDs is returned via the 'exceptionBreakpointFilters' capability. */ - filters: string[]; - /** Configuration options for selected exceptions. */ - exceptionOptions?: ExceptionOptions[]; - } - - /** Response to 'setExceptionBreakpoints' request. This is just an acknowledgement, so no body field is required. */ - export interface SetExceptionBreakpointsResponse extends Response { - } - - /** DataBreakpointInfo request; value of command field is 'dataBreakpointInfo'. - Obtains information on a possible data breakpoint that could be set on an expression or variable. - */ - export interface DataBreakpointInfoRequest extends Request { - // command: 'dataBreakpointInfo'; - arguments: DataBreakpointInfoArguments; - } - - /** Arguments for 'dataBreakpointInfo' request. */ - export interface DataBreakpointInfoArguments { - /** Reference to the Variable container if the data breakpoint is requested for a child of the container. */ - variablesReference?: number; - /** The name of the Variable's child to obtain data breakpoint information for. If variableReference isn’t provided, this can be an expression. */ - name: string; - } - - /** Response to 'dataBreakpointInfo' request. */ - export interface DataBreakpointInfoResponse extends Response { - body: { - /** An identifier for the data on which a data breakpoint can be registered with the setDataBreakpoints request or null if no data breakpoint is available. */ - dataId: string | null; - /** UI string that describes on what data the breakpoint is set on or why a data breakpoint is not available. */ - description: string; - /** Optional attribute listing the available access types for a potential data breakpoint. A UI frontend could surface this information. */ - accessTypes?: DataBreakpointAccessType[]; - /** Optional attribute indicating that a potential data breakpoint could be persisted across sessions. */ - canPersist?: boolean; - }; - } - - /** SetDataBreakpoints request; value of command field is 'setDataBreakpoints'. - Replaces all existing data breakpoints with new data breakpoints. - To clear all data breakpoints, specify an empty array. - When a data breakpoint is hit, a 'stopped' event (with reason 'data breakpoint') is generated. - */ - export interface SetDataBreakpointsRequest extends Request { - // command: 'setDataBreakpoints'; - arguments: SetDataBreakpointsArguments; - } - - /** Arguments for 'setDataBreakpoints' request. */ - export interface SetDataBreakpointsArguments { - /** The contents of this array replaces all existing data breakpoints. An empty array clears all data breakpoints. */ - breakpoints: DataBreakpoint[]; - } - - /** Response to 'setDataBreakpoints' request. - Returned is information about each breakpoint created by this request. - */ - export interface SetDataBreakpointsResponse extends Response { - body: { - /** Information about the data breakpoints. The array elements correspond to the elements of the input argument 'breakpoints' array. */ - breakpoints: Breakpoint[]; - }; - } - - /** Continue request; value of command field is 'continue'. - The request starts the debuggee to run again. - */ - export interface ContinueRequest extends Request { - // command: 'continue'; - arguments: ContinueArguments; - } - - /** Arguments for 'continue' request. */ - export interface ContinueArguments { - /** Continue execution for the specified thread (if possible). If the backend cannot continue on a single thread but will continue on all threads, it should set the 'allThreadsContinued' attribute in the response to true. */ - threadId: number; - } - - /** Response to 'continue' request. */ - export interface ContinueResponse extends Response { - body: { - /** If true, the 'continue' request has ignored the specified thread and continued all threads instead. If this attribute is missing a value of 'true' is assumed for backward compatibility. */ - allThreadsContinued?: boolean; - }; - } - - /** Next request; value of command field is 'next'. - The request starts the debuggee to run again for one step. - The debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed. - */ - export interface NextRequest extends Request { - // command: 'next'; - arguments: NextArguments; - } - - /** Arguments for 'next' request. */ - export interface NextArguments { - /** Execute 'next' for this thread. */ - threadId: number; - } - - /** Response to 'next' request. This is just an acknowledgement, so no body field is required. */ - export interface NextResponse extends Response { - } - - /** StepIn request; value of command field is 'stepIn'. - The request starts the debuggee to step into a function/method if possible. - If it cannot step into a target, 'stepIn' behaves like 'next'. - The debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed. - If there are multiple function/method calls (or other targets) on the source line, - the optional argument 'targetId' can be used to control into which target the 'stepIn' should occur. - The list of possible targets for a given source line can be retrieved via the 'stepInTargets' request. - */ - export interface StepInRequest extends Request { - // command: 'stepIn'; - arguments: StepInArguments; - } - - /** Arguments for 'stepIn' request. */ - export interface StepInArguments { - /** Execute 'stepIn' for this thread. */ - threadId: number; - /** Optional id of the target to step into. */ - targetId?: number; - } - - /** Response to 'stepIn' request. This is just an acknowledgement, so no body field is required. */ - export interface StepInResponse extends Response { - } - - /** StepOut request; value of command field is 'stepOut'. - The request starts the debuggee to run again for one step. - The debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed. - */ - export interface StepOutRequest extends Request { - // command: 'stepOut'; - arguments: StepOutArguments; - } - - /** Arguments for 'stepOut' request. */ - export interface StepOutArguments { - /** Execute 'stepOut' for this thread. */ - threadId: number; - } - - /** Response to 'stepOut' request. This is just an acknowledgement, so no body field is required. */ - export interface StepOutResponse extends Response { - } - - /** StepBack request; value of command field is 'stepBack'. - The request starts the debuggee to run one step backwards. - The debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed. Clients should only call this request if the capability 'supportsStepBack' is true. - */ - export interface StepBackRequest extends Request { - // command: 'stepBack'; - arguments: StepBackArguments; - } - - /** Arguments for 'stepBack' request. */ - export interface StepBackArguments { - /** Execute 'stepBack' for this thread. */ - threadId: number; - } - - /** Response to 'stepBack' request. This is just an acknowledgement, so no body field is required. */ - export interface StepBackResponse extends Response { - } - - /** ReverseContinue request; value of command field is 'reverseContinue'. - The request starts the debuggee to run backward. Clients should only call this request if the capability 'supportsStepBack' is true. - */ - export interface ReverseContinueRequest extends Request { - // command: 'reverseContinue'; - arguments: ReverseContinueArguments; - } - - /** Arguments for 'reverseContinue' request. */ - export interface ReverseContinueArguments { - /** Execute 'reverseContinue' for this thread. */ - threadId: number; - } - - /** Response to 'reverseContinue' request. This is just an acknowledgement, so no body field is required. */ - export interface ReverseContinueResponse extends Response { - } - - /** RestartFrame request; value of command field is 'restartFrame'. - The request restarts execution of the specified stackframe. - The debug adapter first sends the response and then a 'stopped' event (with reason 'restart') after the restart has completed. - */ - export interface RestartFrameRequest extends Request { - // command: 'restartFrame'; - arguments: RestartFrameArguments; - } - - /** Arguments for 'restartFrame' request. */ - export interface RestartFrameArguments { - /** Restart this stackframe. */ - frameId: number; - } - - /** Response to 'restartFrame' request. This is just an acknowledgement, so no body field is required. */ - export interface RestartFrameResponse extends Response { - } - - /** Goto request; value of command field is 'goto'. - The request sets the location where the debuggee will continue to run. - This makes it possible to skip the execution of code or to executed code again. - The code between the current location and the goto target is not executed but skipped. - The debug adapter first sends the response and then a 'stopped' event with reason 'goto'. - */ - export interface GotoRequest extends Request { - // command: 'goto'; - arguments: GotoArguments; - } - - /** Arguments for 'goto' request. */ - export interface GotoArguments { - /** Set the goto target for this thread. */ - threadId: number; - /** The location where the debuggee will continue to run. */ - targetId: number; - } - - /** Response to 'goto' request. This is just an acknowledgement, so no body field is required. */ - export interface GotoResponse extends Response { - } - - /** Pause request; value of command field is 'pause'. - The request suspends the debuggee. - The debug adapter first sends the response and then a 'stopped' event (with reason 'pause') after the thread has been paused successfully. - */ - export interface PauseRequest extends Request { - // command: 'pause'; - arguments: PauseArguments; - } - - /** Arguments for 'pause' request. */ - export interface PauseArguments { - /** Pause execution for this thread. */ - threadId: number; - } - - /** Response to 'pause' request. This is just an acknowledgement, so no body field is required. */ - export interface PauseResponse extends Response { - } - - /** StackTrace request; value of command field is 'stackTrace'. - The request returns a stacktrace from the current execution state. - */ - export interface StackTraceRequest extends Request { - // command: 'stackTrace'; - arguments: StackTraceArguments; - } - - /** Arguments for 'stackTrace' request. */ - export interface StackTraceArguments { - /** Retrieve the stacktrace for this thread. */ - threadId: number; - /** The index of the first frame to return; if omitted frames start at 0. */ - startFrame?: number; - /** The maximum number of frames to return. If levels is not specified or 0, all frames are returned. */ - levels?: number; - /** Specifies details on how to format the stack frames. */ - format?: StackFrameFormat; - } - - /** Response to 'stackTrace' request. */ - export interface StackTraceResponse extends Response { - body: { - /** The frames of the stackframe. If the array has length zero, there are no stackframes available. - This means that there is no location information available. - */ - stackFrames: StackFrame[]; - /** The total number of frames available. */ - totalFrames?: number; - }; - } - - /** Scopes request; value of command field is 'scopes'. - The request returns the variable scopes for a given stackframe ID. - */ - export interface ScopesRequest extends Request { - // command: 'scopes'; - arguments: ScopesArguments; - } - - /** Arguments for 'scopes' request. */ - export interface ScopesArguments { - /** Retrieve the scopes for this stackframe. */ - frameId: number; - } - - /** Response to 'scopes' request. */ - export interface ScopesResponse extends Response { - body: { - /** The scopes of the stackframe. If the array has length zero, there are no scopes available. */ - scopes: Scope[]; - }; - } - - /** Variables request; value of command field is 'variables'. - Retrieves all child variables for the given variable reference. - An optional filter can be used to limit the fetched children to either named or indexed children. - */ - export interface VariablesRequest extends Request { - // command: 'variables'; - arguments: VariablesArguments; - } - - /** Arguments for 'variables' request. */ - export interface VariablesArguments { - /** The Variable reference. */ - variablesReference: number; - /** Optional filter to limit the child variables to either named or indexed. If omitted, both types are fetched. */ - filter?: 'indexed' | 'named'; - /** The index of the first variable to return; if omitted children start at 0. */ - start?: number; - /** The number of variables to return. If count is missing or 0, all variables are returned. */ - count?: number; - /** Specifies details on how to format the Variable values. */ - format?: ValueFormat; - } - - /** Response to 'variables' request. */ - export interface VariablesResponse extends Response { - body: { - /** All (or a range) of variables for the given variable reference. */ - variables: Variable[]; - }; - } - - /** SetVariable request; value of command field is 'setVariable'. - Set the variable with the given name in the variable container to a new value. - */ - export interface SetVariableRequest extends Request { - // command: 'setVariable'; - arguments: SetVariableArguments; - } - - /** Arguments for 'setVariable' request. */ - export interface SetVariableArguments { - /** The reference of the variable container. */ - variablesReference: number; - /** The name of the variable in the container. */ - name: string; - /** The value of the variable. */ - value: string; - /** Specifies details on how to format the response value. */ - format?: ValueFormat; - } - - /** Response to 'setVariable' request. */ - export interface SetVariableResponse extends Response { - body: { - /** The new value of the variable. */ - value: string; - /** The type of the new value. Typically shown in the UI when hovering over the value. */ - type?: string; - /** If variablesReference is > 0, the new value is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. The value should be less than or equal to 2147483647 (2^31 - 1). */ - variablesReference?: number; - /** The number of named child variables. - The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). - */ - namedVariables?: number; - /** The number of indexed child variables. - The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). - */ - indexedVariables?: number; - }; - } - - /** Source request; value of command field is 'source'. - The request retrieves the source code for a given source reference. - */ - export interface SourceRequest extends Request { - // command: 'source'; - arguments: SourceArguments; - } - - /** Arguments for 'source' request. */ - export interface SourceArguments { - /** Specifies the source content to load. Either source.path or source.sourceReference must be specified. */ - source?: Source; - /** The reference to the source. This is the same as source.sourceReference. This is provided for backward compatibility since old backends do not understand the 'source' attribute. */ - sourceReference: number; - } - - /** Response to 'source' request. */ - export interface SourceResponse extends Response { - body: { - /** Content of the source reference. */ - content: string; - /** Optional content type (mime type) of the source. */ - mimeType?: string; - }; - } - - /** Threads request; value of command field is 'threads'. - The request retrieves a list of all threads. - */ - export interface ThreadsRequest extends Request { - // command: 'threads'; - } - - /** Response to 'threads' request. */ - export interface ThreadsResponse extends Response { - body: { - /** All threads. */ - threads: Thread[]; - }; - } - - /** TerminateThreads request; value of command field is 'terminateThreads'. - The request terminates the threads with the given ids. - */ - export interface TerminateThreadsRequest extends Request { - // command: 'terminateThreads'; - arguments: TerminateThreadsArguments; - } - - /** Arguments for 'terminateThreads' request. */ - export interface TerminateThreadsArguments { - /** Ids of threads to be terminated. */ - threadIds?: number[]; - } - - /** Response to 'terminateThreads' request. This is just an acknowledgement, so no body field is required. */ - export interface TerminateThreadsResponse extends Response { - } - - /** Modules request; value of command field is 'modules'. - Modules can be retrieved from the debug adapter with the ModulesRequest which can either return all modules or a range of modules to support paging. - */ - export interface ModulesRequest extends Request { - // command: 'modules'; - arguments: ModulesArguments; - } - - /** Arguments for 'modules' request. */ - export interface ModulesArguments { - /** The index of the first module to return; if omitted modules start at 0. */ - startModule?: number; - /** The number of modules to return. If moduleCount is not specified or 0, all modules are returned. */ - moduleCount?: number; - } - - /** Response to 'modules' request. */ - export interface ModulesResponse extends Response { - body: { - /** All modules or range of modules. */ - modules: Module[]; - /** The total number of modules available. */ - totalModules?: number; - }; - } - - /** LoadedSources request; value of command field is 'loadedSources'. - Retrieves the set of all sources currently loaded by the debugged process. - */ - export interface LoadedSourcesRequest extends Request { - // command: 'loadedSources'; - arguments?: LoadedSourcesArguments; - } - - /** Arguments for 'loadedSources' request. */ - export interface LoadedSourcesArguments { - } - - /** Response to 'loadedSources' request. */ - export interface LoadedSourcesResponse extends Response { - body: { - /** Set of loaded sources. */ - sources: Source[]; - }; - } - - /** Evaluate request; value of command field is 'evaluate'. - Evaluates the given expression in the context of the top most stack frame. - The expression has access to any variables and arguments that are in scope. - */ - export interface EvaluateRequest extends Request { - // command: 'evaluate'; - arguments: EvaluateArguments; - } - - /** Arguments for 'evaluate' request. */ - export interface EvaluateArguments { - /** The expression to evaluate. */ - expression: string; - /** Evaluate the expression in the scope of this stack frame. If not specified, the expression is evaluated in the global scope. */ - frameId?: number; - /** The context in which the evaluate request is run. - Values: - 'watch': evaluate is run in a watch. - 'repl': evaluate is run from REPL console. - 'hover': evaluate is run from a data hover. - etc. - */ - context?: string; - /** Specifies details on how to format the Evaluate result. */ - format?: ValueFormat; - } - - /** Response to 'evaluate' request. */ - export interface EvaluateResponse extends Response { - body: { - /** The result of the evaluate request. */ - result: string; - /** The optional type of the evaluate result. */ - type?: string; - /** Properties of a evaluate result that can be used to determine how to render the result in the UI. */ - presentationHint?: VariablePresentationHint; - /** If variablesReference is > 0, the evaluate result is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. The value should be less than or equal to 2147483647 (2^31 - 1). */ - variablesReference: number; - /** The number of named child variables. - The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). - */ - namedVariables?: number; - /** The number of indexed child variables. - The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). - */ - indexedVariables?: number; - /** Memory reference to a location appropriate for this result. For pointer type eval results, this is generally a reference to the memory address contained in the pointer. */ - memoryReference?: string; - }; - } - - /** SetExpression request; value of command field is 'setExpression'. - Evaluates the given 'value' expression and assigns it to the 'expression' which must be a modifiable l-value. - The expressions have access to any variables and arguments that are in scope of the specified frame. - */ - export interface SetExpressionRequest extends Request { - // command: 'setExpression'; - arguments: SetExpressionArguments; - } - - /** Arguments for 'setExpression' request. */ - export interface SetExpressionArguments { - /** The l-value expression to assign to. */ - expression: string; - /** The value expression to assign to the l-value expression. */ - value: string; - /** Evaluate the expressions in the scope of this stack frame. If not specified, the expressions are evaluated in the global scope. */ - frameId?: number; - /** Specifies how the resulting value should be formatted. */ - format?: ValueFormat; - } - - /** Response to 'setExpression' request. */ - export interface SetExpressionResponse extends Response { - body: { - /** The new value of the expression. */ - value: string; - /** The optional type of the value. */ - type?: string; - /** Properties of a value that can be used to determine how to render the result in the UI. */ - presentationHint?: VariablePresentationHint; - /** If variablesReference is > 0, the value is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. The value should be less than or equal to 2147483647 (2^31 - 1). */ - variablesReference?: number; - /** The number of named child variables. - The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). - */ - namedVariables?: number; - /** The number of indexed child variables. - The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). - */ - indexedVariables?: number; - }; - } - - /** StepInTargets request; value of command field is 'stepInTargets'. - This request retrieves the possible stepIn targets for the specified stack frame. - These targets can be used in the 'stepIn' request. - The StepInTargets may only be called if the 'supportsStepInTargetsRequest' capability exists and is true. - */ - export interface StepInTargetsRequest extends Request { - // command: 'stepInTargets'; - arguments: StepInTargetsArguments; - } - - /** Arguments for 'stepInTargets' request. */ - export interface StepInTargetsArguments { - /** The stack frame for which to retrieve the possible stepIn targets. */ - frameId: number; - } - - /** Response to 'stepInTargets' request. */ - export interface StepInTargetsResponse extends Response { - body: { - /** The possible stepIn targets of the specified source location. */ - targets: StepInTarget[]; - }; - } - - /** GotoTargets request; value of command field is 'gotoTargets'. - This request retrieves the possible goto targets for the specified source location. - These targets can be used in the 'goto' request. - The GotoTargets request may only be called if the 'supportsGotoTargetsRequest' capability exists and is true. - */ - export interface GotoTargetsRequest extends Request { - // command: 'gotoTargets'; - arguments: GotoTargetsArguments; - } - - /** Arguments for 'gotoTargets' request. */ - export interface GotoTargetsArguments { - /** The source location for which the goto targets are determined. */ - source: Source; - /** The line location for which the goto targets are determined. */ - line: number; - /** An optional column location for which the goto targets are determined. */ - column?: number; - } - - /** Response to 'gotoTargets' request. */ - export interface GotoTargetsResponse extends Response { - body: { - /** The possible goto targets of the specified location. */ - targets: GotoTarget[]; - }; - } - - /** Completions request; value of command field is 'completions'. - Returns a list of possible completions for a given caret position and text. - The CompletionsRequest may only be called if the 'supportsCompletionsRequest' capability exists and is true. - */ - export interface CompletionsRequest extends Request { - // command: 'completions'; - arguments: CompletionsArguments; - } - - /** Arguments for 'completions' request. */ - export interface CompletionsArguments { - /** Returns completions in the scope of this stack frame. If not specified, the completions are returned for the global scope. */ - frameId?: number; - /** One or more source lines. Typically this is the text a user has typed into the debug console before he asked for completion. */ - text: string; - /** The character position for which to determine the completion proposals. */ - column: number; - /** An optional line for which to determine the completion proposals. If missing the first line of the text is assumed. */ - line?: number; - } - - /** Response to 'completions' request. */ - export interface CompletionsResponse extends Response { - body: { - /** The possible completions for . */ - targets: CompletionItem[]; - }; - } - - /** ExceptionInfo request; value of command field is 'exceptionInfo'. - Retrieves the details of the exception that caused this event to be raised. - */ - export interface ExceptionInfoRequest extends Request { - // command: 'exceptionInfo'; - arguments: ExceptionInfoArguments; - } - - /** Arguments for 'exceptionInfo' request. */ - export interface ExceptionInfoArguments { - /** Thread for which exception information should be retrieved. */ - threadId: number; - } - - /** Response to 'exceptionInfo' request. */ - export interface ExceptionInfoResponse extends Response { - body: { - /** ID of the exception that was thrown. */ - exceptionId: string; - /** Descriptive text for the exception provided by the debug adapter. */ - description?: string; - /** Mode that caused the exception notification to be raised. */ - breakMode: ExceptionBreakMode; - /** Detailed information about the exception. */ - details?: ExceptionDetails; - }; - } - - /** ReadMemory request; value of command field is 'readMemory'. - Reads bytes from memory at the provided location. - */ - export interface ReadMemoryRequest extends Request { - // command: 'readMemory'; - arguments: ReadMemoryArguments; - } - - /** Arguments for 'readMemory' request. */ - export interface ReadMemoryArguments { - /** Memory reference to the base location from which data should be read. */ - memoryReference: string; - /** Optional offset (in bytes) to be applied to the reference location before reading data. Can be negative. */ - offset?: number; - /** Number of bytes to read at the specified location and offset. */ - count: number; - } - - /** Response to 'readMemory' request. */ - export interface ReadMemoryResponse extends Response { - body?: { - /** The address of the first byte of data returned. Treated as a hex value if prefixed with '0x', or as a decimal value otherwise. */ - address: string; - /** The number of unreadable bytes encountered after the last successfully read byte. This can be used to determine the number of bytes that must be skipped before a subsequent 'readMemory' request will succeed. */ - unreadableBytes?: number; - /** The bytes read from memory, encoded using base64. */ - data?: string; - }; - } - - /** Disassemble request; value of command field is 'disassemble'. - Disassembles code stored at the provided location. - */ - export interface DisassembleRequest extends Request { - // command: 'disassemble'; - arguments: DisassembleArguments; - } - - /** Arguments for 'disassemble' request. */ - export interface DisassembleArguments { - /** Memory reference to the base location containing the instructions to disassemble. */ - memoryReference: string; - /** Optional offset (in bytes) to be applied to the reference location before disassembling. Can be negative. */ - offset?: number; - /** Optional offset (in instructions) to be applied after the byte offset (if any) before disassembling. Can be negative. */ - instructionOffset?: number; - /** Number of instructions to disassemble starting at the specified location and offset. An adapter must return exactly this number of instructions - any unavailable instructions should be replaced with an implementation-defined 'invalid instruction' value. */ - instructionCount: number; - /** If true, the adapter should attempt to resolve memory addresses and other values to symbolic names. */ - resolveSymbols?: boolean; - } - - /** Response to 'disassemble' request. */ - export interface DisassembleResponse extends Response { - body?: { - /** The list of disassembled instructions. */ - instructions: DisassembledInstruction[]; - }; - } - - /** Information about the capabilities of a debug adapter. */ - export interface Capabilities { - /** The debug adapter supports the 'configurationDone' request. */ - supportsConfigurationDoneRequest?: boolean; - /** The debug adapter supports function breakpoints. */ - supportsFunctionBreakpoints?: boolean; - /** The debug adapter supports conditional breakpoints. */ - supportsConditionalBreakpoints?: boolean; - /** The debug adapter supports breakpoints that break execution after a specified number of hits. */ - supportsHitConditionalBreakpoints?: boolean; - /** The debug adapter supports a (side effect free) evaluate request for data hovers. */ - supportsEvaluateForHovers?: boolean; - /** Available filters or options for the setExceptionBreakpoints request. */ - exceptionBreakpointFilters?: ExceptionBreakpointsFilter[]; - /** The debug adapter supports stepping back via the 'stepBack' and 'reverseContinue' requests. */ - supportsStepBack?: boolean; - /** The debug adapter supports setting a variable to a value. */ - supportsSetVariable?: boolean; - /** The debug adapter supports restarting a frame. */ - supportsRestartFrame?: boolean; - /** The debug adapter supports the 'gotoTargets' request. */ - supportsGotoTargetsRequest?: boolean; - /** The debug adapter supports the 'stepInTargets' request. */ - supportsStepInTargetsRequest?: boolean; - /** The debug adapter supports the 'completions' request. */ - supportsCompletionsRequest?: boolean; - /** The set of characters that should trigger completion in a REPL. If not specified, the UI should assume the '.' character. */ - completionTriggerCharacters?: string[]; - /** The debug adapter supports the 'modules' request. */ - supportsModulesRequest?: boolean; - /** The set of additional module information exposed by the debug adapter. */ - additionalModuleColumns?: ColumnDescriptor[]; - /** Checksum algorithms supported by the debug adapter. */ - supportedChecksumAlgorithms?: ChecksumAlgorithm[]; - /** The debug adapter supports the 'restart' request. In this case a client should not implement 'restart' by terminating and relaunching the adapter but by calling the RestartRequest. */ - supportsRestartRequest?: boolean; - /** The debug adapter supports 'exceptionOptions' on the setExceptionBreakpoints request. */ - supportsExceptionOptions?: boolean; - /** The debug adapter supports a 'format' attribute on the stackTraceRequest, variablesRequest, and evaluateRequest. */ - supportsValueFormattingOptions?: boolean; - /** The debug adapter supports the 'exceptionInfo' request. */ - supportsExceptionInfoRequest?: boolean; - /** The debug adapter supports the 'terminateDebuggee' attribute on the 'disconnect' request. */ - supportTerminateDebuggee?: boolean; - /** The debug adapter supports the delayed loading of parts of the stack, which requires that both the 'startFrame' and 'levels' arguments and the 'totalFrames' result of the 'StackTrace' request are supported. */ - supportsDelayedStackTraceLoading?: boolean; - /** The debug adapter supports the 'loadedSources' request. */ - supportsLoadedSourcesRequest?: boolean; - /** The debug adapter supports logpoints by interpreting the 'logMessage' attribute of the SourceBreakpoint. */ - supportsLogPoints?: boolean; - /** The debug adapter supports the 'terminateThreads' request. */ - supportsTerminateThreadsRequest?: boolean; - /** The debug adapter supports the 'setExpression' request. */ - supportsSetExpression?: boolean; - /** The debug adapter supports the 'terminate' request. */ - supportsTerminateRequest?: boolean; - /** The debug adapter supports data breakpoints. */ - supportsDataBreakpoints?: boolean; - /** The debug adapter supports the 'readMemory' request. */ - supportsReadMemoryRequest?: boolean; - /** The debug adapter supports the 'disassemble' request. */ - supportsDisassembleRequest?: boolean; - /** The debug adapter supports the 'cancel' request. */ - supportsCancelRequest?: boolean; - /** The debug adapter supports the 'breakpointLocations' request. */ - supportsBreakpointLocationsRequest?: boolean; - } - - /** An ExceptionBreakpointsFilter is shown in the UI as an option for configuring how exceptions are dealt with. */ - export interface ExceptionBreakpointsFilter { - /** The internal ID of the filter. This value is passed to the setExceptionBreakpoints request. */ - filter: string; - /** The name of the filter. This will be shown in the UI. */ - label: string; - /** Initial value of the filter. If not specified a value 'false' is assumed. */ - default?: boolean; - } - - /** A structured message object. Used to return errors from requests. */ - export interface Message { - /** Unique identifier for the message. */ - id: number; - /** A format string for the message. Embedded variables have the form '{name}'. - If variable name starts with an underscore character, the variable does not contain user data (PII) and can be safely used for telemetry purposes. - */ - format: string; - /** An object used as a dictionary for looking up the variables in the format string. */ - variables?: { [key: string]: string; }; - /** If true send to telemetry. */ - sendTelemetry?: boolean; - /** If true show user. */ - showUser?: boolean; - /** An optional url where additional information about this message can be found. */ - url?: string; - /** An optional label that is presented to the user as the UI for opening the url. */ - urlLabel?: string; - } - - /** A Module object represents a row in the modules view. - Two attributes are mandatory: an id identifies a module in the modules view and is used in a ModuleEvent for identifying a module for adding, updating or deleting. - The name is used to minimally render the module in the UI. - - Additional attributes can be added to the module. They will show up in the module View if they have a corresponding ColumnDescriptor. - - To avoid an unnecessary proliferation of additional attributes with similar semantics but different names - we recommend to re-use attributes from the 'recommended' list below first, and only introduce new attributes if nothing appropriate could be found. - */ - export interface Module { - /** Unique identifier for the module. */ - id: number | string; - /** A name of the module. */ - name: string; - /** optional but recommended attributes. - always try to use these first before introducing additional attributes. - - Logical full path to the module. The exact definition is implementation defined, but usually this would be a full path to the on-disk file for the module. - */ - path?: string; - /** True if the module is optimized. */ - isOptimized?: boolean; - /** True if the module is considered 'user code' by a debugger that supports 'Just My Code'. */ - isUserCode?: boolean; - /** Version of Module. */ - version?: string; - /** User understandable description of if symbols were found for the module (ex: 'Symbols Loaded', 'Symbols not found', etc. */ - symbolStatus?: string; - /** Logical full path to the symbol file. The exact definition is implementation defined. */ - symbolFilePath?: string; - /** Module created or modified. */ - dateTimeStamp?: string; - /** Address range covered by this module. */ - addressRange?: string; - } - - /** A ColumnDescriptor specifies what module attribute to show in a column of the ModulesView, how to format it, and what the column's label should be. - It is only used if the underlying UI actually supports this level of customization. - */ - export interface ColumnDescriptor { - /** Name of the attribute rendered in this column. */ - attributeName: string; - /** Header UI label of column. */ - label: string; - /** Format to use for the rendered values in this column. TBD how the format strings looks like. */ - format?: string; - /** Datatype of values in this column. Defaults to 'string' if not specified. */ - type?: 'string' | 'number' | 'boolean' | 'unixTimestampUTC'; - /** Width of this column in characters (hint only). */ - width?: number; - } - - /** The ModulesViewDescriptor is the container for all declarative configuration options of a ModuleView. - For now it only specifies the columns to be shown in the modules view. - */ - export interface ModulesViewDescriptor { - columns: ColumnDescriptor[]; - } - - /** A Thread */ - export interface Thread { - /** Unique identifier for the thread. */ - id: number; - /** A name of the thread. */ - name: string; - } - - /** A Source is a descriptor for source code. It is returned from the debug adapter as part of a StackFrame and it is used by clients when specifying breakpoints. */ - export interface Source { - /** The short name of the source. Every source returned from the debug adapter has a name. When sending a source to the debug adapter this name is optional. */ - name?: string; - /** The path of the source to be shown in the UI. It is only used to locate and load the content of the source if no sourceReference is specified (or its value is 0). */ - path?: string; - /** If sourceReference > 0 the contents of the source must be retrieved through the SourceRequest (even if a path is specified). A sourceReference is only valid for a session, so it must not be used to persist a source. The value should be less than or equal to 2147483647 (2^31 - 1). */ - sourceReference?: number; - /** An optional hint for how to present the source in the UI. A value of 'deemphasize' can be used to indicate that the source is not available or that it is skipped on stepping. */ - presentationHint?: 'normal' | 'emphasize' | 'deemphasize'; - /** The (optional) origin of this source: possible values 'internal module', 'inlined content from source map', etc. */ - origin?: string; - /** An optional list of sources that are related to this source. These may be the source that generated this source. */ - sources?: Source[]; - /** Optional data that a debug adapter might want to loop through the client. The client should leave the data intact and persist it across sessions. The client should not interpret the data. */ - adapterData?: any; - /** The checksums associated with this file. */ - checksums?: Checksum[]; - } - - /** A Stackframe contains the source location. */ - export interface StackFrame { - /** An identifier for the stack frame. It must be unique across all threads. This id can be used to retrieve the scopes of the frame with the 'scopesRequest' or to restart the execution of a stackframe. */ - id: number; - /** The name of the stack frame, typically a method name. */ - name: string; - /** The optional source of the frame. */ - source?: Source; - /** The line within the file of the frame. If source is null or doesn't exist, line is 0 and must be ignored. */ - line: number; - /** The column within the line. If source is null or doesn't exist, column is 0 and must be ignored. */ - column: number; - /** An optional end line of the range covered by the stack frame. */ - endLine?: number; - /** An optional end column of the range covered by the stack frame. */ - endColumn?: number; - /** Optional memory reference for the current instruction pointer in this frame. */ - instructionPointerReference?: string; - /** The module associated with this frame, if any. */ - moduleId?: number | string; - /** An optional hint for how to present this frame in the UI. A value of 'label' can be used to indicate that the frame is an artificial frame that is used as a visual label or separator. A value of 'subtle' can be used to change the appearance of a frame in a 'subtle' way. */ - presentationHint?: 'normal' | 'label' | 'subtle'; - } - - /** A Scope is a named container for variables. Optionally a scope can map to a source or a range within a source. */ - export interface Scope { - /** Name of the scope such as 'Arguments', 'Locals', or 'Registers'. This string is shown in the UI as is and can be translated. */ - name: string; - /** An optional hint for how to present this scope in the UI. If this attribute is missing, the scope is shown with a generic UI. - Values: - 'arguments': Scope contains method arguments. - 'locals': Scope contains local variables. - 'registers': Scope contains registers. Only a single 'registers' scope should be returned from a 'scopes' request. - etc. - */ - presentationHint?: string; - /** The variables of this scope can be retrieved by passing the value of variablesReference to the VariablesRequest. */ - variablesReference: number; - /** The number of named variables in this scope. - The client can use this optional information to present the variables in a paged UI and fetch them in chunks. - */ - namedVariables?: number; - /** The number of indexed variables in this scope. - The client can use this optional information to present the variables in a paged UI and fetch them in chunks. - */ - indexedVariables?: number; - /** If true, the number of variables in this scope is large or expensive to retrieve. */ - expensive: boolean; - /** Optional source for this scope. */ - source?: Source; - /** Optional start line of the range covered by this scope. */ - line?: number; - /** Optional start column of the range covered by this scope. */ - column?: number; - /** Optional end line of the range covered by this scope. */ - endLine?: number; - /** Optional end column of the range covered by this scope. */ - endColumn?: number; - } - - /** A Variable is a name/value pair. - Optionally a variable can have a 'type' that is shown if space permits or when hovering over the variable's name. - An optional 'kind' is used to render additional properties of the variable, e.g. different icons can be used to indicate that a variable is public or private. - If the value is structured (has children), a handle is provided to retrieve the children with the VariablesRequest. - If the number of named or indexed children is large, the numbers should be returned via the optional 'namedVariables' and 'indexedVariables' attributes. - The client can use this optional information to present the children in a paged UI and fetch them in chunks. - */ - export interface Variable { - /** The variable's name. */ - name: string; - /** The variable's value. This can be a multi-line text, e.g. for a function the body of a function. */ - value: string; - /** The type of the variable's value. Typically shown in the UI when hovering over the value. */ - type?: string; - /** Properties of a variable that can be used to determine how to render the variable in the UI. */ - presentationHint?: VariablePresentationHint; - /** Optional evaluatable name of this variable which can be passed to the 'EvaluateRequest' to fetch the variable's value. */ - evaluateName?: string; - /** If variablesReference is > 0, the variable is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. */ - variablesReference: number; - /** The number of named child variables. - The client can use this optional information to present the children in a paged UI and fetch them in chunks. - */ - namedVariables?: number; - /** The number of indexed child variables. - The client can use this optional information to present the children in a paged UI and fetch them in chunks. - */ - indexedVariables?: number; - /** Optional memory reference for the variable if the variable represents executable code, such as a function pointer. */ - memoryReference?: string; - } - - /** Optional properties of a variable that can be used to determine how to render the variable in the UI. */ - export interface VariablePresentationHint { - /** The kind of variable. Before introducing additional values, try to use the listed values. - Values: - 'property': Indicates that the object is a property. - 'method': Indicates that the object is a method. - 'class': Indicates that the object is a class. - 'data': Indicates that the object is data. - 'event': Indicates that the object is an event. - 'baseClass': Indicates that the object is a base class. - 'innerClass': Indicates that the object is an inner class. - 'interface': Indicates that the object is an interface. - 'mostDerivedClass': Indicates that the object is the most derived class. - 'virtual': Indicates that the object is virtual, that means it is a synthetic object introduced by the adapter for rendering purposes, e.g. an index range for large arrays. - 'dataBreakpoint': Indicates that a data breakpoint is registered for the object. - etc. - */ - kind?: string; - /** Set of attributes represented as an array of strings. Before introducing additional values, try to use the listed values. - Values: - 'static': Indicates that the object is static. - 'constant': Indicates that the object is a constant. - 'readOnly': Indicates that the object is read only. - 'rawString': Indicates that the object is a raw string. - 'hasObjectId': Indicates that the object can have an Object ID created for it. - 'canHaveObjectId': Indicates that the object has an Object ID associated with it. - 'hasSideEffects': Indicates that the evaluation had side effects. - etc. - */ - attributes?: string[]; - /** Visibility of variable. Before introducing additional values, try to use the listed values. - Values: 'public', 'private', 'protected', 'internal', 'final', etc. - */ - visibility?: string; - } - - /** Properties of a breakpoint location returned from the 'breakpointLocations' request. */ - export interface BreakpointLocation { - /** Start line of breakpoint location. */ - line: number; - /** Optional start column of breakpoint location. */ - column?: number; - /** Optional end line of breakpoint location if the location covers a range. */ - endLine?: number; - /** Optional end column of breakpoint location if the location covers a range. */ - endColumn?: number; - } - - /** Properties of a breakpoint or logpoint passed to the setBreakpoints request. */ - export interface SourceBreakpoint { - /** The source line of the breakpoint or logpoint. */ - line: number; - /** An optional source column of the breakpoint. */ - column?: number; - /** An optional expression for conditional breakpoints. */ - condition?: string; - /** An optional expression that controls how many hits of the breakpoint are ignored. The backend is expected to interpret the expression as needed. */ - hitCondition?: string; - /** If this attribute exists and is non-empty, the backend must not 'break' (stop) but log the message instead. Expressions within {} are interpolated. */ - logMessage?: string; - } - - /** Properties of a breakpoint passed to the setFunctionBreakpoints request. */ - export interface FunctionBreakpoint { - /** The name of the function. */ - name: string; - /** An optional expression for conditional breakpoints. */ - condition?: string; - /** An optional expression that controls how many hits of the breakpoint are ignored. The backend is expected to interpret the expression as needed. */ - hitCondition?: string; - } - - /** This enumeration defines all possible access types for data breakpoints. */ - export type DataBreakpointAccessType = 'read' | 'write' | 'readWrite'; - - /** Properties of a data breakpoint passed to the setDataBreakpoints request. */ - export interface DataBreakpoint { - /** An id representing the data. This id is returned from the dataBreakpointInfo request. */ - dataId: string; - /** The access type of the data. */ - accessType?: DataBreakpointAccessType; - /** An optional expression for conditional breakpoints. */ - condition?: string; - /** An optional expression that controls how many hits of the breakpoint are ignored. The backend is expected to interpret the expression as needed. */ - hitCondition?: string; - } - - /** Information about a Breakpoint created in setBreakpoints or setFunctionBreakpoints. */ - export interface Breakpoint { - /** An optional identifier for the breakpoint. It is needed if breakpoint events are used to update or remove breakpoints. */ - id?: number; - /** If true breakpoint could be set (but not necessarily at the desired location). */ - verified: boolean; - /** An optional message about the state of the breakpoint. This is shown to the user and can be used to explain why a breakpoint could not be verified. */ - message?: string; - /** The source where the breakpoint is located. */ - source?: Source; - /** The start line of the actual range covered by the breakpoint. */ - line?: number; - /** An optional start column of the actual range covered by the breakpoint. */ - column?: number; - /** An optional end line of the actual range covered by the breakpoint. */ - endLine?: number; - /** An optional end column of the actual range covered by the breakpoint. If no end line is given, then the end column is assumed to be in the start line. */ - endColumn?: number; - } - - /** A StepInTarget can be used in the 'stepIn' request and determines into which single target the stepIn request should step. */ - export interface StepInTarget { - /** Unique identifier for a stepIn target. */ - id: number; - /** The name of the stepIn target (shown in the UI). */ - label: string; - } - - /** A GotoTarget describes a code location that can be used as a target in the 'goto' request. - The possible goto targets can be determined via the 'gotoTargets' request. - */ - export interface GotoTarget { - /** Unique identifier for a goto target. This is used in the goto request. */ - id: number; - /** The name of the goto target (shown in the UI). */ - label: string; - /** The line of the goto target. */ - line: number; - /** An optional column of the goto target. */ - column?: number; - /** An optional end line of the range covered by the goto target. */ - endLine?: number; - /** An optional end column of the range covered by the goto target. */ - endColumn?: number; - /** Optional memory reference for the instruction pointer value represented by this target. */ - instructionPointerReference?: string; - } - - /** CompletionItems are the suggestions returned from the CompletionsRequest. */ - export interface CompletionItem { - /** The label of this completion item. By default this is also the text that is inserted when selecting this completion. */ - label: string; - /** If text is not falsy then it is inserted instead of the label. */ - text?: string; - /** A string that should be used when comparing this item with other items. When `falsy` the label is used. */ - sortText?: string; - /** The item's type. Typically the client uses this information to render the item in the UI with an icon. */ - type?: CompletionItemType; - /** This value determines the location (in the CompletionsRequest's 'text' attribute) where the completion text is added. - If missing the text is added at the location specified by the CompletionsRequest's 'column' attribute. - */ - start?: number; - /** This value determines how many characters are overwritten by the completion text. - If missing the value 0 is assumed which results in the completion text being inserted. - */ - length?: number; - } - - /** Some predefined types for the CompletionItem. Please note that not all clients have specific icons for all of them. */ - export type CompletionItemType = 'method' | 'function' | 'constructor' | 'field' | 'variable' | 'class' | 'interface' | 'module' | 'property' | 'unit' | 'value' | 'enum' | 'keyword' | 'snippet' | 'text' | 'color' | 'file' | 'reference' | 'customcolor'; - - /** Names of checksum algorithms that may be supported by a debug adapter. */ - export type ChecksumAlgorithm = 'MD5' | 'SHA1' | 'SHA256' | 'timestamp'; - - /** The checksum of an item calculated by the specified algorithm. */ - export interface Checksum { - /** The algorithm used to calculate this checksum. */ - algorithm: ChecksumAlgorithm; - /** Value of the checksum. */ - checksum: string; - } - - /** Provides formatting information for a value. */ - export interface ValueFormat { - /** Display the value in hex. */ - hex?: boolean; - } - - /** Provides formatting information for a stack frame. */ - export interface StackFrameFormat extends ValueFormat { - /** Displays parameters for the stack frame. */ - parameters?: boolean; - /** Displays the types of parameters for the stack frame. */ - parameterTypes?: boolean; - /** Displays the names of parameters for the stack frame. */ - parameterNames?: boolean; - /** Displays the values of parameters for the stack frame. */ - parameterValues?: boolean; - /** Displays the line number of the stack frame. */ - line?: boolean; - /** Displays the module of the stack frame. */ - module?: boolean; - /** Includes all stack frames, including those the debug adapter might otherwise hide. */ - includeAll?: boolean; - } - - /** An ExceptionOptions assigns configuration options to a set of exceptions. */ - export interface ExceptionOptions { - /** A path that selects a single or multiple exceptions in a tree. If 'path' is missing, the whole tree is selected. By convention the first segment of the path is a category that is used to group exceptions in the UI. */ - path?: ExceptionPathSegment[]; - /** Condition when a thrown exception should result in a break. */ - breakMode: ExceptionBreakMode; - } - - /** This enumeration defines all possible conditions when a thrown exception should result in a break. - never: never breaks, - always: always breaks, - unhandled: breaks when exception unhandled, - userUnhandled: breaks if the exception is not handled by user code. - */ - export type ExceptionBreakMode = 'never' | 'always' | 'unhandled' | 'userUnhandled'; - - /** An ExceptionPathSegment represents a segment in a path that is used to match leafs or nodes in a tree of exceptions. If a segment consists of more than one name, it matches the names provided if 'negate' is false or missing or it matches anything except the names provided if 'negate' is true. */ - export interface ExceptionPathSegment { - /** If false or missing this segment matches the names provided, otherwise it matches anything except the names provided. */ - negate?: boolean; - /** Depending on the value of 'negate' the names that should match or not match. */ - names: string[]; - } - - /** Detailed information about an exception that has occurred. */ - export interface ExceptionDetails { - /** Message contained in the exception. */ - message?: string; - /** Short type name of the exception object. */ - typeName?: string; - /** Fully-qualified type name of the exception object. */ - fullTypeName?: string; - /** Optional expression that can be evaluated in the current scope to obtain the exception object. */ - evaluateName?: string; - /** Stack trace at the time the exception was thrown. */ - stackTrace?: string; - /** Details of the exception contained by this exception, if any. */ - innerException?: ExceptionDetails[]; - } - - /** Represents a single disassembled instruction. */ - export interface DisassembledInstruction { - /** The address of the instruction. Treated as a hex value if prefixed with '0x', or as a decimal value otherwise. */ - address: string; - /** Optional raw bytes representing the instruction and its operands, in an implementation-defined format. */ - instructionBytes?: string; - /** Text representing the instruction and its operands, in an implementation-defined format. */ - instruction: string; - /** Name of the symbol that corresponds with the location of this instruction, if any. */ - symbol?: string; - /** Source location that corresponds to this instruction, if any. Should always be set (if available) on the first instruction returned, but can be omitted afterwards if this instruction maps to the same source file as the previous instruction. */ - location?: Source; - /** The line within the source location that corresponds to this instruction, if any. */ - line?: number; - /** The column within the line that corresponds to this instruction, if any. */ - column?: number; - /** The end line of the range that corresponds to this instruction, if any. */ - endLine?: number; - /** The end column of the range that corresponds to this instruction, if any. */ - endColumn?: number; - } -} - -//------------------------------------------------------------------------------------------------------------------------------ - -export class Message implements DebugProtocol.ProtocolMessage { - seq: number; - type: string; - - public constructor(type: string) { - this.seq = 0; - this.type = type; - } -} - -export class Response extends Message implements DebugProtocol.Response { - request_seq: number; - success: boolean; - command: string; - - public constructor(request: DebugProtocol.Request, message?: string) { - super('response'); - this.request_seq = request.seq; - this.command = request.command; - if (message) { - this.success = false; - (<any>this).message = message; - } else { - this.success = true; - } - } -} - -export class Event extends Message implements DebugProtocol.Event { - event: string; - - public constructor(event: string, body?: any) { - super('event'); - this.event = event; - if (body) { - (<any>this).body = body; - } - } -} - -//-------------------------------------------------------------------------------------------------------------------------------- - -export class ProtocolServer implements vscode.DebugAdapter { - - private close = new vscode.EventEmitter<void>(); - onClose: vscode.Event<void> = this.close.event; - - private error = new vscode.EventEmitter<Error>(); - onError: vscode.Event<Error> = this.error.event; - - private sendMessage = new vscode.EventEmitter<DebugProtocol.ProtocolMessage>(); - readonly onDidSendMessage: vscode.Event<DebugProtocol.ProtocolMessage> = this.sendMessage.event; - - private _sequence: number = 1; - private _pendingRequests = new Map<number, (response: DebugProtocol.Response) => void>(); - - - public handleMessage(message: DebugProtocol.ProtocolMessage): void { - this.dispatch(message); - } - - public dispose() { - } - - public sendEvent(event: DebugProtocol.Event): void { - this._send('event', event); - } - - public sendResponse(response: DebugProtocol.Response): void { - if (response.seq > 0) { - console.error(`attempt to send more than one response for command ${response.command}`); - } else { - this._send('response', response); - } - } - - public sendRequest(command: string, args: any, timeout: number, cb: (response: DebugProtocol.Response) => void): void { - - const request: any = { - command: command - }; - if (args && Object.keys(args).length > 0) { - request.arguments = args; - } - - this._send('request', request); - - if (cb) { - this._pendingRequests.set(request.seq, cb); - - const timer = setTimeout(() => { - clearTimeout(timer); - const clb = this._pendingRequests.get(request.seq); - if (clb) { - this._pendingRequests.delete(request.seq); - clb(new Response(request, 'timeout')); - } - }, timeout); - } - } - - // ---- protected ---------------------------------------------------------- - - protected dispatchRequest(_request: DebugProtocol.Request): void { - } - - // ---- private ------------------------------------------------------------ - - private dispatch(msg: DebugProtocol.ProtocolMessage) { - if (msg.type === 'request') { - this.dispatchRequest(<DebugProtocol.Request>msg); - } else if (msg.type === 'response') { - const response = <DebugProtocol.Response>msg; - const clb = this._pendingRequests.get(response.request_seq); - if (clb) { - this._pendingRequests.delete(response.request_seq); - clb(response); - } - } - } - - private _send(typ: 'request' | 'response' | 'event', message: DebugProtocol.ProtocolMessage): void { - - message.type = typ; - message.seq = this._sequence++; - - this.sendMessage.fire(message); - } -} - -//------------------------------------------------------------------------------------------------------------------------------- - -export class Source implements DebugProtocol.Source { - name: string; - path?: string; - sourceReference: number; - - public constructor(name: string, path?: string, id: number = 0, origin?: string, data?: any) { - this.name = name; - this.path = path; - this.sourceReference = id; - if (origin) { - (<any>this).origin = origin; - } - if (data) { - (<any>this).adapterData = data; - } - } -} - -export class Scope implements DebugProtocol.Scope { - name: string; - variablesReference: number; - expensive: boolean; - - public constructor(name: string, reference: number, expensive: boolean = false) { - this.name = name; - this.variablesReference = reference; - this.expensive = expensive; - } -} - -export class StackFrame implements DebugProtocol.StackFrame { - id: number; - source?: Source; - line: number; - column: number; - name: string; - - public constructor(i: number, nm: string, src?: Source, ln: number = 0, col: number = 0) { - this.id = i; - this.source = src; - this.line = ln; - this.column = col; - this.name = nm; - } -} - -export class Thread implements DebugProtocol.Thread { - id: number; - name: string; - - public constructor(id: number, name: string) { - this.id = id; - if (name) { - this.name = name; - } else { - this.name = 'Thread #' + id; - } - } -} - -export class Variable implements DebugProtocol.Variable { - name: string; - value: string; - variablesReference: number; - - public constructor(name: string, value: string, ref: number = 0, indexedVariables?: number, namedVariables?: number) { - this.name = name; - this.value = value; - this.variablesReference = ref; - if (typeof namedVariables === 'number') { - (<DebugProtocol.Variable>this).namedVariables = namedVariables; - } - if (typeof indexedVariables === 'number') { - (<DebugProtocol.Variable>this).indexedVariables = indexedVariables; - } - } -} - -export class Breakpoint implements DebugProtocol.Breakpoint { - verified: boolean; - - public constructor(verified: boolean, line?: number, column?: number, source?: Source) { - this.verified = verified; - const e: DebugProtocol.Breakpoint = this; - if (typeof line === 'number') { - e.line = line; - } - if (typeof column === 'number') { - e.column = column; - } - if (source) { - e.source = source; - } - } -} - -export class Module implements DebugProtocol.Module { - id: number | string; - name: string; - - public constructor(id: number | string, name: string) { - this.id = id; - this.name = name; - } -} - -export class CompletionItem implements DebugProtocol.CompletionItem { - label: string; - start: number; - length: number; - - public constructor(label: string, start: number, length: number = 0) { - this.label = label; - this.start = start; - this.length = length; - } -} - -export class StoppedEvent extends Event implements DebugProtocol.StoppedEvent { - body: { - reason: string; - }; - - public constructor(reason: string, threadId?: number, exceptionText?: string) { - super('stopped'); - this.body = { - reason: reason - }; - if (typeof threadId === 'number') { - (this as DebugProtocol.StoppedEvent).body.threadId = threadId; - } - if (typeof exceptionText === 'string') { - (this as DebugProtocol.StoppedEvent).body.text = exceptionText; - } - } -} - -export class ContinuedEvent extends Event implements DebugProtocol.ContinuedEvent { - body: { - threadId: number; - }; - - public constructor(threadId: number, allThreadsContinued?: boolean) { - super('continued'); - this.body = { - threadId: threadId - }; - - if (typeof allThreadsContinued === 'boolean') { - (<DebugProtocol.ContinuedEvent>this).body.allThreadsContinued = allThreadsContinued; - } - } -} - -export class InitializedEvent extends Event implements DebugProtocol.InitializedEvent { - public constructor() { - super('initialized'); - } -} - -export class TerminatedEvent extends Event implements DebugProtocol.TerminatedEvent { - public constructor(restart?: any) { - super('terminated'); - if (typeof restart === 'boolean' || restart) { - const e: DebugProtocol.TerminatedEvent = this; - e.body = { - restart: restart - }; - } - } -} - -export class OutputEvent extends Event implements DebugProtocol.OutputEvent { - body: { - category: string, - output: string, - data?: any - }; - - public constructor(output: string, category: string = 'console', data?: any) { - super('output'); - this.body = { - category: category, - output: output - }; - if (data !== undefined) { - this.body.data = data; - } - } -} - -export class ThreadEvent extends Event implements DebugProtocol.ThreadEvent { - body: { - reason: string, - threadId: number - }; - - public constructor(reason: string, threadId: number) { - super('thread'); - this.body = { - reason: reason, - threadId: threadId - }; - } -} - -export class BreakpointEvent extends Event implements DebugProtocol.BreakpointEvent { - body: { - reason: string, - breakpoint: Breakpoint - }; - - public constructor(reason: string, breakpoint: Breakpoint) { - super('breakpoint'); - this.body = { - reason: reason, - breakpoint: breakpoint - }; - } -} - -export class ModuleEvent extends Event implements DebugProtocol.ModuleEvent { - body: { - reason: 'new' | 'changed' | 'removed', - module: Module - }; - - public constructor(reason: 'new' | 'changed' | 'removed', module: Module) { - super('module'); - this.body = { - reason: reason, - module: module - }; - } -} - -export class LoadedSourceEvent extends Event implements DebugProtocol.LoadedSourceEvent { - body: { - reason: 'new' | 'changed' | 'removed', - source: Source - }; - - public constructor(reason: 'new' | 'changed' | 'removed', source: Source) { - super('loadedSource'); - this.body = { - reason: reason, - source: source - }; - } -} - -export class CapabilitiesEvent extends Event implements DebugProtocol.CapabilitiesEvent { - body: { - capabilities: DebugProtocol.Capabilities - }; - - public constructor(capabilities: DebugProtocol.Capabilities) { - super('capabilities'); - this.body = { - capabilities: capabilities - }; - } -} - -export enum ErrorDestination { - User = 1, - Telemetry = 2 -} - -export class DebugSession extends ProtocolServer { - - private _debuggerLinesStartAt1: boolean; - private _debuggerColumnsStartAt1: boolean; - private _debuggerPathsAreURIs: boolean; - - private _clientLinesStartAt1: boolean; - private _clientColumnsStartAt1: boolean; - private _clientPathsAreURIs: boolean; - - protected _isServer: boolean; - - public constructor(obsolete_debuggerLinesAndColumnsStartAt1?: boolean, obsolete_isServer?: boolean) { - super(); - - const linesAndColumnsStartAt1 = typeof obsolete_debuggerLinesAndColumnsStartAt1 === 'boolean' ? obsolete_debuggerLinesAndColumnsStartAt1 : false; - this._debuggerLinesStartAt1 = linesAndColumnsStartAt1; - this._debuggerColumnsStartAt1 = linesAndColumnsStartAt1; - this._debuggerPathsAreURIs = false; - - this._clientLinesStartAt1 = true; - this._clientColumnsStartAt1 = true; - this._clientPathsAreURIs = false; - - this._isServer = typeof obsolete_isServer === 'boolean' ? obsolete_isServer : false; - - this.onClose(() => { - this.shutdown(); - }); - this.onError((_error) => { - this.shutdown(); - }); - } - - public setDebuggerPathFormat(format: string) { - this._debuggerPathsAreURIs = format !== 'path'; - } - - public setDebuggerLinesStartAt1(enable: boolean) { - this._debuggerLinesStartAt1 = enable; - } - - public setDebuggerColumnsStartAt1(enable: boolean) { - this._debuggerColumnsStartAt1 = enable; - } - - public setRunAsServer(enable: boolean) { - this._isServer = enable; - } - - public shutdown(): void { - if (this._isServer) { - // shutdown ignored in server mode - } else { - // TODO@AW - /* - // wait a bit before shutting down - setTimeout(() => { - process.exit(0); - }, 100); - */ - } - } - - protected sendErrorResponse(response: DebugProtocol.Response, codeOrMessage: number | DebugProtocol.Message, format?: string, variables?: any, dest: ErrorDestination = ErrorDestination.User): void { - - let msg: DebugProtocol.Message; - if (typeof codeOrMessage === 'number') { - msg = <DebugProtocol.Message>{ - id: <number>codeOrMessage, - format: format - }; - if (variables) { - msg.variables = variables; - } - if (dest & ErrorDestination.User) { - msg.showUser = true; - } - if (dest & ErrorDestination.Telemetry) { - msg.sendTelemetry = true; - } - } else { - msg = codeOrMessage; - } - - response.success = false; - response.message = DebugSession.formatPII(msg.format, true, msg.variables); - if (!response.body) { - response.body = {}; - } - response.body.error = msg; - - this.sendResponse(response); - } - - public runInTerminalRequest(args: DebugProtocol.RunInTerminalRequestArguments, timeout: number, cb: (response: DebugProtocol.Response) => void) { - this.sendRequest('runInTerminal', args, timeout, cb); - } - - protected dispatchRequest(request: DebugProtocol.Request): void { - - const response = new Response(request); - - try { - if (request.command === 'initialize') { - const args = <DebugProtocol.InitializeRequestArguments>request.arguments; - - if (typeof args.linesStartAt1 === 'boolean') { - this._clientLinesStartAt1 = args.linesStartAt1; - } - if (typeof args.columnsStartAt1 === 'boolean') { - this._clientColumnsStartAt1 = args.columnsStartAt1; - } - - if (args.pathFormat !== 'path') { - this.sendErrorResponse(response, 2018, 'debug adapter only supports native paths', null, ErrorDestination.Telemetry); - } else { - const initializeResponse = <DebugProtocol.InitializeResponse>response; - initializeResponse.body = {}; - this.initializeRequest(initializeResponse, args); - } - - } else if (request.command === 'launch') { - this.launchRequest(<DebugProtocol.LaunchResponse>response, request.arguments, request); - - } else if (request.command === 'attach') { - this.attachRequest(<DebugProtocol.AttachResponse>response, request.arguments, request); - - } else if (request.command === 'disconnect') { - this.disconnectRequest(<DebugProtocol.DisconnectResponse>response, request.arguments, request); - - } else if (request.command === 'terminate') { - this.terminateRequest(<DebugProtocol.TerminateResponse>response, request.arguments, request); - - } else if (request.command === 'restart') { - this.restartRequest(<DebugProtocol.RestartResponse>response, request.arguments, request); - - } else if (request.command === 'setBreakpoints') { - this.setBreakPointsRequest(<DebugProtocol.SetBreakpointsResponse>response, request.arguments, request); - - } else if (request.command === 'setFunctionBreakpoints') { - this.setFunctionBreakPointsRequest(<DebugProtocol.SetFunctionBreakpointsResponse>response, request.arguments, request); - - } else if (request.command === 'setExceptionBreakpoints') { - this.setExceptionBreakPointsRequest(<DebugProtocol.SetExceptionBreakpointsResponse>response, request.arguments, request); - - } else if (request.command === 'configurationDone') { - this.configurationDoneRequest(<DebugProtocol.ConfigurationDoneResponse>response, request.arguments, request); - - } else if (request.command === 'continue') { - this.continueRequest(<DebugProtocol.ContinueResponse>response, request.arguments, request); - - } else if (request.command === 'next') { - this.nextRequest(<DebugProtocol.NextResponse>response, request.arguments, request); - - } else if (request.command === 'stepIn') { - this.stepInRequest(<DebugProtocol.StepInResponse>response, request.arguments, request); - - } else if (request.command === 'stepOut') { - this.stepOutRequest(<DebugProtocol.StepOutResponse>response, request.arguments, request); - - } else if (request.command === 'stepBack') { - this.stepBackRequest(<DebugProtocol.StepBackResponse>response, request.arguments, request); - - } else if (request.command === 'reverseContinue') { - this.reverseContinueRequest(<DebugProtocol.ReverseContinueResponse>response, request.arguments, request); - - } else if (request.command === 'restartFrame') { - this.restartFrameRequest(<DebugProtocol.RestartFrameResponse>response, request.arguments, request); - - } else if (request.command === 'goto') { - this.gotoRequest(<DebugProtocol.GotoResponse>response, request.arguments, request); - - } else if (request.command === 'pause') { - this.pauseRequest(<DebugProtocol.PauseResponse>response, request.arguments, request); - - } else if (request.command === 'stackTrace') { - this.stackTraceRequest(<DebugProtocol.StackTraceResponse>response, request.arguments, request); - - } else if (request.command === 'scopes') { - this.scopesRequest(<DebugProtocol.ScopesResponse>response, request.arguments, request); - - } else if (request.command === 'variables') { - this.variablesRequest(<DebugProtocol.VariablesResponse>response, request.arguments, request); - - } else if (request.command === 'setVariable') { - this.setVariableRequest(<DebugProtocol.SetVariableResponse>response, request.arguments, request); - - } else if (request.command === 'setExpression') { - this.setExpressionRequest(<DebugProtocol.SetExpressionResponse>response, request.arguments, request); - - } else if (request.command === 'source') { - this.sourceRequest(<DebugProtocol.SourceResponse>response, request.arguments, request); - - } else if (request.command === 'threads') { - this.threadsRequest(<DebugProtocol.ThreadsResponse>response, request); - - } else if (request.command === 'terminateThreads') { - this.terminateThreadsRequest(<DebugProtocol.TerminateThreadsResponse>response, request.arguments, request); - - } else if (request.command === 'evaluate') { - this.evaluateRequest(<DebugProtocol.EvaluateResponse>response, request.arguments, request); - - } else if (request.command === 'stepInTargets') { - this.stepInTargetsRequest(<DebugProtocol.StepInTargetsResponse>response, request.arguments, request); - - } else if (request.command === 'gotoTargets') { - this.gotoTargetsRequest(<DebugProtocol.GotoTargetsResponse>response, request.arguments, request); - - } else if (request.command === 'completions') { - this.completionsRequest(<DebugProtocol.CompletionsResponse>response, request.arguments, request); - - } else if (request.command === 'exceptionInfo') { - this.exceptionInfoRequest(<DebugProtocol.ExceptionInfoResponse>response, request.arguments, request); - - } else if (request.command === 'loadedSources') { - this.loadedSourcesRequest(<DebugProtocol.LoadedSourcesResponse>response, request.arguments, request); - - } else if (request.command === 'dataBreakpointInfo') { - this.dataBreakpointInfoRequest(<DebugProtocol.DataBreakpointInfoResponse>response, request.arguments, request); - - } else if (request.command === 'setDataBreakpoints') { - this.setDataBreakpointsRequest(<DebugProtocol.SetDataBreakpointsResponse>response, request.arguments, request); - - } else if (request.command === 'readMemory') { - this.readMemoryRequest(<DebugProtocol.ReadMemoryResponse>response, request.arguments, request); - - } else if (request.command === 'disassemble') { - this.disassembleRequest(<DebugProtocol.DisassembleResponse>response, request.arguments, request); - - } else if (request.command === 'cancel') { - this.cancelRequest(<DebugProtocol.CancelResponse>response, request.arguments, request); - - } else if (request.command === 'breakpointLocations') { - this.breakpointLocationsRequest(<DebugProtocol.BreakpointLocationsResponse>response, request.arguments, request); - - } else { - this.customRequest(request.command, <DebugProtocol.Response>response, request.arguments, request); - } - } catch (e) { - this.sendErrorResponse(response, 1104, '{_stack}', { _exception: e.message, _stack: e.stack }, ErrorDestination.Telemetry); - } - } - - protected initializeRequest(response: DebugProtocol.InitializeResponse, _args: DebugProtocol.InitializeRequestArguments): void { - - response.body = response.body || {}; - - // This default debug adapter does not support conditional breakpoints. - response.body.supportsConditionalBreakpoints = false; - - // This default debug adapter does not support hit conditional breakpoints. - response.body.supportsHitConditionalBreakpoints = false; - - // This default debug adapter does not support function breakpoints. - response.body.supportsFunctionBreakpoints = false; - - // This default debug adapter implements the 'configurationDone' request. - response.body.supportsConfigurationDoneRequest = true; - - // This default debug adapter does not support hovers based on the 'evaluate' request. - response.body.supportsEvaluateForHovers = false; - - // This default debug adapter does not support the 'stepBack' request. - response.body.supportsStepBack = false; - - // This default debug adapter does not support the 'setVariable' request. - response.body.supportsSetVariable = false; - - // This default debug adapter does not support the 'restartFrame' request. - response.body.supportsRestartFrame = false; - - // This default debug adapter does not support the 'stepInTargets' request. - response.body.supportsStepInTargetsRequest = false; - - // This default debug adapter does not support the 'gotoTargets' request. - response.body.supportsGotoTargetsRequest = false; - - // This default debug adapter does not support the 'completions' request. - response.body.supportsCompletionsRequest = false; - - // This default debug adapter does not support the 'restart' request. - response.body.supportsRestartRequest = false; - - // This default debug adapter does not support the 'exceptionOptions' attribute on the 'setExceptionBreakpoints' request. - response.body.supportsExceptionOptions = false; - - // This default debug adapter does not support the 'format' attribute on the 'variables', 'evaluate', and 'stackTrace' request. - response.body.supportsValueFormattingOptions = false; - - // This debug adapter does not support the 'exceptionInfo' request. - response.body.supportsExceptionInfoRequest = false; - - // This debug adapter does not support the 'TerminateDebuggee' attribute on the 'disconnect' request. - response.body.supportTerminateDebuggee = false; - - // This debug adapter does not support delayed loading of stack frames. - response.body.supportsDelayedStackTraceLoading = false; - - // This debug adapter does not support the 'loadedSources' request. - response.body.supportsLoadedSourcesRequest = false; - - // This debug adapter does not support the 'logMessage' attribute of the SourceBreakpoint. - response.body.supportsLogPoints = false; - - // This debug adapter does not support the 'terminateThreads' request. - response.body.supportsTerminateThreadsRequest = false; - - // This debug adapter does not support the 'setExpression' request. - response.body.supportsSetExpression = false; - - // This debug adapter does not support the 'terminate' request. - response.body.supportsTerminateRequest = false; - - // This debug adapter does not support data breakpoints. - response.body.supportsDataBreakpoints = false; - - /** This debug adapter does not support the 'readMemory' request. */ - response.body.supportsReadMemoryRequest = false; - - /** The debug adapter does not support the 'disassemble' request. */ - response.body.supportsDisassembleRequest = false; - - /** The debug adapter does not support the 'cancel' request. */ - response.body.supportsCancelRequest = false; - - /** The debug adapter does not support the 'breakpointLocations' request. */ - response.body.supportsBreakpointLocationsRequest = false; - - this.sendResponse(response); - } - - protected disconnectRequest(response: DebugProtocol.DisconnectResponse, _args: DebugProtocol.DisconnectArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - this.shutdown(); - } - - protected launchRequest(response: DebugProtocol.LaunchResponse, _args: DebugProtocol.LaunchRequestArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected attachRequest(response: DebugProtocol.AttachResponse, _args: DebugProtocol.AttachRequestArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected terminateRequest(response: DebugProtocol.TerminateResponse, _args: DebugProtocol.TerminateArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected restartRequest(response: DebugProtocol.RestartResponse, _args: DebugProtocol.RestartArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, _args: DebugProtocol.SetBreakpointsArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected setFunctionBreakPointsRequest(response: DebugProtocol.SetFunctionBreakpointsResponse, _args: DebugProtocol.SetFunctionBreakpointsArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected setExceptionBreakPointsRequest(response: DebugProtocol.SetExceptionBreakpointsResponse, _args: DebugProtocol.SetExceptionBreakpointsArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse, _args: DebugProtocol.ConfigurationDoneArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected continueRequest(response: DebugProtocol.ContinueResponse, _args: DebugProtocol.ContinueArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected nextRequest(response: DebugProtocol.NextResponse, _args: DebugProtocol.NextArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected stepInRequest(response: DebugProtocol.StepInResponse, _args: DebugProtocol.StepInArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected stepOutRequest(response: DebugProtocol.StepOutResponse, _args: DebugProtocol.StepOutArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected stepBackRequest(response: DebugProtocol.StepBackResponse, _args: DebugProtocol.StepBackArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, _args: DebugProtocol.ReverseContinueArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected restartFrameRequest(response: DebugProtocol.RestartFrameResponse, _args: DebugProtocol.RestartFrameArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected gotoRequest(response: DebugProtocol.GotoResponse, _args: DebugProtocol.GotoArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected pauseRequest(response: DebugProtocol.PauseResponse, _args: DebugProtocol.PauseArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected sourceRequest(response: DebugProtocol.SourceResponse, _args: DebugProtocol.SourceArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected threadsRequest(response: DebugProtocol.ThreadsResponse, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected terminateThreadsRequest(response: DebugProtocol.TerminateThreadsResponse, _args: DebugProtocol.TerminateThreadsArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, _args: DebugProtocol.StackTraceArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected scopesRequest(response: DebugProtocol.ScopesResponse, _args: DebugProtocol.ScopesArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected variablesRequest(response: DebugProtocol.VariablesResponse, _args: DebugProtocol.VariablesArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected setVariableRequest(response: DebugProtocol.SetVariableResponse, _args: DebugProtocol.SetVariableArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected setExpressionRequest(response: DebugProtocol.SetExpressionResponse, _args: DebugProtocol.SetExpressionArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected evaluateRequest(response: DebugProtocol.EvaluateResponse, _args: DebugProtocol.EvaluateArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected stepInTargetsRequest(response: DebugProtocol.StepInTargetsResponse, _args: DebugProtocol.StepInTargetsArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected gotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, _args: DebugProtocol.GotoTargetsArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected completionsRequest(response: DebugProtocol.CompletionsResponse, _args: DebugProtocol.CompletionsArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected exceptionInfoRequest(response: DebugProtocol.ExceptionInfoResponse, _args: DebugProtocol.ExceptionInfoArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected loadedSourcesRequest(response: DebugProtocol.LoadedSourcesResponse, _args: DebugProtocol.LoadedSourcesArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected dataBreakpointInfoRequest(response: DebugProtocol.DataBreakpointInfoResponse, _args: DebugProtocol.DataBreakpointInfoArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected setDataBreakpointsRequest(response: DebugProtocol.SetDataBreakpointsResponse, _args: DebugProtocol.SetDataBreakpointsArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected readMemoryRequest(response: DebugProtocol.ReadMemoryResponse, _args: DebugProtocol.ReadMemoryArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected disassembleRequest(response: DebugProtocol.DisassembleResponse, _args: DebugProtocol.DisassembleArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected cancelRequest(response: DebugProtocol.CancelResponse, _args: DebugProtocol.CancelArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - protected breakpointLocationsRequest(response: DebugProtocol.BreakpointLocationsResponse, _args: DebugProtocol.BreakpointLocationsArguments, _request?: DebugProtocol.Request): void { - this.sendResponse(response); - } - - /** - * Override this hook to implement custom requests. - */ - protected customRequest(_command: string, response: DebugProtocol.Response, _args: any, _request?: DebugProtocol.Request): void { - this.sendErrorResponse(response, 1014, 'unrecognized request', null, ErrorDestination.Telemetry); - } - - //---- protected ------------------------------------------------------------------------------------------------- - - protected convertClientLineToDebugger(line: number): number { - if (this._debuggerLinesStartAt1) { - return this._clientLinesStartAt1 ? line : line + 1; - } - return this._clientLinesStartAt1 ? line - 1 : line; - } - - protected convertDebuggerLineToClient(line: number): number { - if (this._debuggerLinesStartAt1) { - return this._clientLinesStartAt1 ? line : line - 1; - } - return this._clientLinesStartAt1 ? line + 1 : line; - } - - protected convertClientColumnToDebugger(column: number): number { - if (this._debuggerColumnsStartAt1) { - return this._clientColumnsStartAt1 ? column : column + 1; - } - return this._clientColumnsStartAt1 ? column - 1 : column; - } - - protected convertDebuggerColumnToClient(column: number): number { - if (this._debuggerColumnsStartAt1) { - return this._clientColumnsStartAt1 ? column : column - 1; - } - return this._clientColumnsStartAt1 ? column + 1 : column; - } - - protected convertClientPathToDebugger(clientPath: string): string { - if (this._clientPathsAreURIs !== this._debuggerPathsAreURIs) { - if (this._clientPathsAreURIs) { - return DebugSession.uri2path(clientPath); - } else { - return DebugSession.path2uri(clientPath); - } - } - return clientPath; - } - - protected convertDebuggerPathToClient(debuggerPath: string): string { - if (this._debuggerPathsAreURIs !== this._clientPathsAreURIs) { - if (this._debuggerPathsAreURIs) { - return DebugSession.uri2path(debuggerPath); - } else { - return DebugSession.path2uri(debuggerPath); - } - } - return debuggerPath; - } - - //---- private ------------------------------------------------------------------------------- - - private static path2uri(path: string): string { - - path = encodeURI(path); - - let uri = new URL(`file:`); // ignore 'path' for now - uri.pathname = path; // now use 'path' to get the correct percent encoding (see https://url.spec.whatwg.org) - return uri.toString(); - } - - private static uri2path(sourceUri: string): string { - - let uri = new URL(sourceUri); - let s = decodeURIComponent(uri.pathname); - return s; - } - - private static _formatPIIRegexp = /{([^}]+)}/g; - - /* - * If argument starts with '_' it is OK to send its value to telemetry. - */ - private static formatPII(format: string, excludePII: boolean, args?: { [key: string]: string }): string { - return format.replace(DebugSession._formatPIIRegexp, function (match, paramName) { - if (excludePII && paramName.length > 0 && paramName[0] !== '_') { - return match; - } - return args && args[paramName] && args.hasOwnProperty(paramName) ? - args[paramName] : - match; - }); - } -} - -//--------------------------------------------------------------------------- - -export class Handles<T> { - - private START_HANDLE = 1000; - - private _nextHandle: number; - private _handleMap = new Map<number, T>(); - - public constructor(startHandle?: number) { - this._nextHandle = typeof startHandle === 'number' ? startHandle : this.START_HANDLE; - } - - public reset(): void { - this._nextHandle = this.START_HANDLE; - this._handleMap = new Map<number, T>(); - } - - public create(value: T): number { - const handle = this._nextHandle++; - this._handleMap.set(handle, value); - return handle; - } - - public get(handle: number, dflt?: T): T | undefined { - return this._handleMap.get(handle) || dflt; - } -} - -//--------------------------------------------------------------------------- - -class MockConfigurationProvider implements vscode.DebugConfigurationProvider { - - /** - * Massage a debug configuration just before a debug session is being launched, - * e.g. add all missing attributes to the debug configuration. - */ - resolveDebugConfiguration(_folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration, _token?: vscode.CancellationToken): vscode.ProviderResult<vscode.DebugConfiguration> { - - // if launch.json is missing or empty - if (!config.type && !config.request && !config.name) { - const editor = vscode.window.activeTextEditor; - if (editor && editor.document.languageId === 'markdown') { - config.type = 'mock'; - config.name = 'Launch'; - config.request = 'launch'; - config.program = '${file}'; - config.stopOnEntry = true; - } - } - - if (!config.program) { - return vscode.window.showInformationMessage('Cannot find a program to debug').then(_ => { - return undefined; // abort launch - }); - } - - return config; - } -} - -export class MockDebugAdapterDescriptorFactory implements vscode.DebugAdapterDescriptorFactory { - - constructor(private memfs: MemFS) { - } - - createDebugAdapterDescriptor(_session: vscode.DebugSession, _executable: vscode.DebugAdapterExecutable | undefined): vscode.ProviderResult<vscode.DebugAdapterDescriptor> { - return <any>new vscode.DebugAdapterInlineImplementation(new MockDebugSession(this.memfs)); - } -} - -function basename(path: string): string { - const pos = path.lastIndexOf('/'); - if (pos >= 0) { - return path.substring(pos + 1); - } - return path; -} - -function timeout(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -/** - * This interface describes the mock-debug specific launch attributes - * (which are not part of the Debug Adapter Protocol). - * The schema for these attributes lives in the package.json of the mock-debug extension. - * The interface should always match this schema. - */ -interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { - /** An absolute path to the "program" to debug. */ - program: string; - /** Automatically stop target after launch. If not specified, target does not stop. */ - stopOnEntry?: boolean; - /** enable logging the Debug Adapter Protocol */ - trace?: boolean; -} - -export class MockDebugSession extends DebugSession { - - // we don't support multiple threads, so we can use a hardcoded ID for the default thread - private static THREAD_ID = 1; - - // a Mock runtime (or debugger) - private _runtime: MockRuntime; - - private _variableHandles = new Handles<string>(); - - //private _configurationDone = new Subject(); - - private promiseResolve?: () => void; - private _configurationDone = new Promise<void>((r, _e) => { - this.promiseResolve = r; - setTimeout(r, 1000); - }); - - private _cancelationTokens = new Map<number, boolean>(); - private _isLongrunning = new Map<number, boolean>(); - - /** - * Creates a new debug adapter that is used for one debug session. - * We configure the default implementation of a debug adapter here. - */ - public constructor(memfs: MemFS) { - - super(); - - // this debugger uses zero-based lines and columns - this.setDebuggerLinesStartAt1(false); - this.setDebuggerColumnsStartAt1(false); - - this._runtime = new MockRuntime(memfs); - - // setup event handlers - this._runtime.onStopOnEntry(() => { - this.sendEvent(new StoppedEvent('entry', MockDebugSession.THREAD_ID)); - }); - this._runtime.onStopOnStep(() => { - this.sendEvent(new StoppedEvent('step', MockDebugSession.THREAD_ID)); - }); - this._runtime.onStopOnBreakpoint(() => { - this.sendEvent(new StoppedEvent('breakpoint', MockDebugSession.THREAD_ID)); - }); - this._runtime.onStopOnDataBreakpoint(() => { - this.sendEvent(new StoppedEvent('data breakpoint', MockDebugSession.THREAD_ID)); - }); - this._runtime.onStopOnException(() => { - this.sendEvent(new StoppedEvent('exception', MockDebugSession.THREAD_ID)); - }); - this._runtime.onBreakpointValidated((bp: MockBreakpoint) => { - this.sendEvent(new BreakpointEvent('changed', <DebugProtocol.Breakpoint>{ verified: bp.verified, id: bp.id })); - }); - this._runtime.onOutput(oe => { - const e: DebugProtocol.OutputEvent = new OutputEvent(`${oe.text}\n`); - e.body.source = this.createSource(oe.filePath); - e.body.line = this.convertDebuggerLineToClient(oe.line); - e.body.column = this.convertDebuggerColumnToClient(oe.column); - this.sendEvent(e); - }); - this._runtime.onEnd(() => { - this.sendEvent(new TerminatedEvent()); - }); - } - - /** - * The 'initialize' request is the first request called by the frontend - * to interrogate the features the debug adapter provides. - */ - protected initializeRequest(response: DebugProtocol.InitializeResponse, _args: DebugProtocol.InitializeRequestArguments): void { - - // build and return the capabilities of this debug adapter: - response.body = response.body || {}; - - // the adapter implements the configurationDoneRequest. - response.body.supportsConfigurationDoneRequest = true; - - // make VS Code to use 'evaluate' when hovering over source - response.body.supportsEvaluateForHovers = true; - - // make VS Code to show a 'step back' button - response.body.supportsStepBack = true; - - // make VS Code to support data breakpoints - response.body.supportsDataBreakpoints = true; - - // make VS Code to support completion in REPL - response.body.supportsCompletionsRequest = true; - response.body.completionTriggerCharacters = ['.', '[']; - - // make VS Code to send cancelRequests - response.body.supportsCancelRequest = true; - - // make VS Code send the breakpointLocations request - response.body.supportsBreakpointLocationsRequest = true; - - this.sendResponse(response); - - // since this debug adapter can accept configuration requests like 'setBreakpoint' at any time, - // we request them early by sending an 'initializeRequest' to the frontend. - // The frontend will end the configuration sequence by calling 'configurationDone' request. - this.sendEvent(new InitializedEvent()); - } - - /** - * Called at the end of the configuration sequence. - * Indicates that all breakpoints etc. have been sent to the DA and that the 'launch' can start. - */ - protected configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse, args: DebugProtocol.ConfigurationDoneArguments): void { - super.configurationDoneRequest(response, args); - - // notify the launchRequest that configuration has finished - //this._configurationDone.notify(); - if (this.promiseResolve) { - this.promiseResolve(); - } - } - - protected async launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments) { - - // make sure to 'Stop' the buffered logging if 'trace' is not set - //logger.setup(args.trace ? Logger.LogLevel.Verbose : Logger.LogLevel.Stop, false); - - // wait until configuration has finished (and configurationDoneRequest has been called) - await this._configurationDone; - - // start the program in the runtime - this._runtime.start(`memfs:${args.program}`, !!args.stopOnEntry); - - this.sendResponse(response); - } - - protected setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void { - - const path = <string>args.source.path; - const clientLines = args.lines || []; - - // clear all breakpoints for this file - this._runtime.clearBreakpoints(path); - - // set and verify breakpoint locations - const actualBreakpoints = clientLines.map(l => { - let { verified, line, id } = this._runtime.setBreakPoint(path, this.convertClientLineToDebugger(l)); - const bp = <DebugProtocol.Breakpoint>new Breakpoint(verified, this.convertDebuggerLineToClient(line)); - bp.id = id; - return bp; - }); - - // send back the actual breakpoint positions - response.body = { - breakpoints: actualBreakpoints - }; - this.sendResponse(response); - } - - protected breakpointLocationsRequest(response: DebugProtocol.BreakpointLocationsResponse, args: DebugProtocol.BreakpointLocationsArguments, _request?: DebugProtocol.Request): void { - - if (args.source.path) { - const bps = this._runtime.getBreakpoints(args.source.path, this.convertClientLineToDebugger(args.line)); - response.body = { - breakpoints: bps.map(col => { - return { - line: args.line, - column: this.convertDebuggerColumnToClient(col) - }; - }) - }; - } else { - response.body = { - breakpoints: [] - }; - } - this.sendResponse(response); - } - - protected threadsRequest(response: DebugProtocol.ThreadsResponse): void { - - // runtime supports no threads so just return a default thread. - response.body = { - threads: [ - new Thread(MockDebugSession.THREAD_ID, 'thread 1') - ] - }; - this.sendResponse(response); - } - - protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { - - const startFrame = typeof args.startFrame === 'number' ? args.startFrame : 0; - const maxLevels = typeof args.levels === 'number' ? args.levels : 1000; - const endFrame = startFrame + maxLevels; - - const stk = this._runtime.stack(startFrame, endFrame); - - response.body = { - stackFrames: stk.frames.map(f => new StackFrame(f.index, f.name, this.createSource(f.file), this.convertDebuggerLineToClient(f.line))), - totalFrames: stk.count - }; - this.sendResponse(response); - } - - protected scopesRequest(response: DebugProtocol.ScopesResponse, _args: DebugProtocol.ScopesArguments): void { - - response.body = { - scopes: [ - new Scope('Local', this._variableHandles.create('local'), false), - new Scope('Global', this._variableHandles.create('global'), true) - ] - }; - this.sendResponse(response); - } - - protected async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments, request?: DebugProtocol.Request) { - - const variables: DebugProtocol.Variable[] = []; - - if (this._isLongrunning.get(args.variablesReference)) { - // long running - - if (request) { - this._cancelationTokens.set(request.seq, false); - } - - for (let i = 0; i < 100; i++) { - await timeout(1000); - variables.push({ - name: `i_${i}`, - type: 'integer', - value: `${i}`, - variablesReference: 0 - }); - if (request && this._cancelationTokens.get(request.seq)) { - break; - } - } - - if (request) { - this._cancelationTokens.delete(request.seq); - } - - } else { - - const id = this._variableHandles.get(args.variablesReference); - - if (id) { - variables.push({ - name: id + '_i', - type: 'integer', - value: '123', - variablesReference: 0 - }); - variables.push({ - name: id + '_f', - type: 'float', - value: '3.14', - variablesReference: 0 - }); - variables.push({ - name: id + '_s', - type: 'string', - value: 'hello world', - variablesReference: 0 - }); - variables.push({ - name: id + '_o', - type: 'object', - value: 'Object', - variablesReference: this._variableHandles.create(id + '_o') - }); - - // cancelation support for long running requests - const nm = id + '_long_running'; - const ref = this._variableHandles.create(id + '_lr'); - variables.push({ - name: nm, - type: 'object', - value: 'Object', - variablesReference: ref - }); - this._isLongrunning.set(ref, true); - } - } - - response.body = { - variables: variables - }; - this.sendResponse(response); - } - - protected continueRequest(response: DebugProtocol.ContinueResponse, _args: DebugProtocol.ContinueArguments): void { - this._runtime.continue(); - this.sendResponse(response); - } - - protected reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, _args: DebugProtocol.ReverseContinueArguments): void { - this._runtime.continue(true); - this.sendResponse(response); - } - - protected nextRequest(response: DebugProtocol.NextResponse, _args: DebugProtocol.NextArguments): void { - this._runtime.step(); - this.sendResponse(response); - } - - protected stepBackRequest(response: DebugProtocol.StepBackResponse, _args: DebugProtocol.StepBackArguments): void { - this._runtime.step(true); - this.sendResponse(response); - } - - protected evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { - - let reply: string | undefined = undefined; - - if (args.context === 'repl') { - // 'evaluate' supports to create and delete breakpoints from the 'repl': - const matches = /new +([0-9]+)/.exec(args.expression); - if (matches && matches.length === 2) { - if (this._runtime.sourceFile) { - const mbp = this._runtime.setBreakPoint(this._runtime.sourceFile, this.convertClientLineToDebugger(parseInt(matches[1]))); - const bp = <DebugProtocol.Breakpoint>new Breakpoint(mbp.verified, this.convertDebuggerLineToClient(mbp.line), undefined, this.createSource(this._runtime.sourceFile)); - bp.id = mbp.id; - this.sendEvent(new BreakpointEvent('new', bp)); - reply = `breakpoint created`; - } - } else { - const matches = /del +([0-9]+)/.exec(args.expression); - if (matches && matches.length === 2) { - const mbp = this._runtime.sourceFile ? this._runtime.clearBreakPoint(this._runtime.sourceFile, this.convertClientLineToDebugger(parseInt(matches[1]))) : undefined; - if (mbp) { - const bp = <DebugProtocol.Breakpoint>new Breakpoint(false); - bp.id = mbp.id; - this.sendEvent(new BreakpointEvent('removed', bp)); - reply = `breakpoint deleted`; - } - } - } - } - - response.body = { - result: reply ? reply : `evaluate(context: '${args.context}', '${args.expression}')`, - variablesReference: 0 - }; - this.sendResponse(response); - } - - protected dataBreakpointInfoRequest(response: DebugProtocol.DataBreakpointInfoResponse, args: DebugProtocol.DataBreakpointInfoArguments): void { - - response.body = { - dataId: null, - description: 'cannot break on data access', - accessTypes: undefined, - canPersist: false - }; - - if (args.variablesReference && args.name) { - const id = this._variableHandles.get(args.variablesReference); - if (id && id.startsWith('global_')) { - response.body.dataId = args.name; - response.body.description = args.name; - response.body.accessTypes = ['read']; - response.body.canPersist = false; - } - } - - this.sendResponse(response); - } - - protected setDataBreakpointsRequest(response: DebugProtocol.SetDataBreakpointsResponse, args: DebugProtocol.SetDataBreakpointsArguments): void { - - // clear all data breakpoints - this._runtime.clearAllDataBreakpoints(); - - response.body = { - breakpoints: [] - }; - - for (let dbp of args.breakpoints) { - // assume that id is the "address" to break on - const ok = this._runtime.setDataBreakpoint(dbp.dataId); - response.body.breakpoints.push({ - verified: ok - }); - } - - this.sendResponse(response); - } - - protected completionsRequest(response: DebugProtocol.CompletionsResponse, _args: DebugProtocol.CompletionsArguments): void { - - response.body = { - targets: [ - { - label: 'item 10', - sortText: '10' - }, - { - label: 'item 1', - sortText: '01' - }, - { - label: 'item 2', - sortText: '02' - } - ] - }; - this.sendResponse(response); - } - - protected cancelRequest(_response: DebugProtocol.CancelResponse, args: DebugProtocol.CancelArguments) { - if (args.requestId) { - this._cancelationTokens.set(args.requestId, true); - } - } - - //---- helpers - - private createSource(filePath: string): Source { - return new Source(basename(filePath), this.convertDebuggerPathToClient(filePath), undefined, undefined, 'mock-adapter-data'); - } -} - -//------------------------------------------------------------------------------------------------------------------------------------------ - - -/*--------------------------------------------------------- - * Copyright (C) Microsoft Corporation. All rights reserved. - *--------------------------------------------------------*/ - -export interface MockBreakpoint { - id: number; - line: number; - verified: boolean; -} - -export interface MockOutputEvent { - text: string; - filePath: string; - line: number; - column: number; -} - -/** - * A Mock runtime with minimal debugger functionality. - */ -export class MockRuntime { - - private stopOnEntry = new vscode.EventEmitter<void>(); - onStopOnEntry: vscode.Event<void> = this.stopOnEntry.event; - - private stopOnStep = new vscode.EventEmitter<void>(); - onStopOnStep: vscode.Event<void> = this.stopOnStep.event; - - private stopOnBreakpoint = new vscode.EventEmitter<void>(); - onStopOnBreakpoint: vscode.Event<void> = this.stopOnBreakpoint.event; - - private stopOnDataBreakpoint = new vscode.EventEmitter<void>(); - onStopOnDataBreakpoint: vscode.Event<void> = this.stopOnDataBreakpoint.event; - - private stopOnException = new vscode.EventEmitter<void>(); - onStopOnException: vscode.Event<void> = this.stopOnException.event; - - private breakpointValidated = new vscode.EventEmitter<MockBreakpoint>(); - onBreakpointValidated: vscode.Event<MockBreakpoint> = this.breakpointValidated.event; - - private output = new vscode.EventEmitter<MockOutputEvent>(); - onOutput: vscode.Event<MockOutputEvent> = this.output.event; - - private end = new vscode.EventEmitter<void>(); - onEnd: vscode.Event<void> = this.end.event; - - - // the initial (and one and only) file we are 'debugging' - private _sourceFile?: string; - public get sourceFile() { - return this._sourceFile; - } - - // the contents (= lines) of the one and only file - private _sourceLines: string[] = []; - - // This is the next line that will be 'executed' - private _currentLine = 0; - - // maps from sourceFile to array of Mock breakpoints - private _breakPoints = new Map<string, MockBreakpoint[]>(); - - // since we want to send breakpoint events, we will assign an id to every event - // so that the frontend can match events with breakpoints. - private _breakpointId = 1; - - private _breakAddresses = new Set<string>(); - - constructor(private memfs: MemFS) { - } - - /** - * Start executing the given program. - */ - public start(program: string, stopOnEntry: boolean) { - - this.loadSource(program); - this._currentLine = -1; - - if (this._sourceFile) { - this.verifyBreakpoints(this._sourceFile); - } - - if (stopOnEntry) { - // we step once - this.step(false, this.stopOnEntry); - } else { - // we just start to run until we hit a breakpoint or an exception - this.continue(); - } - } - - /** - * Continue execution to the end/beginning. - */ - public continue(reverse = false) { - this.run(reverse, undefined); - } - - /** - * Step to the next/previous non empty line. - */ - public step(reverse = false, event = this.stopOnStep) { - this.run(reverse, event); - } - - /** - * Returns a fake 'stacktrace' where every 'stackframe' is a word from the current line. - */ - public stack(startFrame: number, endFrame: number): { frames: any[], count: number } { - - const words = this._sourceLines[this._currentLine].trim().split(/\s+/); - - const frames = new Array<any>(); - // every word of the current line becomes a stack frame. - for (let i = startFrame; i < Math.min(endFrame, words.length); i++) { - const name = words[i]; // use a word of the line as the stackframe name - frames.push({ - index: i, - name: `${name}(${i})`, - file: this._sourceFile, - line: this._currentLine - }); - } - return { - frames: frames, - count: words.length - }; - } - - public getBreakpoints(_path: string, line: number): number[] { - - const l = this._sourceLines[line]; - - let sawSpace = true; - const bps: number[] = []; - for (let i = 0; i < l.length; i++) { - if (l[i] !== ' ') { - if (sawSpace) { - bps.push(i); - sawSpace = false; - } - } else { - sawSpace = true; - } - } - - return bps; - } - - /* - * Set breakpoint in file with given line. - */ - public setBreakPoint(path: string, line: number): MockBreakpoint { - - const bp = <MockBreakpoint>{ verified: false, line, id: this._breakpointId++ }; - let bps = this._breakPoints.get(path); - if (!bps) { - bps = new Array<MockBreakpoint>(); - this._breakPoints.set(path, bps); - } - bps.push(bp); - - this.verifyBreakpoints(path); - - return bp; - } - - /* - * Clear breakpoint in file with given line. - */ - public clearBreakPoint(path: string, line: number): MockBreakpoint | undefined { - let bps = this._breakPoints.get(path); - if (bps) { - const index = bps.findIndex(bp => bp.line === line); - if (index >= 0) { - const bp = bps[index]; - bps.splice(index, 1); - return bp; - } - } - return undefined; - } - - /* - * Clear all breakpoints for file. - */ - public clearBreakpoints(path: string): void { - this._breakPoints.delete(path); - } - - /* - * Set data breakpoint. - */ - public setDataBreakpoint(address: string): boolean { - if (address) { - this._breakAddresses.add(address); - return true; - } - return false; - } - - /* - * Clear all data breakpoints. - */ - public clearAllDataBreakpoints(): void { - this._breakAddresses.clear(); - } - - // private methods - - private loadSource(file: string) { - if (this._sourceFile !== file) { - this._sourceFile = file; - - const _textDecoder = new TextDecoder(); - - const uri = vscode.Uri.parse(file); - const content = _textDecoder.decode(this.memfs.readFile(uri)); - this._sourceLines = content.split('\n'); - - //this._sourceLines = readFileSync(this._sourceFile).toString().split('\n'); - } - } - - /** - * Run through the file. - * If stepEvent is specified only run a single step and emit the stepEvent. - */ - private run(reverse = false, stepEvent?: vscode.EventEmitter<void>): void { - if (reverse) { - for (let ln = this._currentLine - 1; ln >= 0; ln--) { - if (this.fireEventsForLine(ln, stepEvent)) { - this._currentLine = ln; - return; - } - } - // no more lines: stop at first line - this._currentLine = 0; - this.stopOnEntry.fire(); - } else { - for (let ln = this._currentLine + 1; ln < this._sourceLines.length; ln++) { - if (this.fireEventsForLine(ln, stepEvent)) { - this._currentLine = ln; - return; - } - } - // no more lines: run to end - this.end.fire(); - } - } - - private verifyBreakpoints(path: string): void { - let bps = this._breakPoints.get(path); - if (bps) { - this.loadSource(path); - bps.forEach(bp => { - if (!bp.verified && bp.line < this._sourceLines.length) { - const srcLine = this._sourceLines[bp.line].trim(); - - // if a line is empty or starts with '+' we don't allow to set a breakpoint but move the breakpoint down - if (srcLine.length === 0 || srcLine.indexOf('+') === 0) { - bp.line++; - } - // if a line starts with '-' we don't allow to set a breakpoint but move the breakpoint up - if (srcLine.indexOf('-') === 0) { - bp.line--; - } - // don't set 'verified' to true if the line contains the word 'lazy' - // in this case the breakpoint will be verified 'lazy' after hitting it once. - if (srcLine.indexOf('lazy') < 0) { - bp.verified = true; - this.breakpointValidated.fire(bp); - } - } - }); - } - } - - /** - * Fire events if line has a breakpoint or the word 'exception' is found. - * Returns true is execution needs to stop. - */ - private fireEventsForLine(ln: number, stepEvent?: vscode.EventEmitter<void>): boolean { - - const line = this._sourceLines[ln].trim(); - - // if 'log(...)' found in source -> send argument to debug console - const matches = /log\((.*)\)/.exec(line); - if (matches && matches.length === 2) { - if (this._sourceFile) { - this.output.fire({ text: matches[1], filePath: this._sourceFile, line: ln, column: matches.index }); - } - } - - // if a word in a line matches a data breakpoint, fire a 'dataBreakpoint' event - const words = line.split(' '); - for (let word of words) { - if (this._breakAddresses.has(word)) { - this.stopOnDataBreakpoint.fire(); - return true; - } - } - - // if word 'exception' found in source -> throw exception - if (line.indexOf('exception') >= 0) { - this.stopOnException.fire(); - return true; - } - - // is there a breakpoint? - const breakpoints = this._sourceFile ? this._breakPoints.get(this._sourceFile) : undefined; - if (breakpoints) { - const bps = breakpoints.filter(bp => bp.line === ln); - if (bps.length > 0) { - - // send 'stopped' event - this.stopOnBreakpoint.fire(); - - // the following shows the use of 'breakpoint' events to update properties of a breakpoint in the UI - // if breakpoint is not yet verified, verify it now and send a 'breakpoint' update event - if (!bps[0].verified) { - bps[0].verified = true; - this.breakpointValidated.fire(bps[0]); - } - return true; - } - } - - // non-empty line - if (stepEvent && line.length > 0) { - stepEvent.fire(); - return true; - } - - // nothing interesting found -> continue - return false; - } -} 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 739ce386371..1d62a12c170 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts @@ -8,7 +8,7 @@ import * as assert from 'assert'; import { join } from 'path'; import { commands, workspace, window, Uri, Range, Position, ViewColumn } from 'vscode'; -suite('commands namespace tests', () => { +suite('vscode API - commands', () => { test('getCommands', function (done) { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts index ffd8b53e06c..0b6f13fb01f 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts @@ -7,7 +7,7 @@ import 'mocha'; import * as assert from 'assert'; import * as vscode from 'vscode'; -suite('Configuration tests', () => { +suite('vscode API - configuration', () => { test('configurations, language defaults', function () { const defaultLanguageSettings = vscode.workspace.getConfiguration().get('[abcLang]'); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts index a7f2f1867d0..ec976ff5334 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts @@ -8,7 +8,7 @@ import { debug, workspace, Disposable, commands, window } from 'vscode'; import { disposeAll } from '../utils'; import { basename } from 'path'; -suite('Debug', function () { +suite('vscode API - debug', function () { test('breakpoints', async function () { assert.equal(debug.breakpoints.length, 0); @@ -37,16 +37,24 @@ suite('Debug', function () { disposeAll(toDispose); }); - // @isidor flakey test test.skip('start debugging', async function () { - assert.equal(debug.activeDebugSession, undefined); let stoppedEvents = 0; let variablesReceived: () => void; let initializedReceived: () => void; let configurationDoneReceived: () => void; + const toDispose: Disposable[] = []; + if (debug.activeDebugSession) { + // We are re-running due to flakyness, make sure to clear out state + let sessionTerminatedRetry: () => void; + toDispose.push(debug.onDidTerminateDebugSession(() => { + sessionTerminatedRetry(); + })); + const sessionTerminatedPromise = new Promise<void>(resolve => sessionTerminatedRetry = resolve); + await commands.executeCommand('workbench.action.debug.stop'); + await sessionTerminatedPromise; + } const firstVariablesRetrieved = new Promise<void>(resolve => variablesReceived = resolve); - const toDispose: Disposable[] = []; toDispose.push(debug.registerDebugAdapterTrackerFactory('*', { createDebugAdapterTracker: () => ({ onDidSendMessage: m => { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts index 4b6ee382120..20bf80b0b05 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { workspace, window, Position, Range, commands, TextEditor, TextDocument, TextEditorCursorStyle, TextEditorLineNumbersStyle, SnippetString, Selection, Uri } from 'vscode'; import { createRandomFile, deleteFile, closeAllEditors } from '../utils'; -suite('editor tests', () => { +suite('vscode API - editors', () => { teardown(closeAllEditors); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts index 318b0d07bb3..7bc2f75973f 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { env, extensions, ExtensionKind, UIKind, Uri } from 'vscode'; -suite('env-namespace', () => { +suite('vscode API - env', () => { test('env is set', function () { assert.equal(typeof env.language, 'string'); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/languages.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/languages.test.ts index 0a828ad8d73..9c30a873e85 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/languages.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/languages.test.ts @@ -8,7 +8,7 @@ import { join } from 'path'; import * as vscode from 'vscode'; import { createRandomFile, testFs } from '../utils'; -suite('languages namespace tests', () => { +suite('vscode API - languages', () => { const isWindows = process.platform === 'win32'; diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts index d1237703dac..55fbe0655a7 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts @@ -18,190 +18,188 @@ interface QuickPickExpected { }; } -suite('window namespace tests', function () { +suite('vscode API - quick input', function () { - suite('QuickInput tests', function () { - teardown(closeAllEditors); + teardown(closeAllEditors); - test('createQuickPick, select second', function (_done) { - let done = (err?: any) => { - done = () => {}; - _done(err); - }; + test('createQuickPick, select second', function (_done) { + let done = (err?: any) => { + done = () => { }; + _done(err); + }; - const quickPick = createQuickPick({ - events: ['active', 'active', 'selection', 'accept', 'hide'], - activeItems: [['eins'], ['zwei']], - selectionItems: [['zwei']], - acceptedItems: { - active: [['zwei']], - selection: [['zwei']], - dispose: [true] - }, - }, (err?: any) => done(err)); - quickPick.items = ['eins', 'zwei', 'drei'].map(label => ({ label })); - quickPick.show(); + const quickPick = createQuickPick({ + events: ['active', 'active', 'selection', 'accept', 'hide'], + activeItems: [['eins'], ['zwei']], + selectionItems: [['zwei']], + acceptedItems: { + active: [['zwei']], + selection: [['zwei']], + dispose: [true] + }, + }, (err?: any) => done(err)); + quickPick.items = ['eins', 'zwei', 'drei'].map(label => ({ label })); + quickPick.show(); - (async () => { - await commands.executeCommand('workbench.action.quickOpenSelectNext'); - await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); - })() - .catch(err => done(err)); - }); + (async () => { + await commands.executeCommand('workbench.action.quickOpenSelectNext'); + await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); + })() + .catch(err => done(err)); + }); - test('createQuickPick, focus second', function (_done) { - let done = (err?: any) => { - done = () => {}; - _done(err); - }; + test('createQuickPick, focus second', function (_done) { + let done = (err?: any) => { + done = () => { }; + _done(err); + }; - const quickPick = createQuickPick({ - events: ['active', 'selection', 'accept', 'hide'], - activeItems: [['zwei']], - selectionItems: [['zwei']], - acceptedItems: { - active: [['zwei']], - selection: [['zwei']], - dispose: [true] - }, - }, (err?: any) => done(err)); - quickPick.items = ['eins', 'zwei', 'drei'].map(label => ({ label })); - quickPick.activeItems = [quickPick.items[1]]; - quickPick.show(); + const quickPick = createQuickPick({ + events: ['active', 'selection', 'accept', 'hide'], + activeItems: [['zwei']], + selectionItems: [['zwei']], + acceptedItems: { + active: [['zwei']], + selection: [['zwei']], + dispose: [true] + }, + }, (err?: any) => done(err)); + quickPick.items = ['eins', 'zwei', 'drei'].map(label => ({ label })); + quickPick.activeItems = [quickPick.items[1]]; + quickPick.show(); - (async () => { - await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); - })() - .catch(err => done(err)); - }); + (async () => { + await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); + })() + .catch(err => done(err)); + }); - test('createQuickPick, select first and second', function (_done) { - let done = (err?: any) => { - done = () => {}; - _done(err); - }; + test('createQuickPick, select first and second', function (_done) { + let done = (err?: any) => { + done = () => { }; + _done(err); + }; - const quickPick = createQuickPick({ - events: ['active', 'selection', 'active', 'selection', 'accept', 'hide'], - activeItems: [['eins'], ['zwei']], - selectionItems: [['eins'], ['eins', 'zwei']], - acceptedItems: { - active: [['zwei']], - selection: [['eins', 'zwei']], - dispose: [true] - }, - }, (err?: any) => done(err)); - quickPick.canSelectMany = true; - quickPick.items = ['eins', 'zwei', 'drei'].map(label => ({ label })); - quickPick.show(); + const quickPick = createQuickPick({ + events: ['active', 'selection', 'active', 'selection', 'accept', 'hide'], + activeItems: [['eins'], ['zwei']], + selectionItems: [['eins'], ['eins', 'zwei']], + acceptedItems: { + active: [['zwei']], + selection: [['eins', 'zwei']], + dispose: [true] + }, + }, (err?: any) => done(err)); + quickPick.canSelectMany = true; + quickPick.items = ['eins', 'zwei', 'drei'].map(label => ({ label })); + quickPick.show(); - (async () => { - await commands.executeCommand('workbench.action.quickOpenSelectNext'); - await commands.executeCommand('workbench.action.quickPickManyToggle'); - await commands.executeCommand('workbench.action.quickOpenSelectNext'); - await commands.executeCommand('workbench.action.quickPickManyToggle'); - await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); - })() - .catch(err => done(err)); - }); + (async () => { + await commands.executeCommand('workbench.action.quickOpenSelectNext'); + await commands.executeCommand('workbench.action.quickPickManyToggle'); + await commands.executeCommand('workbench.action.quickOpenSelectNext'); + await commands.executeCommand('workbench.action.quickPickManyToggle'); + await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); + })() + .catch(err => done(err)); + }); - test('createQuickPick, selection events', function (_done) { - let done = (err?: any) => { - done = () => {}; - _done(err); - }; + test('createQuickPick, selection events', function (_done) { + let done = (err?: any) => { + done = () => { }; + _done(err); + }; - const quickPick = createQuickPick({ - events: ['active', 'selection', 'accept', 'selection', 'accept', 'hide'], - activeItems: [['eins']], - selectionItems: [['zwei'], ['drei']], - acceptedItems: { - active: [['eins'], ['eins']], - selection: [['zwei'], ['drei']], - dispose: [false, true] - }, - }, (err?: any) => done(err)); - quickPick.items = ['eins', 'zwei', 'drei'].map(label => ({ label })); - quickPick.show(); + const quickPick = createQuickPick({ + events: ['active', 'selection', 'accept', 'selection', 'accept', 'hide'], + activeItems: [['eins']], + selectionItems: [['zwei'], ['drei']], + acceptedItems: { + active: [['eins'], ['eins']], + selection: [['zwei'], ['drei']], + dispose: [false, true] + }, + }, (err?: any) => done(err)); + quickPick.items = ['eins', 'zwei', 'drei'].map(label => ({ label })); + quickPick.show(); - quickPick.selectedItems = [quickPick.items[1]]; - setTimeout(() => { - quickPick.selectedItems = [quickPick.items[2]]; - }, 0); - }); + quickPick.selectedItems = [quickPick.items[1]]; + setTimeout(() => { + quickPick.selectedItems = [quickPick.items[2]]; + }, 0); + }); - test('createQuickPick, continue after first accept', function (_done) { - let done = (err?: any) => { - done = () => {}; - _done(err); - }; + test('createQuickPick, continue after first accept', function (_done) { + let done = (err?: any) => { + done = () => { }; + _done(err); + }; - const quickPick = createQuickPick({ - events: ['active', 'selection', 'accept', 'active', 'selection', 'active', 'selection', 'accept', 'hide'], - activeItems: [['eins'], [], ['drei']], - selectionItems: [['eins'], [], ['drei']], - acceptedItems: { - active: [['eins'], ['drei']], - selection: [['eins'], ['drei']], - dispose: [false, true] - }, - }, (err?: any) => done(err)); - quickPick.items = ['eins', 'zwei'].map(label => ({ label })); - quickPick.show(); + const quickPick = createQuickPick({ + events: ['active', 'selection', 'accept', 'active', 'selection', 'active', 'selection', 'accept', 'hide'], + activeItems: [['eins'], [], ['drei']], + selectionItems: [['eins'], [], ['drei']], + acceptedItems: { + active: [['eins'], ['drei']], + selection: [['eins'], ['drei']], + dispose: [false, true] + }, + }, (err?: any) => done(err)); + quickPick.items = ['eins', 'zwei'].map(label => ({ label })); + quickPick.show(); - (async () => { - await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); + (async () => { + await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); + await timeout(async () => { + quickPick.items = ['drei', 'vier'].map(label => ({ label })); await timeout(async () => { - quickPick.items = ['drei', 'vier'].map(label => ({ label })); - await timeout(async () => { - await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); - }, 0); + await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); }, 0); - })() - .catch(err => done(err)); + }, 0); + })() + .catch(err => done(err)); + }); + + test('createQuickPick, dispose in onDidHide', function (_done) { + let done = (err?: any) => { + done = () => { }; + _done(err); + }; + + let hidden = false; + const quickPick = window.createQuickPick(); + quickPick.onDidHide(() => { + if (hidden) { + done(new Error('Already hidden')); + } else { + hidden = true; + quickPick.dispose(); + setTimeout(done, 0); + } }); + quickPick.show(); + quickPick.hide(); + }); - test('createQuickPick, dispose in onDidHide', function (_done) { - let done = (err?: any) => { - done = () => {}; - _done(err); - }; + test('createQuickPick, hide and dispose', function (_done) { + let done = (err?: any) => { + done = () => { }; + _done(err); + }; - let hidden = false; - const quickPick = window.createQuickPick(); - quickPick.onDidHide(() => { - if (hidden) { - done(new Error('Already hidden')); - } else { - hidden = true; - quickPick.dispose(); - setTimeout(done, 0); - } - }); - quickPick.show(); - quickPick.hide(); - }); - - test('createQuickPick, hide and dispose', function (_done) { - let done = (err?: any) => { - done = () => {}; - _done(err); - }; - - let hidden = false; - const quickPick = window.createQuickPick(); - quickPick.onDidHide(() => { - if (hidden) { - done(new Error('Already hidden')); - } else { - hidden = true; - setTimeout(done, 0); - } - }); - quickPick.show(); - quickPick.hide(); - quickPick.dispose(); + let hidden = false; + const quickPick = window.createQuickPick(); + quickPick.onDidHide(() => { + if (hidden) { + done(new Error('Already hidden')); + } else { + hidden = true; + setTimeout(done, 0); + } }); + quickPick.show(); + quickPick.hide(); + quickPick.dispose(); }); }); @@ -276,4 +274,4 @@ function createQuickPick(expected: QuickPickExpected, done: (err?: any) => void, async function timeout<T>(run: () => Promise<T> | T, ms: number): Promise<T> { return new Promise<T>(resolve => setTimeout(() => resolve(run()), ms)); -} \ No newline at end of file +} 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 ce5b0f44aa7..9f45c7b8897 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts @@ -3,10 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { window, Pseudoterminal, EventEmitter, TerminalDimensions, workspace, ConfigurationTarget, Disposable } from 'vscode'; +import { window, Pseudoterminal, EventEmitter, TerminalDimensions, workspace, ConfigurationTarget, Disposable, UIKind, env, EnvironmentVariableMutatorType, EnvironmentVariableMutator } from 'vscode'; import { doesNotThrow, equal, ok, deepEqual, throws } from 'assert'; -suite('window namespace tests', () => { +// TODO@Daniel flaky tests (https://github.com/microsoft/vscode/issues/92826) +((env.uiKind === UIKind.Web) ? suite.skip : suite)('vscode API - terminal', () => { suiteSetup(async () => { // Disable conpty in integration tests because of https://github.com/microsoft/vscode/issues/76548 await workspace.getConfiguration('terminal.integrated').update('windowsEnableConpty', false, ConfigurationTarget.Global); @@ -19,6 +20,7 @@ suite('window namespace tests', () => { disposables.length = 0; }); + test('sendText immediately after createTerminal should not throw', (done) => { disposables.push(window.onDidOpenTerminal(term => { try { @@ -531,5 +533,203 @@ suite('window namespace tests', () => { } }); }); + + suite('getEnvironmentVariableCollection', () => { + test('should have collection variables apply to terminals immediately after setting', (done) => { + // Text to match on before passing the test + const expectedText = [ + '~a2~', + 'b1~b2~', + '~c2~c1' + ]; + disposables.push(window.onDidWriteTerminalData(e => { + try { + equal(terminal, e.terminal); + } catch (e) { + done(e); + } + console.log('Terminal data: ' + e.data); + // Multiple expected could show up in the same data event + while (expectedText.length > 0 && e.data.indexOf(expectedText[0]) >= 0) { + expectedText.shift(); + // Check if all string are found, if so finish the test + if (expectedText.length === 0) { + disposables.push(window.onDidCloseTerminal(() => done())); + terminal.dispose(); + } + } + })); + const collection = window.getEnvironmentVariableCollection(); + disposables.push(collection); + collection.replace('A', '~a2~'); + collection.append('B', '~b2~'); + collection.prepend('C', '~c2~'); + const terminal = window.createTerminal({ + env: { + A: 'a1', + B: 'b1', + C: 'c1' + } + }); + // Run both PowerShell and sh commands, errors don't matter we're just looking for + // the correct output + terminal.sendText('$env:A'); + terminal.sendText('echo $A'); + terminal.sendText('$env:B'); + terminal.sendText('echo $B'); + terminal.sendText('$env:C'); + terminal.sendText('echo $C'); + }); + + test('should have collection variables apply to environment variables that don\'t exist', (done) => { + // Text to match on before passing the test + const expectedText = [ + '~a2~', + '~b2~', + '~c2~' + ]; + disposables.push(window.onDidWriteTerminalData(e => { + try { + equal(terminal, e.terminal); + } catch (e) { + done(e); + } + console.log('Terminal data: ' + e.data); + // Multiple expected could show up in the same data event + while (expectedText.length > 0 && e.data.indexOf(expectedText[0]) >= 0) { + expectedText.shift(); + // Check if all string are found, if so finish the test + if (expectedText.length === 0) { + disposables.push(window.onDidCloseTerminal(() => done())); + terminal.dispose(); + } + } + })); + const collection = window.getEnvironmentVariableCollection(); + disposables.push(collection); + collection.replace('A', '~a2~'); + collection.append('B', '~b2~'); + collection.prepend('C', '~c2~'); + const terminal = window.createTerminal({ + env: { + A: null, + B: null, + C: null + } + }); + // Run both PowerShell and sh commands, errors don't matter we're just looking for + // the correct output + terminal.sendText('$env:A'); + terminal.sendText('echo $A'); + terminal.sendText('$env:B'); + terminal.sendText('echo $B'); + terminal.sendText('$env:C'); + terminal.sendText('echo $C'); + }); + + test('should respect clearing entries', (done) => { + // Text to match on before passing the test + const expectedText = [ + '~a1~', + '~b1~' + ]; + disposables.push(window.onDidWriteTerminalData(e => { + try { + equal(terminal, e.terminal); + } catch (e) { + done(e); + } + // Multiple expected could show up in the same data event + while (expectedText.length > 0 && e.data.indexOf(expectedText[0]) >= 0) { + expectedText.shift(); + // Check if all string are found, if so finish the test + if (expectedText.length === 0) { + disposables.push(window.onDidCloseTerminal(() => done())); + terminal.dispose(); + } + } + })); + const collection = window.getEnvironmentVariableCollection(); + disposables.push(collection); + collection.replace('A', '~a2~'); + collection.replace('B', '~a2~'); + collection.clear(); + const terminal = window.createTerminal({ + env: { + A: '~a1~', + B: '~b1~' + } + }); + // Run both PowerShell and sh commands, errors don't matter we're just looking for + // the correct output + terminal.sendText('$env:A'); + terminal.sendText('echo $A'); + terminal.sendText('$env:B'); + terminal.sendText('echo $B'); + }); + + test('should respect deleting entries', (done) => { + // Text to match on before passing the test + const expectedText = [ + '~a1~', + '~b2~' + ]; + disposables.push(window.onDidWriteTerminalData(e => { + try { + equal(terminal, e.terminal); + } catch (e) { + done(e); + } + // Multiple expected could show up in the same data event + while (expectedText.length > 0 && e.data.indexOf(expectedText[0]) >= 0) { + expectedText.shift(); + // Check if all string are found, if so finish the test + if (expectedText.length === 0) { + disposables.push(window.onDidCloseTerminal(() => done())); + terminal.dispose(); + } + } + })); + const collection = window.getEnvironmentVariableCollection(); + disposables.push(collection); + collection.replace('A', '~a2~'); + collection.replace('B', '~b2~'); + collection.delete('A'); + const terminal = window.createTerminal({ + env: { + A: '~a1~', + B: '~b2~' + } + }); + // Run both PowerShell and sh commands, errors don't matter we're just looking for + // the correct output + terminal.sendText('$env:A'); + terminal.sendText('echo $A'); + terminal.sendText('$env:B'); + terminal.sendText('echo $B'); + }); + + test('get and forEach should work', () => { + const collection = window.getEnvironmentVariableCollection(); + disposables.push(collection); + collection.replace('A', '~a2~'); + collection.append('B', '~b2~'); + collection.prepend('C', '~c2~'); + + // Verify get + deepEqual(collection.get('A'), { value: '~a2~', type: EnvironmentVariableMutatorType.Replace }); + deepEqual(collection.get('B'), { value: '~b2~', type: EnvironmentVariableMutatorType.Append }); + deepEqual(collection.get('C'), { value: '~c2~', type: EnvironmentVariableMutatorType.Prepend }); + + // Verify forEach + const entries: [string, EnvironmentVariableMutator][] = []; + collection.forEach((v, m) => entries.push([v, m])); + deepEqual(entries, [ + ['A', { value: '~a2~', type: EnvironmentVariableMutatorType.Replace }], + ['B', { value: '~b2~', type: EnvironmentVariableMutatorType.Append }], + ['C', { value: '~c2~', type: EnvironmentVariableMutatorType.Prepend }] + ]); + }); + }); }); }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/types.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/types.test.ts index b2ad43d30b0..53265b35e99 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/types.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/types.test.ts @@ -7,12 +7,9 @@ import 'mocha'; import * as assert from 'assert'; import * as vscode from 'vscode'; - -suite('types', () => { +suite('vscode API - types', () => { test('static properties, es5 compat class', function () { - - assert.ok(vscode.ThemeIcon.File instanceof vscode.ThemeIcon); assert.ok(vscode.ThemeIcon.Folder instanceof vscode.ThemeIcon); assert.ok(vscode.CodeActionKind.Empty instanceof vscode.CodeActionKind); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts index 06d284c3762..704d2f115ed 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts @@ -14,7 +14,7 @@ const webviewId = 'myWebview'; const testDocument = join(vscode.workspace.rootPath || '', './bower.json'); -suite('Webview tests', () => { +suite('vscode API - webview', () => { const disposables: vscode.Disposable[] = []; function _register<T extends vscode.Disposable>(disposable: T) { 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 620ce762632..d1d6f3e7fb4 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts @@ -8,7 +8,7 @@ import { workspace, window, commands, ViewColumn, TextEditorViewColumnChangeEven import { join } from 'path'; import { closeAllEditors, pathEquals, createRandomFile } from '../utils'; -suite('window namespace tests', () => { +suite('vscode API - window', () => { teardown(closeAllEditors); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.event.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.event.test.ts index 58f0b48f126..e192c63c0ab 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.event.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.event.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; import { createRandomFile, withLogDisabled } from '../utils'; -suite('workspace-event', () => { +suite('vscode API - workspace events', () => { const disposables: vscode.Disposable[] = []; diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.fs.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.fs.test.ts index f3c69fbbe67..2eb21a4c1f9 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.fs.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.fs.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; import { posix } from 'path'; -suite('workspace-fs', () => { +suite('vscode API - workspace-fs', () => { let root: vscode.Uri; diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts index 1f7a9c93197..f3e56977125 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { window, tasks, Disposable, TaskDefinition, Task, EventEmitter, CustomExecution, Pseudoterminal, TaskScope, commands, Task2 } from 'vscode'; -suite('workspace-namespace', () => { +suite('vscode API - tasks', () => { suite('Tasks', () => { let disposables: Disposable[] = []; 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 c26c88671dd..2a58c4630e0 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -9,7 +9,7 @@ import { createRandomFile, deleteFile, closeAllEditors, pathEquals, rndName, dis import { join, posix, basename } from 'path'; import * as fs from 'fs'; -suite('workspace-namespace', () => { +suite('vscode API - workspace', () => { teardown(closeAllEditors); diff --git a/extensions/vscode-api-tests/src/typings/ref.d.ts b/extensions/vscode-api-tests/src/typings/ref.d.ts index 9abc416f7e8..e3e47385d66 100644 --- a/extensions/vscode-api-tests/src/typings/ref.d.ts +++ b/extensions/vscode-api-tests/src/typings/ref.d.ts @@ -5,6 +5,4 @@ /// <reference path="../../../../src/vs/vscode.d.ts" /> /// <reference path="../../../../src/vs/vscode.proposed.d.ts" /> -/// <reference path="../../../types/lib.textEncoder.d.ts" /> -/// <reference path="../../../types/lib.url.d.ts" /> /// <reference types='@types/node'/> diff --git a/extensions/vscode-api-tests/src/workspace-tests/workspace.test.ts b/extensions/vscode-api-tests/src/workspace-tests/workspace.test.ts index f018f581c42..1b4ef88325a 100644 --- a/extensions/vscode-api-tests/src/workspace-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/workspace-tests/workspace.test.ts @@ -8,7 +8,7 @@ import * as vscode from 'vscode'; import { closeAllEditors, pathEquals } from '../utils'; import { join } from 'path'; -suite('workspace-namespace', () => { +suite('vscode API - workspace', () => { teardown(closeAllEditors); diff --git a/extensions/vscode-colorize-tests/package.json b/extensions/vscode-colorize-tests/package.json index d99e050d302..c976dbc8d0f 100644 --- a/extensions/vscode-colorize-tests/package.json +++ b/extensions/vscode-colorize-tests/package.json @@ -1,60 +1,59 @@ { - "name": "vscode-colorize-tests", - "description": "Colorize tests for VS Code", - "version": "0.0.1", - "publisher": "vscode", - "license": "MIT", - "private": true, - "activationEvents": [ - "onLanguage:json" - ], - "main": "./out/colorizerTestMain", - "enableProposedApi": true, - "engines": { - "vscode": "*" - }, - "scripts": { - "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-colorize-tests ./tsconfig.json" - }, - "dependencies": { - "jsonc-parser": "2.2.1" - }, - "devDependencies": { - "@types/node": "^12.11.7", - "mocha-junit-reporter": "^1.17.0", - "mocha-multi-reporters": "^1.1.7", - "vscode": "1.1.5" - }, - "contributes": { - "semanticTokenTypes": [ - { - "id": "testToken", - "description": "A test token" - } - ], - "semanticTokenModifiers": [ - { - "id": "testModifier", - "description": "A test modifier" - } - ], - "semanticTokenStyleDefaults": [ - { - "selector": "testToken", - "scope": [ "entity.name.function.special" ] - }, - { - "selector": "*.testModifier", - "light": { - "fontStyle": "bold" - }, - "dark": { - "fontStyle": "bold" - }, - "highContrast": { - "fontStyle": "bold" - } - } - ] - } + "name": "vscode-colorize-tests", + "description": "Colorize tests for VS Code", + "version": "0.0.1", + "publisher": "vscode", + "license": "MIT", + "private": true, + "activationEvents": [ + "onLanguage:json" + ], + "main": "./out/colorizerTestMain", + "enableProposedApi": true, + "engines": { + "vscode": "*" + }, + "scripts": { + "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-colorize-tests ./tsconfig.json" + }, + "dependencies": { + "jsonc-parser": "2.2.1" + }, + "devDependencies": { + "@types/node": "^12.11.7", + "mocha-junit-reporter": "^1.17.0", + "mocha-multi-reporters": "^1.1.7", + "vscode": "1.1.5" + }, + "contributes": { + "semanticTokenTypes": [ + { + "id": "testToken", + "description": "A test token" + } + ], + "semanticTokenModifiers": [ + { + "id": "testModifier", + "description": "A test modifier" + } + ], + "semanticTokenScopes": [ + { + "scopes": { + "testToken": [ + "entity.name.function.special" + ] + } + } + ], + "productIconThemes": [ + { + "id": "Test Product Icons", + "label": "The Test Product Icon Theme", + "path": "./producticons/test-product-icon-theme.json", + "_watch": true + } + ] + } } diff --git a/extensions/vscode-colorize-tests/producticons/ElegantIcons.woff b/extensions/vscode-colorize-tests/producticons/ElegantIcons.woff new file mode 100644 index 00000000000..393305253e5 Binary files /dev/null and b/extensions/vscode-colorize-tests/producticons/ElegantIcons.woff differ diff --git a/extensions/vscode-colorize-tests/producticons/index.html b/extensions/vscode-colorize-tests/producticons/index.html new file mode 100644 index 00000000000..0d34ddedb57 --- /dev/null +++ b/extensions/vscode-colorize-tests/producticons/index.html @@ -0,0 +1,3049 @@ +<!doctype html> +<html> + +<head> + <title>Your Font/Glyphs + + + + + + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+

Class Names

+
+ + +  arrow_up + + + +  arrow_down + + + +  arrow_left + + + +  arrow_right + + + +  arrow_left-up + + + +  arrow_right-up + + + +  arrow_right-down + + + +  arrow_left-down + + + +  arrow-up-down + + + +  arrow_up-down_alt + + + +  arrow_left-right_alt + + + +  arrow_left-right + + + +  arrow_expand_alt2 + + + +  arrow_expand_alt + + + +  arrow_condense + + + +  arrow_expand + + + +  arrow_move + + + +  arrow_carrot-up + + + +  arrow_carrot-down + + + +  arrow_carrot-left + + + +  arrow_carrot-right + + + +  arrow_carrot-2up + + + +  arrow_carrot-2down + + + +  arrow_carrot-2left + + + +  arrow_carrot-2right + + + +  arrow_carrot-up_alt2 + + + +  arrow_carrot-down_alt2 + + + +  arrow_carrot-left_alt2 + + + +  arrow_carrot-right_alt2 + + + +  arrow_carrot-2up_alt2 + + + +  arrow_carrot-2down_alt2 + + + +  arrow_carrot-2left_alt2 + + + +  arrow_carrot-2right_alt2 + + + +  arrow_triangle-up + + + +  arrow_triangle-down + + + +  arrow_triangle-left + + + +  arrow_triangle-right + + + +  arrow_triangle-up_alt2 + + + +  arrow_triangle-down_alt2 + + + +  arrow_triangle-left_alt2 + + + +  arrow_triangle-right_alt2 + + + +  arrow_back + + + +  icon_minus-06 + + + +  icon_plus + + + +  icon_close + + + +  icon_check + + + +  icon_minus_alt2 + + + +  icon_plus_alt2 + + + +  icon_close_alt2 + + + +  icon_check_alt2 + + + +  icon_zoom-out_alt + + + +  icon_zoom-in_alt + + + +  icon_search + + + +  icon_box-empty + + + +  icon_box-selected + + + +  icon_minus-box + + + +  icon_plus-box + + + +  icon_box-checked + + + +  icon_circle-empty + + + +  icon_circle-slelected + + + +  icon_stop_alt2 + + + +  icon_stop + + + +  icon_pause_alt2 + + + +  icon_pause + + + +  icon_menu + + + +  icon_menu-square_alt2 + + + +  icon_menu-circle_alt2 + + + +  icon_ul + + + +  icon_ol + + + +  icon_adjust-horiz + + + +  icon_adjust-vert + + + +  icon_document_alt + + + +  icon_documents_alt + + + +  icon_pencil + + + +  icon_pencil-edit_alt + + + +  icon_pencil-edit + + + +  icon_folder-alt + + + +  icon_folder-open_alt + + + +  icon_folder-add_alt + + + +  icon_info_alt + + + +  icon_error-oct_alt + + + +  icon_error-circle_alt + + + +  icon_error-triangle_alt + + + +  icon_question_alt2 + + + +  icon_question + + + +  icon_comment_alt + + + +  icon_chat_alt + + + +  icon_vol-mute_alt + + + +  icon_volume-low_alt + + + +  icon_volume-high_alt + + + +  icon_quotations + + + +  icon_quotations_alt2 + + + +  icon_clock_alt + + + +  icon_lock_alt + + + +  icon_lock-open_alt + + + +  icon_key_alt + + + +  icon_cloud_alt + + + +  icon_cloud-upload_alt + + + +  icon_cloud-download_alt + + + +  icon_image + + + +  icon_images + + + +  icon_lightbulb_alt + + + +  icon_gift_alt + + + +  icon_house_alt + + + +  icon_genius + + + +  icon_mobile + + + +  icon_tablet + + + +  icon_laptop + + + +  icon_desktop + + + +  icon_camera_alt + + + +  icon_mail_alt + + + +  icon_cone_alt + + + +  icon_ribbon_alt + + + +  icon_bag_alt + + + +  icon_creditcard + + + +  icon_cart_alt + + + +  icon_paperclip + + + +  icon_tag_alt + + + +  icon_tags_alt + + + +  icon_trash_alt + + + +  icon_cursor_alt + + + +  icon_mic_alt + + + +  icon_compass_alt + + + +  icon_pin_alt + + + +  icon_pushpin_alt + + + +  icon_map_alt + + + +  icon_drawer_alt + + + +  icon_toolbox_alt + + + +  icon_book_alt + + + +  icon_calendar + + + +  icon_film + + + +  icon_table + + + +  icon_contacts_alt + + + +  icon_headphones + + + +  icon_lifesaver + + + +  icon_piechart + + + +  icon_refresh + + + +  icon_link_alt + + + +  icon_link + + + +  icon_loading + + + +  icon_blocked + + + +  icon_archive_alt + + + +  icon_heart_alt + + +
+ + + +  icon_printer + + + +  icon_calulator + + + +  icon_building + + + +  icon_floppy + + + +  icon_drive + + + +  icon_search-2 + + + +  icon_id + + + +  icon_id-2 + + + +  icon_puzzle + + + +  icon_like + + + +  icon_dislike + + + +  icon_mug + + + +  icon_currency + + + +  icon_wallet + + + +  icon_pens + + + +  icon_easel + + + +  icon_flowchart + + + +  icon_datareport + + + +  icon_briefcase + + + +  icon_shield + + + +  icon_percent + + + +  icon_globe + + + +  icon_globe-2 + + + +  icon_target + + + +  icon_hourglass + + + +  icon_balance + + +
+ + + +  icon_star_alt + + + +  icon_star-half_alt + + + +  icon_star + + + +  icon_star-half + + + +  icon_tools + + + +  icon_tool + + + +  icon_cog + + + +  icon_cogs + + + +  arrow_up_alt + + + +  arrow_down_alt + + + +  arrow_left_alt + + + +  arrow_right_alt + + + +  arrow_left-up_alt + + + +  arrow_right-up_alt + + + +  arrow_right-down_alt + + + +  arrow_left-down_alt + + + +  arrow_condense_alt + + + +  arrow_expand_alt3 + + + +  arrow_carrot_up_alt + + + +  arrow_carrot-down_alt + + + +  arrow_carrot-left_alt + + + +  arrow_carrot-right_alt + + + +  arrow_carrot-2up_alt + + + +  arrow_carrot-2dwnn_alt + + + +  arrow_carrot-2left_alt + + + +  arrow_carrot-2right_alt + + + +  arrow_triangle-up_alt + + + +  arrow_triangle-down_alt + + + +  arrow_triangle-left_alt + + + +  arrow_triangle-right_alt + + + +  icon_minus_alt + + + +  icon_plus_alt + + + +  icon_close_alt + + + +  icon_check_alt + + + +  icon_zoom-out + + + +  icon_zoom-in + + + +  icon_stop_alt + + + +  icon_menu-square_alt + + + +  icon_menu-circle_alt + + + +  icon_document + + + +  icon_documents + + + +  icon_pencil_alt + + + +  icon_folder + + + +  icon_folder-open + + + +  icon_folder-add + + + +  icon_folder_upload + + + +  icon_folder_download + + + +  icon_info + + + +  icon_error-circle + + + +  icon_error-oct + + + +  icon_error-triangle + + + +  icon_question_alt + + + +  icon_comment + + + +  icon_chat + + + +  icon_vol-mute + + + +  icon_volume-low + + + +  icon_volume-high + + + +  icon_quotations_alt + + + +  icon_clock + + + +  icon_lock + + + +  icon_lock-open + + + +  icon_key + + + +  icon_cloud + + + +  icon_cloud-upload + + + +  icon_cloud-download + + + +  icon_lightbulb + + + +  icon_gift + + + +  icon_house + + + +  icon_camera + + + +  icon_mail + + + +  icon_cone + + + +  icon_ribbon + + + +  icon_bag + + + +  icon_cart + + + +  icon_tag + + + +  icon_tags + + + +  icon_trash + + + +  icon_cursor + + + +  icon_mic + + + +  icon_compass + + + +  icon_pin + + + +  icon_pushpin + + + +  icon_map + + + +  icon_drawer + + + +  icon_toolbox + + + +  icon_book + + + +  icon_contacts + + + +  icon_archive + + + +  icon_heart + + + +  icon_profile + + + +  icon_group + + + +  icon_grid-2x2 + + + +  icon_grid-3x3 + + + +  icon_music + + + +  icon_pause_alt + + + +  icon_phone + + + +  icon_upload + + + +  icon_download + + + +  icon_rook + + +
+ + + +  icon_printer-alt + + + +  icon_calculator_alt + + + +  icon_building_alt + + + +  icon_floppy_alt + + + +  icon_drive_alt + + + +  icon_search_alt + + + +  icon_id_alt + + + +  icon_id-2_alt + + + +  icon_puzzle_alt + + + +  icon_like_alt + + + +  icon_dislike_alt + + + +  icon_mug_alt + + + +  icon_currency_alt + + + +  icon_wallet_alt + + + +  icon_pens_alt + + + +  icon_easel_alt + + + +  icon_flowchart_alt + + + +  icon_datareport_alt + + + +  icon_briefcase_alt + + + +  icon_shield_alt + + + +  icon_percent_alt + + + +  icon_globe_alt + + + +  icon_clipboard + + +
+ + + +  social_facebook + + + +  social_twitter + + + +  social_pinterest + + + +  social_googleplus + + + +  social_tumblr + + + +  social_tumbleupon + + + +  social_wordpress + + + +  social_instagram + + + +  social_dribbble + + + +  social_vimeo + + + +  social_linkedin + + + +  social_rss + + + +  social_deviantart + + + +  social_share + + + +  social_myspace + + + +  social_skype + + + +  social_youtube + + + +  social_picassa + + + +  social_googledrive + + + +  social_flickr + + + +  social_blogger + + + +  social_spotify + + + +  social_delicious + + + +  social_facebook_circle + + + +  social_twitter_circle + + + +  social_pinterest_circle + + + +  social_googleplus_circle + + + +  social_tumblr_circle + + + +  social_stumbleupon_circle + + + +  social_wordpress_circle + + + +  social_instagram_circle + + + +  social_dribbble_circle + + + +  social_vimeo_circle + + + +  social_linkedin_circle + + + +  social_rss_circle + + + +  social_deviantart_circle + + + +  social_share_circle + + + +  social_myspace_circle + + + +  social_skype_circle + + + +  social_youtube_circle + + + +  social_picassa_circle + + + +  social_googledrive_alt2 + + + +  social_flickr_circle + + + +  social_blogger_circle + + + +  social_spotify_circle + + + +  social_delicious_circle + + + +  social_facebook_square + + + +  social_twitter_square + + + +  social_pinterest_square + + + +  social_googleplus_square + + + +  social_tumblr_square + + + +  social_stumbleupon_square + + + +  social_wordpress_square + + + +  social_instagram_square + + + +  social_dribbble_square + + + +  social_vimeo_square + + + +  social_linkedin_square + + + +  social_rss_square + + + +  social_deviantart_square + + + +  social_share_square + + + +  social_myspace_square + + + +  social_skype_square + + + +  social_youtube_square + + + +  social_picassa_square + + + +  social_googledrive_square + + + +  social_flickr_square + + + +  social_blogger_square + + + +  social_spotify_square + + + +  social_delicious_square + +
+ +
+ + + + diff --git a/src/vs/editor/common/standalone/promise-polyfill/polyfill.license.txt b/extensions/vscode-colorize-tests/producticons/mit_license.txt similarity index 92% rename from src/vs/editor/common/standalone/promise-polyfill/polyfill.license.txt rename to extensions/vscode-colorize-tests/producticons/mit_license.txt index 6f7c0123162..effefee5f0c 100644 --- a/src/vs/editor/common/standalone/promise-polyfill/polyfill.license.txt +++ b/extensions/vscode-colorize-tests/producticons/mit_license.txt @@ -1,5 +1,6 @@ -Copyright (c) 2014 Taylor Hakes -Copyright (c) 2014 Forbes Lindesay +The MIT License (MIT) + +Copyright (c) <2013> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -17,4 +18,4 @@ 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. +THE SOFTWARE. \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/producticons/test-product-icon-theme.json b/extensions/vscode-colorize-tests/producticons/test-product-icon-theme.json new file mode 100644 index 00000000000..dc076aef5f9 --- /dev/null +++ b/extensions/vscode-colorize-tests/producticons/test-product-icon-theme.json @@ -0,0 +1,43 @@ +{ + // ElegantIcons from https://www.elegantthemes.com/icons/elegant_font.zip + "fonts": [ + { + "id": "elegant", + "src": [ + { + "path": "./ElegantIcons.woff", + "format": "woff" + } + ], + "weight": "normal", + "style": "normal", + } + ], + "iconDefinitions": { + "chevron-down": { + "fontCharacter": "\\43", + }, + "chevron-right": { + "fontCharacter": "\\45" + }, + "error": { + "fontCharacter": "\\e062" + }, + "warning": { + "fontCharacter": "\\e063" + }, + "settings-gear": { + "fontCharacter": "\\e030" + }, + "files": { + "fontCharacter": "\\e056" + }, + "extensions": { + "fontCharacter": "\\e015" + }, + "debug-alt-2": { + "fontCharacter": "\\e072" + } + + } +} diff --git a/extensions/vscode-colorize-tests/src/colorizerTestMain.ts b/extensions/vscode-colorize-tests/src/colorizerTestMain.ts index a014275a7e6..a29a35b1233 100644 --- a/extensions/vscode-colorize-tests/src/colorizerTestMain.ts +++ b/extensions/vscode-colorize-tests/src/colorizerTestMain.ts @@ -56,7 +56,7 @@ export function activate(context: vscode.ExtensionContext): any { }; jsoncParser.visit(document.getText(), visitor); - return new vscode.SemanticTokens(builder.build()); + return builder.build(); } }; diff --git a/extensions/vscode-web-playground/.gitignore b/extensions/vscode-web-playground/.gitignore new file mode 100644 index 00000000000..8e5962ee727 --- /dev/null +++ b/extensions/vscode-web-playground/.gitignore @@ -0,0 +1,2 @@ +out +node_modules \ No newline at end of file diff --git a/extensions/vscode-web-playground/.vscode/tasks.json b/extensions/vscode-web-playground/.vscode/tasks.json new file mode 100644 index 00000000000..390a93a3a7f --- /dev/null +++ b/extensions/vscode-web-playground/.vscode/tasks.json @@ -0,0 +1,11 @@ +{ + "version": "2.0.0", + "command": "npm", + "type": "shell", + "presentation": { + "reveal": "silent" + }, + "args": ["run", "compile"], + "isBackground": true, + "problemMatcher": "$tsc-watch" +} diff --git a/extensions/vscode-web-playground/.vscodeignore b/extensions/vscode-web-playground/.vscodeignore new file mode 100644 index 00000000000..eb6a48615c7 --- /dev/null +++ b/extensions/vscode-web-playground/.vscodeignore @@ -0,0 +1,6 @@ +.vscode/** +typings/** +**/*.ts +**/*.map +.gitignore +tsconfig.json diff --git a/extensions/vscode-web-playground/package.json b/extensions/vscode-web-playground/package.json new file mode 100644 index 00000000000..46b6102c10e --- /dev/null +++ b/extensions/vscode-web-playground/package.json @@ -0,0 +1,93 @@ +{ + "name": "vscode-web-playground", + "description": "Web playground for VS Code", + "version": "0.0.1", + "publisher": "vscode", + "license": "MIT", + "enableProposedApi": true, + "private": true, + "activationEvents": [ + "onFileSystem:memfs", + "onDebug" + ], + "main": "./out/extension", + "engines": { + "vscode": "^1.25.0" + }, + "contributes": { + "taskDefinitions": [ + { + "type": "custombuildscript", + "required": [ + "flavor" + ], + "properties": { + "flavor": { + "type": "string", + "description": "The build flavor. Should be either '32' or '64'." + }, + "flags": { + "type": "array", + "description": "Additional build flags." + } + } + } + ], + "breakpoints": [ + { + "language": "markdown" + } + ], + "debuggers": [ + { + "type": "mock", + "label": "Mock Debug", + "languages": [ + "markdown" + ], + "configurationAttributes": { + "launch": { + "required": [ + "program" + ], + "properties": { + "program": { + "type": "string", + "description": "Absolute path to a text file.", + "default": "${workspaceFolder}/file.md" + }, + "stopOnEntry": { + "type": "boolean", + "description": "Automatically stop after launch.", + "default": true + }, + "trace": { + "type": "boolean", + "description": "Enable logging of the Debug Adapter Protocol.", + "default": true + } + } + } + }, + "initialConfigurations": [ + { + "type": "mock", + "request": "launch", + "name": "Debug file.md", + "program": "${workspaceFolder}/file.md" + } + ] + } + ] + }, + "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-api-tests ./tsconfig.json" + }, + "devDependencies": { + "@types/mocha": "2.2.43", + "@types/node": "^12.11.7", + "mocha-junit-reporter": "^1.17.0", + "mocha-multi-reporters": "^1.1.7" + } +} diff --git a/extensions/vscode-web-playground/src/extension.ts b/extensions/vscode-web-playground/src/extension.ts new file mode 100644 index 00000000000..5bd7ba43ad6 --- /dev/null +++ b/extensions/vscode-web-playground/src/extension.ts @@ -0,0 +1,4614 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// +// ############################################################################ +// +// ! USED FOR RUNNING VSCODE OUT OF SOURCES FOR WEB ! +// ! DO NOT REMOVE ! +// +// ############################################################################ +// + +import * as vscode from 'vscode'; + +declare const window: unknown; + +const textEncoder = new TextEncoder(); +const SCHEME = 'memfs'; + +export function activate(context: vscode.ExtensionContext) { + if (typeof window !== 'undefined') { // do not run under node.js + const memFs = enableFs(context); + enableProblems(context); + enableSearch(context, memFs); + enableTasks(); + enableDebug(context, memFs); + + vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(`memfs:/sample-folder/large.ts`)); + } +} + +function enableFs(context: vscode.ExtensionContext): MemFS { + const memFs = new MemFS(); + context.subscriptions.push(vscode.workspace.registerFileSystemProvider(SCHEME, memFs, { isCaseSensitive: true })); + + memFs.createDirectory(vscode.Uri.parse(`memfs:/sample-folder/`)); + + // most common files types + memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/large.ts`), textEncoder.encode(getLargeTSFile()), { create: true, overwrite: true }); + memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.txt`), textEncoder.encode('foo'), { create: true, overwrite: true }); + memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.html`), textEncoder.encode('

Hello

'), { create: true, overwrite: true }); + memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.js`), textEncoder.encode('console.log("JavaScript")'), { create: true, overwrite: true }); + memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.json`), textEncoder.encode('{ "json": true }'), { create: true, overwrite: true }); + memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.ts`), textEncoder.encode('console.log("TypeScript")'), { create: true, overwrite: true }); + memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.css`), textEncoder.encode('* { color: green; }'), { create: true, overwrite: true }); + memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.md`), textEncoder.encode(getDebuggableFile()), { create: true, overwrite: true }); + memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.xml`), textEncoder.encode(''), { create: true, overwrite: true }); + memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.py`), textEncoder.encode('import base64, sys; base64.decode(open(sys.argv[1], "rb"), open(sys.argv[2], "wb"))'), { create: true, overwrite: true }); + memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.php`), textEncoder.encode('&1\'); ?>'), { create: true, overwrite: true }); + memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/file.yaml`), textEncoder.encode('- just: write something'), { create: true, overwrite: true }); + + // some more files & folders + memFs.createDirectory(vscode.Uri.parse(`memfs:/sample-folder/folder/`)); + memFs.createDirectory(vscode.Uri.parse(`memfs:/sample-folder/large/`)); + memFs.createDirectory(vscode.Uri.parse(`memfs:/sample-folder/xyz/`)); + memFs.createDirectory(vscode.Uri.parse(`memfs:/sample-folder/xyz/abc`)); + memFs.createDirectory(vscode.Uri.parse(`memfs:/sample-folder/xyz/def`)); + + memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/folder/empty.txt`), new Uint8Array(0), { create: true, overwrite: true }); + memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/folder/empty.foo`), new Uint8Array(0), { create: true, overwrite: true }); + memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/folder/file.ts`), textEncoder.encode('let a:number = true; console.log(a);'), { create: true, overwrite: true }); + memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/large/rnd.foo`), randomData(50000), { create: true, overwrite: true }); + memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/xyz/UPPER.txt`), textEncoder.encode('UPPER'), { create: true, overwrite: true }); + memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/xyz/upper.txt`), textEncoder.encode('upper'), { create: true, overwrite: true }); + memFs.writeFile(vscode.Uri.parse(`memfs:/sample-folder/xyz/def/foo.md`), textEncoder.encode('*MemFS*'), { create: true, overwrite: true }); + + function getLargeTSFile(): string { + return `/// +/// + +module Mankala { + export var storeHouses = [6,13]; + export var svgNS = 'http://www.w3.org/2000/svg'; + + function createSVGRect(r:Rectangle) { + var rect = document.createElementNS(svgNS,'rect'); + rect.setAttribute('x', r.x.toString()); + rect.setAttribute('y', r.y.toString()); + rect.setAttribute('width', r.width.toString()); + rect.setAttribute('height', r.height.toString()); + return rect; + } + + function createSVGEllipse(r:Rectangle) { + var ell = document.createElementNS(svgNS,'ellipse'); + ell.setAttribute('rx',(r.width/2).toString()); + ell.setAttribute('ry',(r.height/2).toString()); + ell.setAttribute('cx',(r.x+r.width/2).toString()); + ell.setAttribute('cy',(r.y+r.height/2).toString()); + return ell; + } + + function createSVGEllipsePolar(angle:number,radius:number,tx:number,ty:number,cxo:number,cyo:number) { + var ell = document.createElementNS(svgNS,'ellipse'); + ell.setAttribute('rx',radius.toString()); + ell.setAttribute('ry',(radius/3).toString()); + ell.setAttribute('cx',cxo.toString()); + ell.setAttribute('cy',cyo.toString()); + var dangle = angle*(180/Math.PI); + ell.setAttribute('transform','rotate('+dangle+','+cxo+','+cyo+') translate('+tx+','+ty+')'); + return ell; + } + + function createSVGInscribedCircle(sq:Square) { + var circle = document.createElementNS(svgNS,'circle'); + circle.setAttribute('r',(sq.length/2).toString()); + circle.setAttribute('cx',(sq.x+(sq.length/2)).toString()); + circle.setAttribute('cy',(sq.y+(sq.length/2)).toString()); + return circle; + } + + export class Position { + + seedCounts:number[]; + startMove:number; + turn:number; + + constructor(seedCounts:number[],startMove:number,turn:number) { + this.seedCounts = seedCounts; + this.startMove = startMove; + this.turn = turn; + } + + score() { + var baseScore = this.seedCounts[storeHouses[1-this.turn]]-this.seedCounts[storeHouses[this.turn]]; + var otherSpaces = homeSpaces[this.turn]; + var sum = 0; + for (var k = 0,len = otherSpaces.length;k0) { + features.clear(); + var len = this.seedCounts.length; + for (var i = 0;i0) { + if (nextSpace==storeHouses[this.turn]) { + features.seedStoredCount++; + } + if ((nextSpace!=storeHouses[1-this.turn])) { + nextSeedCounts[nextSpace]++; + seedCount--; + } + if (seedCount==0) { + if (nextSpace==storeHouses[this.turn]) { + features.turnContinues = true; + } + else { + if ((nextSeedCounts[nextSpace]==1)&& + (nextSpace>=firstHomeSpace[this.turn])&& + (nextSpace<=lastHomeSpace[this.turn])) { + // capture + var capturedSpace = capturedSpaces[nextSpace]; + if (capturedSpace>=0) { + features.spaceCaptured = capturedSpace; + features.capturedCount = nextSeedCounts[capturedSpace]; + nextSeedCounts[capturedSpace] = 0; + nextSeedCounts[storeHouses[this.turn]] += features.capturedCount; + features.seedStoredCount += nextSeedCounts[capturedSpace]; + } + } + } + } + nextSpace = (nextSpace+1)%14; + } + return true; + } + else { + return false; + } + } + } + + export class SeedCoords { + tx:number; + ty:number; + angle:number; + + constructor(tx:number, ty:number, angle:number) { + this.tx = tx; + this.ty = ty; + this.angle = angle; + } + } + + export class DisplayPosition extends Position { + + config:SeedCoords[][]; + + constructor(seedCounts:number[],startMove:number,turn:number) { + super(seedCounts,startMove,turn); + + this.config = []; + + for (var i = 0;i(); + } + } + + + seedCircleRect(rect:Rectangle,seedCount:number,board:Element,seed:number) { + var coords = this.config[seed]; + var sq = rect.inner(0.95).square(); + var cxo = (sq.width/2)+sq.x; + var cyo = (sq.height/2)+sq.y; + var seedNumbers = [5,7,9,11]; + var ringIndex = 0; + var ringRem = seedNumbers[ringIndex]; + var angleDelta = (2*Math.PI)/ringRem; + var angle = angleDelta; + var seedLength = sq.width/(seedNumbers.length<<1); + var crMax = sq.width/2-(seedLength/2); + var pit = createSVGInscribedCircle(sq); + if (seed<7) { + pit.setAttribute('fill','brown'); + } + else { + pit.setAttribute('fill','saddlebrown'); + } + board.appendChild(pit); + var seedsSeen = 0; + while (seedCount > 0) { + if (ringRem == 0) { + ringIndex++; + ringRem = seedNumbers[ringIndex]; + angleDelta = (2*Math.PI)/ringRem; + angle = angleDelta; + } + var tx:number; + var ty:number; + var tangle = angle; + if (coords.length>seedsSeen) { + tx = coords[seedsSeen].tx; + ty = coords[seedsSeen].ty; + tangle = coords[seedsSeen].angle; + } + else { + tx = (Math.random()*crMax)-(crMax/3); + ty = (Math.random()*crMax)-(crMax/3); + coords[seedsSeen] = new SeedCoords(tx,ty,angle); + } + var ell = createSVGEllipsePolar(tangle,seedLength,tx,ty,cxo,cyo); + board.appendChild(ell); + angle += angleDelta; + ringRem--; + seedCount--; + seedsSeen++; + } + } + + toCircleSVG() { + var seedDivisions = 14; + var board = document.createElementNS(svgNS,'svg'); + var boardRect = new Rectangle(0,0,1800,800); + board.setAttribute('width','1800'); + board.setAttribute('height','800'); + var whole = createSVGRect(boardRect); + whole.setAttribute('fill','tan'); + board.appendChild(whole); + var labPlayLab = boardRect.proportionalSplitVert(20,760,20); + var playSurface = labPlayLab[1]; + var storeMainStore = playSurface.proportionalSplitHoriz(8,48,8); + var mainPair = storeMainStore[1].subDivideVert(2); + var playerRects = [mainPair[0].subDivideHoriz(6), mainPair[1].subDivideHoriz(6)]; + // reverse top layer because storehouse on left + for (var k = 0;k<3;k++) { + var temp = playerRects[0][k]; + playerRects[0][k] = playerRects[0][5-k]; + playerRects[0][5-k] = temp; + } + var storehouses = [storeMainStore[0],storeMainStore[2]]; + var playerSeeds = this.seedCounts.length>>1; + for (var i = 0;i<2;i++) { + var player = playerRects[i]; + var storehouse = storehouses[i]; + var r:Rectangle; + for (var j = 0;j(); + } + } + } + return board; + } + } +} +`; + } + + function getDebuggableFile(): string { + return `# VS Code Mock Debug + +This is a starter sample for developing VS Code debug adapters. + +**Mock Debug** simulates a debug adapter for Visual Studio Code. +It supports *step*, *continue*, *breakpoints*, *exceptions*, and +*variable access* but it is not connected to any real debugger. + +The sample is meant as an educational piece showing how to implement a debug +adapter for VS Code. It can be used as a starting point for developing a real adapter. + +More information about how to develop a new debug adapter can be found +[here](https://code.visualstudio.com/docs/extensions/example-debuggers). +Or discuss debug adapters on Gitter: +[![Gitter Chat](https://img.shields.io/badge/chat-online-brightgreen.svg)](https://gitter.im/Microsoft/vscode) + +## Using Mock Debug + +* Install the **Mock Debug** extension in VS Code. +* Create a new 'program' file 'readme.md' and enter several lines of arbitrary text. +* Switch to the debug viewlet and press the gear dropdown. +* Select the debug environment "Mock Debug". +* Press the green 'play' button to start debugging. + +You can now 'step through' the 'readme.md' file, set and hit breakpoints, and run into exceptions (if the word exception appears in a line). + +![Mock Debug](images/mock-debug.gif) + +## Build and Run + +[![build status](https://travis-ci.org/Microsoft/vscode-mock-debug.svg?branch=master)](https://travis-ci.org/Microsoft/vscode-mock-debug) +[![build status](https://ci.appveyor.com/api/projects/status/empmw5q1tk6h1fly/branch/master?svg=true)](https://ci.appveyor.com/project/weinand/vscode-mock-debug) + + +* Clone the project [https://github.com/Microsoft/vscode-mock-debug.git](https://github.com/Microsoft/vscode-mock-debug.git) +* Open the project folder in VS Code. +* Press 'F5' to build and launch Mock Debug in another VS Code window. In that window: + * Open a new workspace, create a new 'program' file 'readme.md' and enter several lines of arbitrary text. + * Switch to the debug viewlet and press the gear dropdown. + * Select the debug environment "Mock Debug". + * Press 'F5' to start debugging.`; + } + + return memFs; +} + +function randomData(lineCnt: number, lineLen = 155): Uint8Array { + let lines: string[] = []; + for (let i = 0; i < lineCnt; i++) { + let line = ''; + while (line.length < lineLen) { + line += Math.random().toString(2 + (i % 34)).substr(2); + } + lines.push(line.substr(0, lineLen)); + } + return textEncoder.encode(lines.join('\n')); +} + +function enableProblems(context: vscode.ExtensionContext): void { + const collection = vscode.languages.createDiagnosticCollection('test'); + if (vscode.window.activeTextEditor) { + updateDiagnostics(vscode.window.activeTextEditor.document, collection); + } + context.subscriptions.push(vscode.window.onDidChangeActiveTextEditor(editor => { + if (editor) { + updateDiagnostics(editor.document, collection); + } + })); +} + +function updateDiagnostics(document: vscode.TextDocument, collection: vscode.DiagnosticCollection): void { + if (document && document.fileName === '/sample-folder/large.ts') { + collection.set(document.uri, [{ + code: '', + message: 'cannot assign twice to immutable variable `storeHouses`', + range: new vscode.Range(new vscode.Position(4, 12), new vscode.Position(4, 32)), + severity: vscode.DiagnosticSeverity.Error, + source: '', + relatedInformation: [ + new vscode.DiagnosticRelatedInformation(new vscode.Location(document.uri, new vscode.Range(new vscode.Position(1, 8), new vscode.Position(1, 9))), 'first assignment to `x`') + ] + }, { + code: '', + message: 'function does not follow naming conventions', + range: new vscode.Range(new vscode.Position(7, 10), new vscode.Position(7, 23)), + severity: vscode.DiagnosticSeverity.Warning, + source: '' + }]); + } else { + collection.clear(); + } +} + +function enableSearch(context: vscode.ExtensionContext, memFs: MemFS): void { + context.subscriptions.push(vscode.workspace.registerFileSearchProvider(SCHEME, memFs)); + context.subscriptions.push(vscode.workspace.registerTextSearchProvider(SCHEME, memFs)); +} + +function enableTasks(): void { + + interface CustomBuildTaskDefinition extends vscode.TaskDefinition { + /** + * The build flavor. Should be either '32' or '64'. + */ + flavor: string; + + /** + * Additional build flags + */ + flags?: string[]; + } + + class CustomBuildTaskProvider implements vscode.TaskProvider { + static CustomBuildScriptType: string = 'custombuildscript'; + private tasks: vscode.Task[] | undefined; + + // We use a CustomExecution task when state needs to be shared accross runs of the task or when + // the task requires use of some VS Code API to run. + // If you don't need to share state between runs and if you don't need to execute VS Code API in your task, + // then a simple ShellExecution or ProcessExecution should be enough. + // Since our build has this shared state, the CustomExecution is used below. + private sharedState: string | undefined; + + constructor(private workspaceRoot: string) { } + + public async provideTasks(): Promise { + return this.getTasks(); + } + + public resolveTask(_task: vscode.Task): vscode.Task | undefined { + const flavor: string = _task.definition.flavor; + if (flavor) { + const definition: CustomBuildTaskDefinition = _task.definition; + return this.getTask(definition.flavor, definition.flags ? definition.flags : [], definition); + } + return undefined; + } + + private getTasks(): vscode.Task[] { + if (this.tasks !== undefined) { + return this.tasks; + } + // In our fictional build, we have two build flavors + const flavors: string[] = ['32', '64']; + // Each flavor can have some options. + const flags: string[][] = [['watch', 'incremental'], ['incremental'], []]; + + this.tasks = []; + flavors.forEach(flavor => { + flags.forEach(flagGroup => { + this.tasks!.push(this.getTask(flavor, flagGroup)); + }); + }); + return this.tasks; + } + + private getTask(flavor: string, flags: string[], definition?: CustomBuildTaskDefinition): vscode.Task { + if (definition === undefined) { + definition = { + type: CustomBuildTaskProvider.CustomBuildScriptType, + flavor, + flags + }; + } + return new vscode.Task2(definition, vscode.TaskScope.Workspace, `${flavor} ${flags.join(' ')}`, + CustomBuildTaskProvider.CustomBuildScriptType, new vscode.CustomExecution(async (): Promise => { + // When the task is executed, this callback will run. Here, we setup for running the task. + return new CustomBuildTaskTerminal(this.workspaceRoot, flavor, flags, () => this.sharedState, (state: string) => this.sharedState = state); + })); + } + } + + class CustomBuildTaskTerminal implements vscode.Pseudoterminal { + private writeEmitter = new vscode.EventEmitter(); + onDidWrite: vscode.Event = this.writeEmitter.event; + private closeEmitter = new vscode.EventEmitter(); + onDidClose?: vscode.Event = this.closeEmitter.event; + + private fileWatcher: vscode.FileSystemWatcher | undefined; + + constructor(private workspaceRoot: string, _flavor: string, private flags: string[], private getSharedState: () => string | undefined, private setSharedState: (state: string) => void) { + } + + open(_initialDimensions: vscode.TerminalDimensions | undefined): void { + // At this point we can start using the terminal. + if (this.flags.indexOf('watch') > -1) { + let pattern = this.workspaceRoot + '/customBuildFile'; + this.fileWatcher = vscode.workspace.createFileSystemWatcher(pattern); + this.fileWatcher.onDidChange(() => this.doBuild()); + this.fileWatcher.onDidCreate(() => this.doBuild()); + this.fileWatcher.onDidDelete(() => this.doBuild()); + } + this.doBuild(); + } + + close(): void { + // The terminal has been closed. Shutdown the build. + if (this.fileWatcher) { + this.fileWatcher.dispose(); + } + } + + private async doBuild(): Promise { + return new Promise((resolve) => { + this.writeEmitter.fire('Starting build...\r\n'); + let isIncremental = this.flags.indexOf('incremental') > -1; + if (isIncremental) { + if (this.getSharedState()) { + this.writeEmitter.fire('Using last build results: ' + this.getSharedState() + '\r\n'); + } else { + isIncremental = false; + this.writeEmitter.fire('No result from last build. Doing full build.\r\n'); + } + } + + // Since we don't actually build anything in this example set a timeout instead. + setTimeout(() => { + const date = new Date(); + this.setSharedState(date.toTimeString() + ' ' + date.toDateString()); + this.writeEmitter.fire('Build complete.\r\n\r\n'); + if (this.flags.indexOf('watch') === -1) { + this.closeEmitter.fire(); + resolve(); + } + }, isIncremental ? 1000 : 4000); + }); + } + } + + vscode.tasks.registerTaskProvider(CustomBuildTaskProvider.CustomBuildScriptType, new CustomBuildTaskProvider(vscode.workspace.rootPath!)); +} + +export class File implements vscode.FileStat { + + type: vscode.FileType; + ctime: number; + mtime: number; + size: number; + + name: string; + data?: Uint8Array; + + constructor(public uri: vscode.Uri, name: string) { + this.type = vscode.FileType.File; + this.ctime = Date.now(); + this.mtime = Date.now(); + this.size = 0; + this.name = name; + } +} + +export class Directory implements vscode.FileStat { + + type: vscode.FileType; + ctime: number; + mtime: number; + size: number; + + name: string; + entries: Map; + + constructor(public uri: vscode.Uri, name: string) { + this.type = vscode.FileType.Directory; + this.ctime = Date.now(); + this.mtime = Date.now(); + this.size = 0; + this.name = name; + this.entries = new Map(); + } +} + +export type Entry = File | Directory; + +export class MemFS implements vscode.FileSystemProvider, vscode.FileSearchProvider, vscode.TextSearchProvider { + + root = new Directory(vscode.Uri.parse('memfs:/'), ''); + + // --- manage file metadata + + stat(uri: vscode.Uri): vscode.FileStat { + return this._lookup(uri, false); + } + + readDirectory(uri: vscode.Uri): [string, vscode.FileType][] { + const entry = this._lookupAsDirectory(uri, false); + let result: [string, vscode.FileType][] = []; + for (const [name, child] of entry.entries) { + result.push([name, child.type]); + } + return result; + } + + // --- manage file contents + + readFile(uri: vscode.Uri): Uint8Array { + const data = this._lookupAsFile(uri, false).data; + if (data) { + return data; + } + throw vscode.FileSystemError.FileNotFound(); + } + + writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean, overwrite: boolean }): void { + let basename = this._basename(uri.path); + let parent = this._lookupParentDirectory(uri); + let entry = parent.entries.get(basename); + if (entry instanceof Directory) { + throw vscode.FileSystemError.FileIsADirectory(uri); + } + if (!entry && !options.create) { + throw vscode.FileSystemError.FileNotFound(uri); + } + if (entry && options.create && !options.overwrite) { + throw vscode.FileSystemError.FileExists(uri); + } + if (!entry) { + entry = new File(uri, basename); + parent.entries.set(basename, entry); + this._fireSoon({ type: vscode.FileChangeType.Created, uri }); + } + entry.mtime = Date.now(); + entry.size = content.byteLength; + entry.data = content; + + this._fireSoon({ type: vscode.FileChangeType.Changed, uri }); + } + + // --- manage files/folders + + rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean }): void { + if (!options.overwrite && this._lookup(newUri, true)) { + throw vscode.FileSystemError.FileExists(newUri); + } + + let entry = this._lookup(oldUri, false); + let oldParent = this._lookupParentDirectory(oldUri); + + let newParent = this._lookupParentDirectory(newUri); + let newName = this._basename(newUri.path); + + oldParent.entries.delete(entry.name); + entry.name = newName; + newParent.entries.set(newName, entry); + + this._fireSoon( + { type: vscode.FileChangeType.Deleted, uri: oldUri }, + { type: vscode.FileChangeType.Created, uri: newUri } + ); + } + + delete(uri: vscode.Uri): void { + let dirname = uri.with({ path: this._dirname(uri.path) }); + let basename = this._basename(uri.path); + let parent = this._lookupAsDirectory(dirname, false); + if (!parent.entries.has(basename)) { + throw vscode.FileSystemError.FileNotFound(uri); + } + parent.entries.delete(basename); + parent.mtime = Date.now(); + parent.size -= 1; + this._fireSoon({ type: vscode.FileChangeType.Changed, uri: dirname }, { uri, type: vscode.FileChangeType.Deleted }); + } + + createDirectory(uri: vscode.Uri): void { + let basename = this._basename(uri.path); + let dirname = uri.with({ path: this._dirname(uri.path) }); + let parent = this._lookupAsDirectory(dirname, false); + + let entry = new Directory(uri, basename); + parent.entries.set(entry.name, entry); + parent.mtime = Date.now(); + parent.size += 1; + this._fireSoon({ type: vscode.FileChangeType.Changed, uri: dirname }, { type: vscode.FileChangeType.Created, uri }); + } + + // --- lookup + + private _lookup(uri: vscode.Uri, silent: false): Entry; + private _lookup(uri: vscode.Uri, silent: boolean): Entry | undefined; + private _lookup(uri: vscode.Uri, silent: boolean): Entry | undefined { + let parts = uri.path.split('/'); + let entry: Entry = this.root; + for (const part of parts) { + if (!part) { + continue; + } + let child: Entry | undefined; + if (entry instanceof Directory) { + child = entry.entries.get(part); + } + if (!child) { + if (!silent) { + throw vscode.FileSystemError.FileNotFound(uri); + } else { + return undefined; + } + } + entry = child; + } + return entry; + } + + private _lookupAsDirectory(uri: vscode.Uri, silent: boolean): Directory { + let entry = this._lookup(uri, silent); + if (entry instanceof Directory) { + return entry; + } + throw vscode.FileSystemError.FileNotADirectory(uri); + } + + private _lookupAsFile(uri: vscode.Uri, silent: boolean): File { + let entry = this._lookup(uri, silent); + if (entry instanceof File) { + return entry; + } + throw vscode.FileSystemError.FileIsADirectory(uri); + } + + private _lookupParentDirectory(uri: vscode.Uri): Directory { + const dirname = uri.with({ path: this._dirname(uri.path) }); + return this._lookupAsDirectory(dirname, false); + } + + // --- manage file events + + private _emitter = new vscode.EventEmitter(); + private _bufferedEvents: vscode.FileChangeEvent[] = []; + private _fireSoonHandle?: NodeJS.Timer; + + readonly onDidChangeFile: vscode.Event = this._emitter.event; + + watch(_resource: vscode.Uri): vscode.Disposable { + // ignore, fires for all changes... + return new vscode.Disposable(() => { }); + } + + private _fireSoon(...events: vscode.FileChangeEvent[]): void { + this._bufferedEvents.push(...events); + + if (this._fireSoonHandle) { + clearTimeout(this._fireSoonHandle); + } + + this._fireSoonHandle = setTimeout(() => { + this._emitter.fire(this._bufferedEvents); + this._bufferedEvents.length = 0; + }, 5); + } + + // --- path utils + + private _basename(path: string): string { + path = this._rtrim(path, '/'); + if (!path) { + return ''; + } + + return path.substr(path.lastIndexOf('/') + 1); + } + + private _dirname(path: string): string { + path = this._rtrim(path, '/'); + if (!path) { + return '/'; + } + + return path.substr(0, path.lastIndexOf('/')); + } + + private _rtrim(haystack: string, needle: string): string { + if (!haystack || !needle) { + return haystack; + } + + const needleLen = needle.length, + haystackLen = haystack.length; + + if (needleLen === 0 || haystackLen === 0) { + return haystack; + } + + let offset = haystackLen, + idx = -1; + + while (true) { + idx = haystack.lastIndexOf(needle, offset - 1); + if (idx === -1 || idx + needleLen !== offset) { + break; + } + if (idx === 0) { + return ''; + } + offset = idx; + } + + return haystack.substring(0, offset); + } + + private _getFiles(): Set { + const files = new Set(); + + this._doGetFiles(this.root, files); + + return files; + } + + private _doGetFiles(dir: Directory, files: Set): void { + dir.entries.forEach(entry => { + if (entry instanceof File) { + files.add(entry); + } else { + this._doGetFiles(entry, files); + } + }); + } + + private _convertSimple2RegExpPattern(pattern: string): string { + return pattern.replace(/[\-\\\{\}\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&').replace(/[\*]/g, '.*'); + } + + // --- search provider + + provideFileSearchResults(query: vscode.FileSearchQuery, _options: vscode.FileSearchOptions, _token: vscode.CancellationToken): vscode.ProviderResult { + return this._findFiles(query.pattern); + } + + private _findFiles(query: string | undefined): vscode.Uri[] { + const files = this._getFiles(); + const result: vscode.Uri[] = []; + + const pattern = query ? new RegExp(this._convertSimple2RegExpPattern(query)) : null; + + for (const file of files) { + if (!pattern || pattern.exec(file.name)) { + result.push(file.uri); + } + } + + return result; + } + + private _textDecoder = new TextDecoder(); + + provideTextSearchResults(query: vscode.TextSearchQuery, options: vscode.TextSearchOptions, progress: vscode.Progress, _token: vscode.CancellationToken) { + const result: vscode.TextSearchComplete = { limitHit: false }; + + const files = this._findFiles(options.includes[0]); + if (files) { + for (const file of files) { + const content = this._textDecoder.decode(this.readFile(file)); + + const lines = content.split('\n'); + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const index = line.indexOf(query.pattern); + if (index !== -1) { + progress.report({ + uri: file, + ranges: new vscode.Range(new vscode.Position(i, index), new vscode.Position(i, index + query.pattern.length)), + preview: { + text: line, + matches: new vscode.Range(new vscode.Position(0, index), new vscode.Position(0, index + query.pattern.length)) + } + }); + } + } + } + } + + return result; + } +} + +//--------------------------------------------------------------------------- +// DEBUG +//--------------------------------------------------------------------------- + +function enableDebug(context: vscode.ExtensionContext, memFs: MemFS): void { + context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('mock', new MockConfigurationProvider())); + context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory('mock', new MockDebugAdapterDescriptorFactory(memFs))); +} + +/** + * Declaration module describing the VS Code debug protocol. + * Auto-generated from json schema. Do not edit manually. + */ +declare module DebugProtocol { + + /** Base class of requests, responses, and events. */ + export interface ProtocolMessage { + /** Sequence number (also known as message ID). For protocol messages of type 'request' this ID can be used to cancel the request. */ + seq: number; + /** Message type. + Values: 'request', 'response', 'event', etc. + */ + type: string; + } + + /** A client or debug adapter initiated request. */ + export interface Request extends ProtocolMessage { + // type: 'request'; + /** The command to execute. */ + command: string; + /** Object containing arguments for the command. */ + arguments?: any; + } + + /** A debug adapter initiated event. */ + export interface Event extends ProtocolMessage { + // type: 'event'; + /** Type of event. */ + event: string; + /** Event-specific information. */ + body?: any; + } + + /** Response for a request. */ + export interface Response extends ProtocolMessage { + // type: 'response'; + /** Sequence number of the corresponding request. */ + request_seq: number; + /** Outcome of the request. + If true, the request was successful and the 'body' attribute may contain the result of the request. + If the value is false, the attribute 'message' contains the error in short form and the 'body' may contain additional information (see 'ErrorResponse.body.error'). + */ + success: boolean; + /** The command requested. */ + command: string; + /** Contains the raw error in short form if 'success' is false. + This raw error might be interpreted by the frontend and is not shown in the UI. + Some predefined values exist. + Values: + 'cancelled': request was cancelled. + etc. + */ + message?: string; + /** Contains request result if success is true and optional error details if success is false. */ + body?: any; + } + + /** On error (whenever 'success' is false), the body can provide more details. */ + export interface ErrorResponse extends Response { + body: { + /** An optional, structured error message. */ + error?: Message; + }; + } + + /** Cancel request; value of command field is 'cancel'. + The 'cancel' request is used by the frontend to indicate that it is no longer interested in the result produced by a specific request issued earlier. + This request has a hint characteristic: a debug adapter can only be expected to make a 'best effort' in honouring this request but there are no guarantees. + The 'cancel' request may return an error if it could not cancel an operation but a frontend should refrain from presenting this error to end users. + A frontend client should only call this request if the capability 'supportsCancelRequest' is true. + The request that got canceled still needs to send a response back. + This can either be a normal result ('success' attribute true) or an error response ('success' attribute false and the 'message' set to 'cancelled'). + Returning partial results from a cancelled request is possible but please note that a frontend client has no generic way for detecting that a response is partial or not. + */ + export interface CancelRequest extends Request { + // command: 'cancel'; + arguments?: CancelArguments; + } + + /** Arguments for 'cancel' request. */ + export interface CancelArguments { + /** The ID (attribute 'seq') of the request to cancel. */ + requestId?: number; + } + + /** Response to 'cancel' request. This is just an acknowledgement, so no body field is required. */ + export interface CancelResponse extends Response { + } + + /** Event message for 'initialized' event type. + This event indicates that the debug adapter is ready to accept configuration requests (e.g. SetBreakpointsRequest, SetExceptionBreakpointsRequest). + A debug adapter is expected to send this event when it is ready to accept configuration requests (but not before the 'initialize' request has finished). + The sequence of events/requests is as follows: + - adapters sends 'initialized' event (after the 'initialize' request has returned) + - frontend sends zero or more 'setBreakpoints' requests + - frontend sends one 'setFunctionBreakpoints' request + - frontend sends a 'setExceptionBreakpoints' request if one or more 'exceptionBreakpointFilters' have been defined (or if 'supportsConfigurationDoneRequest' is not defined or false) + - frontend sends other future configuration requests + - frontend sends one 'configurationDone' request to indicate the end of the configuration. + */ + export interface InitializedEvent extends Event { + // event: 'initialized'; + } + + /** Event message for 'stopped' event type. + The event indicates that the execution of the debuggee has stopped due to some condition. + This can be caused by a break point previously set, a stepping action has completed, by executing a debugger statement etc. + */ + export interface StoppedEvent extends Event { + // event: 'stopped'; + body: { + /** The reason for the event. + For backward compatibility this string is shown in the UI if the 'description' attribute is missing (but it must not be translated). + Values: 'step', 'breakpoint', 'exception', 'pause', 'entry', 'goto', 'function breakpoint', 'data breakpoint', etc. + */ + reason: string; + /** The full reason for the event, e.g. 'Paused on exception'. This string is shown in the UI as is and must be translated. */ + description?: string; + /** The thread which was stopped. */ + threadId?: number; + /** A value of true hints to the frontend that this event should not change the focus. */ + preserveFocusHint?: boolean; + /** Additional information. E.g. if reason is 'exception', text contains the exception name. This string is shown in the UI. */ + text?: string; + /** If 'allThreadsStopped' is true, a debug adapter can announce that all threads have stopped. + - The client should use this information to enable that all threads can be expanded to access their stacktraces. + - If the attribute is missing or false, only the thread with the given threadId can be expanded. + */ + allThreadsStopped?: boolean; + }; + } + + /** Event message for 'continued' event type. + The event indicates that the execution of the debuggee has continued. + Please note: a debug adapter is not expected to send this event in response to a request that implies that execution continues, e.g. 'launch' or 'continue'. + It is only necessary to send a 'continued' event if there was no previous request that implied this. + */ + export interface ContinuedEvent extends Event { + // event: 'continued'; + body: { + /** The thread which was continued. */ + threadId: number; + /** If 'allThreadsContinued' is true, a debug adapter can announce that all threads have continued. */ + allThreadsContinued?: boolean; + }; + } + + /** Event message for 'exited' event type. + The event indicates that the debuggee has exited and returns its exit code. + */ + export interface ExitedEvent extends Event { + // event: 'exited'; + body: { + /** The exit code returned from the debuggee. */ + exitCode: number; + }; + } + + /** Event message for 'terminated' event type. + The event indicates that debugging of the debuggee has terminated. This does **not** mean that the debuggee itself has exited. + */ + export interface TerminatedEvent extends Event { + // event: 'terminated'; + body?: { + /** A debug adapter may set 'restart' to true (or to an arbitrary object) to request that the front end restarts the session. + The value is not interpreted by the client and passed unmodified as an attribute '__restart' to the 'launch' and 'attach' requests. + */ + restart?: any; + }; + } + + /** Event message for 'thread' event type. + The event indicates that a thread has started or exited. + */ + export interface ThreadEvent extends Event { + // event: 'thread'; + body: { + /** The reason for the event. + Values: 'started', 'exited', etc. + */ + reason: string; + /** The identifier of the thread. */ + threadId: number; + }; + } + + /** Event message for 'output' event type. + The event indicates that the target has produced some output. + */ + export interface OutputEvent extends Event { + // event: 'output'; + body: { + /** The output category. If not specified, 'console' is assumed. + Values: 'console', 'stdout', 'stderr', 'telemetry', etc. + */ + category?: string; + /** The output to report. */ + output: string; + /** If an attribute 'variablesReference' exists and its value is > 0, the output contains objects which can be retrieved by passing 'variablesReference' to the 'variables' request. The value should be less than or equal to 2147483647 (2^31 - 1). */ + variablesReference?: number; + /** An optional source location where the output was produced. */ + source?: Source; + /** An optional source location line where the output was produced. */ + line?: number; + /** An optional source location column where the output was produced. */ + column?: number; + /** Optional data to report. For the 'telemetry' category the data will be sent to telemetry, for the other categories the data is shown in JSON format. */ + data?: any; + }; + } + + /** Event message for 'breakpoint' event type. + The event indicates that some information about a breakpoint has changed. + */ + export interface BreakpointEvent extends Event { + // event: 'breakpoint'; + body: { + /** The reason for the event. + Values: 'changed', 'new', 'removed', etc. + */ + reason: string; + /** The 'id' attribute is used to find the target breakpoint and the other attributes are used as the new values. */ + breakpoint: Breakpoint; + }; + } + + /** Event message for 'module' event type. + The event indicates that some information about a module has changed. + */ + export interface ModuleEvent extends Event { + // event: 'module'; + body: { + /** The reason for the event. */ + reason: 'new' | 'changed' | 'removed'; + /** The new, changed, or removed module. In case of 'removed' only the module id is used. */ + module: Module; + }; + } + + /** Event message for 'loadedSource' event type. + The event indicates that some source has been added, changed, or removed from the set of all loaded sources. + */ + export interface LoadedSourceEvent extends Event { + // event: 'loadedSource'; + body: { + /** The reason for the event. */ + reason: 'new' | 'changed' | 'removed'; + /** The new, changed, or removed source. */ + source: Source; + }; + } + + /** Event message for 'process' event type. + The event indicates that the debugger has begun debugging a new process. Either one that it has launched, or one that it has attached to. + */ + export interface ProcessEvent extends Event { + // event: 'process'; + body: { + /** The logical name of the process. This is usually the full path to process's executable file. Example: /home/example/myproj/program.js. */ + name: string; + /** The system process id of the debugged process. This property will be missing for non-system processes. */ + systemProcessId?: number; + /** If true, the process is running on the same computer as the debug adapter. */ + isLocalProcess?: boolean; + /** Describes how the debug engine started debugging this process. + 'launch': Process was launched under the debugger. + 'attach': Debugger attached to an existing process. + 'attachForSuspendedLaunch': A project launcher component has launched a new process in a suspended state and then asked the debugger to attach. + */ + startMethod?: 'launch' | 'attach' | 'attachForSuspendedLaunch'; + /** The size of a pointer or address for this process, in bits. This value may be used by clients when formatting addresses for display. */ + pointerSize?: number; + }; + } + + /** Event message for 'capabilities' event type. + The event indicates that one or more capabilities have changed. + Since the capabilities are dependent on the frontend and its UI, it might not be possible to change that at random times (or too late). + Consequently this event has a hint characteristic: a frontend can only be expected to make a 'best effort' in honouring individual capabilities but there are no guarantees. + Only changed capabilities need to be included, all other capabilities keep their values. + */ + export interface CapabilitiesEvent extends Event { + // event: 'capabilities'; + body: { + /** The set of updated capabilities. */ + capabilities: Capabilities; + }; + } + + /** RunInTerminal request; value of command field is 'runInTerminal'. + This request is sent from the debug adapter to the client to run a command in a terminal. This is typically used to launch the debuggee in a terminal provided by the client. + */ + export interface RunInTerminalRequest extends Request { + // command: 'runInTerminal'; + arguments: RunInTerminalRequestArguments; + } + + /** Arguments for 'runInTerminal' request. */ + export interface RunInTerminalRequestArguments { + /** What kind of terminal to launch. */ + kind?: 'integrated' | 'external'; + /** Optional title of the terminal. */ + title?: string; + /** Working directory of the command. */ + cwd: string; + /** List of arguments. The first argument is the command to run. */ + args: string[]; + /** Environment key-value pairs that are added to or removed from the default environment. */ + env?: { [key: string]: string | null; }; + } + + /** Response to 'runInTerminal' request. */ + export interface RunInTerminalResponse extends Response { + body: { + /** The process ID. The value should be less than or equal to 2147483647 (2^31 - 1). */ + processId?: number; + /** The process ID of the terminal shell. The value should be less than or equal to 2147483647 (2^31 - 1). */ + shellProcessId?: number; + }; + } + + /** Initialize request; value of command field is 'initialize'. + The 'initialize' request is sent as the first request from the client to the debug adapter in order to configure it with client capabilities and to retrieve capabilities from the debug adapter. + Until the debug adapter has responded to with an 'initialize' response, the client must not send any additional requests or events to the debug adapter. In addition the debug adapter is not allowed to send any requests or events to the client until it has responded with an 'initialize' response. + The 'initialize' request may only be sent once. + */ + export interface InitializeRequest extends Request { + // command: 'initialize'; + arguments: InitializeRequestArguments; + } + + /** Arguments for 'initialize' request. */ + export interface InitializeRequestArguments { + /** The ID of the (frontend) client using this adapter. */ + clientID?: string; + /** The human readable name of the (frontend) client using this adapter. */ + clientName?: string; + /** The ID of the debug adapter. */ + adapterID: string; + /** The ISO-639 locale of the (frontend) client using this adapter, e.g. en-US or de-CH. */ + locale?: string; + /** If true all line numbers are 1-based (default). */ + linesStartAt1?: boolean; + /** If true all column numbers are 1-based (default). */ + columnsStartAt1?: boolean; + /** Determines in what format paths are specified. The default is 'path', which is the native format. + Values: 'path', 'uri', etc. + */ + pathFormat?: string; + /** Client supports the optional type attribute for variables. */ + supportsVariableType?: boolean; + /** Client supports the paging of variables. */ + supportsVariablePaging?: boolean; + /** Client supports the runInTerminal request. */ + supportsRunInTerminalRequest?: boolean; + /** Client supports memory references. */ + supportsMemoryReferences?: boolean; + } + + /** Response to 'initialize' request. */ + export interface InitializeResponse extends Response { + /** The capabilities of this debug adapter. */ + body?: Capabilities; + } + + /** ConfigurationDone request; value of command field is 'configurationDone'. + The client of the debug protocol must send this request at the end of the sequence of configuration requests (which was started by the 'initialized' event). + */ + export interface ConfigurationDoneRequest extends Request { + // command: 'configurationDone'; + arguments?: ConfigurationDoneArguments; + } + + /** Arguments for 'configurationDone' request. */ + export interface ConfigurationDoneArguments { + } + + /** Response to 'configurationDone' request. This is just an acknowledgement, so no body field is required. */ + export interface ConfigurationDoneResponse extends Response { + } + + /** Launch request; value of command field is 'launch'. + The launch request is sent from the client to the debug adapter to start the debuggee with or without debugging (if 'noDebug' is true). Since launching is debugger/runtime specific, the arguments for this request are not part of this specification. + */ + export interface LaunchRequest extends Request { + // command: 'launch'; + arguments: LaunchRequestArguments; + } + + /** Arguments for 'launch' request. Additional attributes are implementation specific. */ + export interface LaunchRequestArguments { + /** If noDebug is true the launch request should launch the program without enabling debugging. */ + noDebug?: boolean; + /** Optional data from the previous, restarted session. + The data is sent as the 'restart' attribute of the 'terminated' event. + The client should leave the data intact. + */ + __restart?: any; + } + + /** Response to 'launch' request. This is just an acknowledgement, so no body field is required. */ + export interface LaunchResponse extends Response { + } + + /** Attach request; value of command field is 'attach'. + The attach request is sent from the client to the debug adapter to attach to a debuggee that is already running. Since attaching is debugger/runtime specific, the arguments for this request are not part of this specification. + */ + export interface AttachRequest extends Request { + // command: 'attach'; + arguments: AttachRequestArguments; + } + + /** Arguments for 'attach' request. Additional attributes are implementation specific. */ + export interface AttachRequestArguments { + /** Optional data from the previous, restarted session. + The data is sent as the 'restart' attribute of the 'terminated' event. + The client should leave the data intact. + */ + __restart?: any; + } + + /** Response to 'attach' request. This is just an acknowledgement, so no body field is required. */ + export interface AttachResponse extends Response { + } + + /** Restart request; value of command field is 'restart'. + Restarts a debug session. If the capability 'supportsRestartRequest' is missing or has the value false, + the client will implement 'restart' by terminating the debug adapter first and then launching it anew. + A debug adapter can override this default behaviour by implementing a restart request + and setting the capability 'supportsRestartRequest' to true. + */ + export interface RestartRequest extends Request { + // command: 'restart'; + arguments?: RestartArguments; + } + + /** Arguments for 'restart' request. */ + export interface RestartArguments { + } + + /** Response to 'restart' request. This is just an acknowledgement, so no body field is required. */ + export interface RestartResponse extends Response { + } + + /** Disconnect request; value of command field is 'disconnect'. + The 'disconnect' request is sent from the client to the debug adapter in order to stop debugging. It asks the debug adapter to disconnect from the debuggee and to terminate the debug adapter. If the debuggee has been started with the 'launch' request, the 'disconnect' request terminates the debuggee. If the 'attach' request was used to connect to the debuggee, 'disconnect' does not terminate the debuggee. This behavior can be controlled with the 'terminateDebuggee' argument (if supported by the debug adapter). + */ + export interface DisconnectRequest extends Request { + // command: 'disconnect'; + arguments?: DisconnectArguments; + } + + /** Arguments for 'disconnect' request. */ + export interface DisconnectArguments { + /** A value of true indicates that this 'disconnect' request is part of a restart sequence. */ + restart?: boolean; + /** Indicates whether the debuggee should be terminated when the debugger is disconnected. + If unspecified, the debug adapter is free to do whatever it thinks is best. + A client can only rely on this attribute being properly honored if a debug adapter returns true for the 'supportTerminateDebuggee' capability. + */ + terminateDebuggee?: boolean; + } + + /** Response to 'disconnect' request. This is just an acknowledgement, so no body field is required. */ + export interface DisconnectResponse extends Response { + } + + /** Terminate request; value of command field is 'terminate'. + The 'terminate' request is sent from the client to the debug adapter in order to give the debuggee a chance for terminating itself. + */ + export interface TerminateRequest extends Request { + // command: 'terminate'; + arguments?: TerminateArguments; + } + + /** Arguments for 'terminate' request. */ + export interface TerminateArguments { + /** A value of true indicates that this 'terminate' request is part of a restart sequence. */ + restart?: boolean; + } + + /** Response to 'terminate' request. This is just an acknowledgement, so no body field is required. */ + export interface TerminateResponse extends Response { + } + + /** BreakpointLocations request; value of command field is 'breakpointLocations'. + The 'breakpointLocations' request returns all possible locations for source breakpoints in a given range. + */ + export interface BreakpointLocationsRequest extends Request { + // command: 'breakpointLocations'; + arguments?: BreakpointLocationsArguments; + } + + /** Arguments for 'breakpointLocations' request. */ + export interface BreakpointLocationsArguments { + /** The source location of the breakpoints; either 'source.path' or 'source.reference' must be specified. */ + source: Source; + /** Start line of range to search possible breakpoint locations in. If only the line is specified, the request returns all possible locations in that line. */ + line: number; + /** Optional start column of range to search possible breakpoint locations in. If no start column is given, the first column in the start line is assumed. */ + column?: number; + /** Optional end line of range to search possible breakpoint locations in. If no end line is given, then the end line is assumed to be the start line. */ + endLine?: number; + /** Optional end column of range to search possible breakpoint locations in. If no end column is given, then it is assumed to be in the last column of the end line. */ + endColumn?: number; + } + + /** Response to 'breakpointLocations' request. + Contains possible locations for source breakpoints. + */ + export interface BreakpointLocationsResponse extends Response { + body: { + /** Sorted set of possible breakpoint locations. */ + breakpoints: BreakpointLocation[]; + }; + } + + /** SetBreakpoints request; value of command field is 'setBreakpoints'. + Sets multiple breakpoints for a single source and clears all previous breakpoints in that source. + To clear all breakpoint for a source, specify an empty array. + When a breakpoint is hit, a 'stopped' event (with reason 'breakpoint') is generated. + */ + export interface SetBreakpointsRequest extends Request { + // command: 'setBreakpoints'; + arguments: SetBreakpointsArguments; + } + + /** Arguments for 'setBreakpoints' request. */ + export interface SetBreakpointsArguments { + /** The source location of the breakpoints; either 'source.path' or 'source.reference' must be specified. */ + source: Source; + /** The code locations of the breakpoints. */ + breakpoints?: SourceBreakpoint[]; + /** Deprecated: The code locations of the breakpoints. */ + lines?: number[]; + /** A value of true indicates that the underlying source has been modified which results in new breakpoint locations. */ + sourceModified?: boolean; + } + + /** Response to 'setBreakpoints' request. + Returned is information about each breakpoint created by this request. + This includes the actual code location and whether the breakpoint could be verified. + The breakpoints returned are in the same order as the elements of the 'breakpoints' + (or the deprecated 'lines') array in the arguments. + */ + export interface SetBreakpointsResponse extends Response { + body: { + /** Information about the breakpoints. The array elements are in the same order as the elements of the 'breakpoints' (or the deprecated 'lines') array in the arguments. */ + breakpoints: Breakpoint[]; + }; + } + + /** SetFunctionBreakpoints request; value of command field is 'setFunctionBreakpoints'. + Replaces all existing function breakpoints with new function breakpoints. + To clear all function breakpoints, specify an empty array. + When a function breakpoint is hit, a 'stopped' event (with reason 'function breakpoint') is generated. + */ + export interface SetFunctionBreakpointsRequest extends Request { + // command: 'setFunctionBreakpoints'; + arguments: SetFunctionBreakpointsArguments; + } + + /** Arguments for 'setFunctionBreakpoints' request. */ + export interface SetFunctionBreakpointsArguments { + /** The function names of the breakpoints. */ + breakpoints: FunctionBreakpoint[]; + } + + /** Response to 'setFunctionBreakpoints' request. + Returned is information about each breakpoint created by this request. + */ + export interface SetFunctionBreakpointsResponse extends Response { + body: { + /** Information about the breakpoints. The array elements correspond to the elements of the 'breakpoints' array. */ + breakpoints: Breakpoint[]; + }; + } + + /** SetExceptionBreakpoints request; value of command field is 'setExceptionBreakpoints'. + The request configures the debuggers response to thrown exceptions. If an exception is configured to break, a 'stopped' event is fired (with reason 'exception'). + */ + export interface SetExceptionBreakpointsRequest extends Request { + // command: 'setExceptionBreakpoints'; + arguments: SetExceptionBreakpointsArguments; + } + + /** Arguments for 'setExceptionBreakpoints' request. */ + export interface SetExceptionBreakpointsArguments { + /** IDs of checked exception options. The set of IDs is returned via the 'exceptionBreakpointFilters' capability. */ + filters: string[]; + /** Configuration options for selected exceptions. */ + exceptionOptions?: ExceptionOptions[]; + } + + /** Response to 'setExceptionBreakpoints' request. This is just an acknowledgement, so no body field is required. */ + export interface SetExceptionBreakpointsResponse extends Response { + } + + /** DataBreakpointInfo request; value of command field is 'dataBreakpointInfo'. + Obtains information on a possible data breakpoint that could be set on an expression or variable. + */ + export interface DataBreakpointInfoRequest extends Request { + // command: 'dataBreakpointInfo'; + arguments: DataBreakpointInfoArguments; + } + + /** Arguments for 'dataBreakpointInfo' request. */ + export interface DataBreakpointInfoArguments { + /** Reference to the Variable container if the data breakpoint is requested for a child of the container. */ + variablesReference?: number; + /** The name of the Variable's child to obtain data breakpoint information for. If variableReference isn’t provided, this can be an expression. */ + name: string; + } + + /** Response to 'dataBreakpointInfo' request. */ + export interface DataBreakpointInfoResponse extends Response { + body: { + /** An identifier for the data on which a data breakpoint can be registered with the setDataBreakpoints request or null if no data breakpoint is available. */ + dataId: string | null; + /** UI string that describes on what data the breakpoint is set on or why a data breakpoint is not available. */ + description: string; + /** Optional attribute listing the available access types for a potential data breakpoint. A UI frontend could surface this information. */ + accessTypes?: DataBreakpointAccessType[]; + /** Optional attribute indicating that a potential data breakpoint could be persisted across sessions. */ + canPersist?: boolean; + }; + } + + /** SetDataBreakpoints request; value of command field is 'setDataBreakpoints'. + Replaces all existing data breakpoints with new data breakpoints. + To clear all data breakpoints, specify an empty array. + When a data breakpoint is hit, a 'stopped' event (with reason 'data breakpoint') is generated. + */ + export interface SetDataBreakpointsRequest extends Request { + // command: 'setDataBreakpoints'; + arguments: SetDataBreakpointsArguments; + } + + /** Arguments for 'setDataBreakpoints' request. */ + export interface SetDataBreakpointsArguments { + /** The contents of this array replaces all existing data breakpoints. An empty array clears all data breakpoints. */ + breakpoints: DataBreakpoint[]; + } + + /** Response to 'setDataBreakpoints' request. + Returned is information about each breakpoint created by this request. + */ + export interface SetDataBreakpointsResponse extends Response { + body: { + /** Information about the data breakpoints. The array elements correspond to the elements of the input argument 'breakpoints' array. */ + breakpoints: Breakpoint[]; + }; + } + + /** Continue request; value of command field is 'continue'. + The request starts the debuggee to run again. + */ + export interface ContinueRequest extends Request { + // command: 'continue'; + arguments: ContinueArguments; + } + + /** Arguments for 'continue' request. */ + export interface ContinueArguments { + /** Continue execution for the specified thread (if possible). If the backend cannot continue on a single thread but will continue on all threads, it should set the 'allThreadsContinued' attribute in the response to true. */ + threadId: number; + } + + /** Response to 'continue' request. */ + export interface ContinueResponse extends Response { + body: { + /** If true, the 'continue' request has ignored the specified thread and continued all threads instead. If this attribute is missing a value of 'true' is assumed for backward compatibility. */ + allThreadsContinued?: boolean; + }; + } + + /** Next request; value of command field is 'next'. + The request starts the debuggee to run again for one step. + The debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed. + */ + export interface NextRequest extends Request { + // command: 'next'; + arguments: NextArguments; + } + + /** Arguments for 'next' request. */ + export interface NextArguments { + /** Execute 'next' for this thread. */ + threadId: number; + } + + /** Response to 'next' request. This is just an acknowledgement, so no body field is required. */ + export interface NextResponse extends Response { + } + + /** StepIn request; value of command field is 'stepIn'. + The request starts the debuggee to step into a function/method if possible. + If it cannot step into a target, 'stepIn' behaves like 'next'. + The debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed. + If there are multiple function/method calls (or other targets) on the source line, + the optional argument 'targetId' can be used to control into which target the 'stepIn' should occur. + The list of possible targets for a given source line can be retrieved via the 'stepInTargets' request. + */ + export interface StepInRequest extends Request { + // command: 'stepIn'; + arguments: StepInArguments; + } + + /** Arguments for 'stepIn' request. */ + export interface StepInArguments { + /** Execute 'stepIn' for this thread. */ + threadId: number; + /** Optional id of the target to step into. */ + targetId?: number; + } + + /** Response to 'stepIn' request. This is just an acknowledgement, so no body field is required. */ + export interface StepInResponse extends Response { + } + + /** StepOut request; value of command field is 'stepOut'. + The request starts the debuggee to run again for one step. + The debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed. + */ + export interface StepOutRequest extends Request { + // command: 'stepOut'; + arguments: StepOutArguments; + } + + /** Arguments for 'stepOut' request. */ + export interface StepOutArguments { + /** Execute 'stepOut' for this thread. */ + threadId: number; + } + + /** Response to 'stepOut' request. This is just an acknowledgement, so no body field is required. */ + export interface StepOutResponse extends Response { + } + + /** StepBack request; value of command field is 'stepBack'. + The request starts the debuggee to run one step backwards. + The debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed. Clients should only call this request if the capability 'supportsStepBack' is true. + */ + export interface StepBackRequest extends Request { + // command: 'stepBack'; + arguments: StepBackArguments; + } + + /** Arguments for 'stepBack' request. */ + export interface StepBackArguments { + /** Execute 'stepBack' for this thread. */ + threadId: number; + } + + /** Response to 'stepBack' request. This is just an acknowledgement, so no body field is required. */ + export interface StepBackResponse extends Response { + } + + /** ReverseContinue request; value of command field is 'reverseContinue'. + The request starts the debuggee to run backward. Clients should only call this request if the capability 'supportsStepBack' is true. + */ + export interface ReverseContinueRequest extends Request { + // command: 'reverseContinue'; + arguments: ReverseContinueArguments; + } + + /** Arguments for 'reverseContinue' request. */ + export interface ReverseContinueArguments { + /** Execute 'reverseContinue' for this thread. */ + threadId: number; + } + + /** Response to 'reverseContinue' request. This is just an acknowledgement, so no body field is required. */ + export interface ReverseContinueResponse extends Response { + } + + /** RestartFrame request; value of command field is 'restartFrame'. + The request restarts execution of the specified stackframe. + The debug adapter first sends the response and then a 'stopped' event (with reason 'restart') after the restart has completed. + */ + export interface RestartFrameRequest extends Request { + // command: 'restartFrame'; + arguments: RestartFrameArguments; + } + + /** Arguments for 'restartFrame' request. */ + export interface RestartFrameArguments { + /** Restart this stackframe. */ + frameId: number; + } + + /** Response to 'restartFrame' request. This is just an acknowledgement, so no body field is required. */ + export interface RestartFrameResponse extends Response { + } + + /** Goto request; value of command field is 'goto'. + The request sets the location where the debuggee will continue to run. + This makes it possible to skip the execution of code or to executed code again. + The code between the current location and the goto target is not executed but skipped. + The debug adapter first sends the response and then a 'stopped' event with reason 'goto'. + */ + export interface GotoRequest extends Request { + // command: 'goto'; + arguments: GotoArguments; + } + + /** Arguments for 'goto' request. */ + export interface GotoArguments { + /** Set the goto target for this thread. */ + threadId: number; + /** The location where the debuggee will continue to run. */ + targetId: number; + } + + /** Response to 'goto' request. This is just an acknowledgement, so no body field is required. */ + export interface GotoResponse extends Response { + } + + /** Pause request; value of command field is 'pause'. + The request suspends the debuggee. + The debug adapter first sends the response and then a 'stopped' event (with reason 'pause') after the thread has been paused successfully. + */ + export interface PauseRequest extends Request { + // command: 'pause'; + arguments: PauseArguments; + } + + /** Arguments for 'pause' request. */ + export interface PauseArguments { + /** Pause execution for this thread. */ + threadId: number; + } + + /** Response to 'pause' request. This is just an acknowledgement, so no body field is required. */ + export interface PauseResponse extends Response { + } + + /** StackTrace request; value of command field is 'stackTrace'. + The request returns a stacktrace from the current execution state. + */ + export interface StackTraceRequest extends Request { + // command: 'stackTrace'; + arguments: StackTraceArguments; + } + + /** Arguments for 'stackTrace' request. */ + export interface StackTraceArguments { + /** Retrieve the stacktrace for this thread. */ + threadId: number; + /** The index of the first frame to return; if omitted frames start at 0. */ + startFrame?: number; + /** The maximum number of frames to return. If levels is not specified or 0, all frames are returned. */ + levels?: number; + /** Specifies details on how to format the stack frames. */ + format?: StackFrameFormat; + } + + /** Response to 'stackTrace' request. */ + export interface StackTraceResponse extends Response { + body: { + /** The frames of the stackframe. If the array has length zero, there are no stackframes available. + This means that there is no location information available. + */ + stackFrames: StackFrame[]; + /** The total number of frames available. */ + totalFrames?: number; + }; + } + + /** Scopes request; value of command field is 'scopes'. + The request returns the variable scopes for a given stackframe ID. + */ + export interface ScopesRequest extends Request { + // command: 'scopes'; + arguments: ScopesArguments; + } + + /** Arguments for 'scopes' request. */ + export interface ScopesArguments { + /** Retrieve the scopes for this stackframe. */ + frameId: number; + } + + /** Response to 'scopes' request. */ + export interface ScopesResponse extends Response { + body: { + /** The scopes of the stackframe. If the array has length zero, there are no scopes available. */ + scopes: Scope[]; + }; + } + + /** Variables request; value of command field is 'variables'. + Retrieves all child variables for the given variable reference. + An optional filter can be used to limit the fetched children to either named or indexed children. + */ + export interface VariablesRequest extends Request { + // command: 'variables'; + arguments: VariablesArguments; + } + + /** Arguments for 'variables' request. */ + export interface VariablesArguments { + /** The Variable reference. */ + variablesReference: number; + /** Optional filter to limit the child variables to either named or indexed. If omitted, both types are fetched. */ + filter?: 'indexed' | 'named'; + /** The index of the first variable to return; if omitted children start at 0. */ + start?: number; + /** The number of variables to return. If count is missing or 0, all variables are returned. */ + count?: number; + /** Specifies details on how to format the Variable values. */ + format?: ValueFormat; + } + + /** Response to 'variables' request. */ + export interface VariablesResponse extends Response { + body: { + /** All (or a range) of variables for the given variable reference. */ + variables: Variable[]; + }; + } + + /** SetVariable request; value of command field is 'setVariable'. + Set the variable with the given name in the variable container to a new value. + */ + export interface SetVariableRequest extends Request { + // command: 'setVariable'; + arguments: SetVariableArguments; + } + + /** Arguments for 'setVariable' request. */ + export interface SetVariableArguments { + /** The reference of the variable container. */ + variablesReference: number; + /** The name of the variable in the container. */ + name: string; + /** The value of the variable. */ + value: string; + /** Specifies details on how to format the response value. */ + format?: ValueFormat; + } + + /** Response to 'setVariable' request. */ + export interface SetVariableResponse extends Response { + body: { + /** The new value of the variable. */ + value: string; + /** The type of the new value. Typically shown in the UI when hovering over the value. */ + type?: string; + /** If variablesReference is > 0, the new value is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. The value should be less than or equal to 2147483647 (2^31 - 1). */ + variablesReference?: number; + /** The number of named child variables. + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). + */ + namedVariables?: number; + /** The number of indexed child variables. + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). + */ + indexedVariables?: number; + }; + } + + /** Source request; value of command field is 'source'. + The request retrieves the source code for a given source reference. + */ + export interface SourceRequest extends Request { + // command: 'source'; + arguments: SourceArguments; + } + + /** Arguments for 'source' request. */ + export interface SourceArguments { + /** Specifies the source content to load. Either source.path or source.sourceReference must be specified. */ + source?: Source; + /** The reference to the source. This is the same as source.sourceReference. This is provided for backward compatibility since old backends do not understand the 'source' attribute. */ + sourceReference: number; + } + + /** Response to 'source' request. */ + export interface SourceResponse extends Response { + body: { + /** Content of the source reference. */ + content: string; + /** Optional content type (mime type) of the source. */ + mimeType?: string; + }; + } + + /** Threads request; value of command field is 'threads'. + The request retrieves a list of all threads. + */ + export interface ThreadsRequest extends Request { + // command: 'threads'; + } + + /** Response to 'threads' request. */ + export interface ThreadsResponse extends Response { + body: { + /** All threads. */ + threads: Thread[]; + }; + } + + /** TerminateThreads request; value of command field is 'terminateThreads'. + The request terminates the threads with the given ids. + */ + export interface TerminateThreadsRequest extends Request { + // command: 'terminateThreads'; + arguments: TerminateThreadsArguments; + } + + /** Arguments for 'terminateThreads' request. */ + export interface TerminateThreadsArguments { + /** Ids of threads to be terminated. */ + threadIds?: number[]; + } + + /** Response to 'terminateThreads' request. This is just an acknowledgement, so no body field is required. */ + export interface TerminateThreadsResponse extends Response { + } + + /** Modules request; value of command field is 'modules'. + Modules can be retrieved from the debug adapter with the ModulesRequest which can either return all modules or a range of modules to support paging. + */ + export interface ModulesRequest extends Request { + // command: 'modules'; + arguments: ModulesArguments; + } + + /** Arguments for 'modules' request. */ + export interface ModulesArguments { + /** The index of the first module to return; if omitted modules start at 0. */ + startModule?: number; + /** The number of modules to return. If moduleCount is not specified or 0, all modules are returned. */ + moduleCount?: number; + } + + /** Response to 'modules' request. */ + export interface ModulesResponse extends Response { + body: { + /** All modules or range of modules. */ + modules: Module[]; + /** The total number of modules available. */ + totalModules?: number; + }; + } + + /** LoadedSources request; value of command field is 'loadedSources'. + Retrieves the set of all sources currently loaded by the debugged process. + */ + export interface LoadedSourcesRequest extends Request { + // command: 'loadedSources'; + arguments?: LoadedSourcesArguments; + } + + /** Arguments for 'loadedSources' request. */ + export interface LoadedSourcesArguments { + } + + /** Response to 'loadedSources' request. */ + export interface LoadedSourcesResponse extends Response { + body: { + /** Set of loaded sources. */ + sources: Source[]; + }; + } + + /** Evaluate request; value of command field is 'evaluate'. + Evaluates the given expression in the context of the top most stack frame. + The expression has access to any variables and arguments that are in scope. + */ + export interface EvaluateRequest extends Request { + // command: 'evaluate'; + arguments: EvaluateArguments; + } + + /** Arguments for 'evaluate' request. */ + export interface EvaluateArguments { + /** The expression to evaluate. */ + expression: string; + /** Evaluate the expression in the scope of this stack frame. If not specified, the expression is evaluated in the global scope. */ + frameId?: number; + /** The context in which the evaluate request is run. + Values: + 'watch': evaluate is run in a watch. + 'repl': evaluate is run from REPL console. + 'hover': evaluate is run from a data hover. + etc. + */ + context?: string; + /** Specifies details on how to format the Evaluate result. */ + format?: ValueFormat; + } + + /** Response to 'evaluate' request. */ + export interface EvaluateResponse extends Response { + body: { + /** The result of the evaluate request. */ + result: string; + /** The optional type of the evaluate result. */ + type?: string; + /** Properties of a evaluate result that can be used to determine how to render the result in the UI. */ + presentationHint?: VariablePresentationHint; + /** If variablesReference is > 0, the evaluate result is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. The value should be less than or equal to 2147483647 (2^31 - 1). */ + variablesReference: number; + /** The number of named child variables. + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). + */ + namedVariables?: number; + /** The number of indexed child variables. + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). + */ + indexedVariables?: number; + /** Memory reference to a location appropriate for this result. For pointer type eval results, this is generally a reference to the memory address contained in the pointer. */ + memoryReference?: string; + }; + } + + /** SetExpression request; value of command field is 'setExpression'. + Evaluates the given 'value' expression and assigns it to the 'expression' which must be a modifiable l-value. + The expressions have access to any variables and arguments that are in scope of the specified frame. + */ + export interface SetExpressionRequest extends Request { + // command: 'setExpression'; + arguments: SetExpressionArguments; + } + + /** Arguments for 'setExpression' request. */ + export interface SetExpressionArguments { + /** The l-value expression to assign to. */ + expression: string; + /** The value expression to assign to the l-value expression. */ + value: string; + /** Evaluate the expressions in the scope of this stack frame. If not specified, the expressions are evaluated in the global scope. */ + frameId?: number; + /** Specifies how the resulting value should be formatted. */ + format?: ValueFormat; + } + + /** Response to 'setExpression' request. */ + export interface SetExpressionResponse extends Response { + body: { + /** The new value of the expression. */ + value: string; + /** The optional type of the value. */ + type?: string; + /** Properties of a value that can be used to determine how to render the result in the UI. */ + presentationHint?: VariablePresentationHint; + /** If variablesReference is > 0, the value is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. The value should be less than or equal to 2147483647 (2^31 - 1). */ + variablesReference?: number; + /** The number of named child variables. + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). + */ + namedVariables?: number; + /** The number of indexed child variables. + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). + */ + indexedVariables?: number; + }; + } + + /** StepInTargets request; value of command field is 'stepInTargets'. + This request retrieves the possible stepIn targets for the specified stack frame. + These targets can be used in the 'stepIn' request. + The StepInTargets may only be called if the 'supportsStepInTargetsRequest' capability exists and is true. + */ + export interface StepInTargetsRequest extends Request { + // command: 'stepInTargets'; + arguments: StepInTargetsArguments; + } + + /** Arguments for 'stepInTargets' request. */ + export interface StepInTargetsArguments { + /** The stack frame for which to retrieve the possible stepIn targets. */ + frameId: number; + } + + /** Response to 'stepInTargets' request. */ + export interface StepInTargetsResponse extends Response { + body: { + /** The possible stepIn targets of the specified source location. */ + targets: StepInTarget[]; + }; + } + + /** GotoTargets request; value of command field is 'gotoTargets'. + This request retrieves the possible goto targets for the specified source location. + These targets can be used in the 'goto' request. + The GotoTargets request may only be called if the 'supportsGotoTargetsRequest' capability exists and is true. + */ + export interface GotoTargetsRequest extends Request { + // command: 'gotoTargets'; + arguments: GotoTargetsArguments; + } + + /** Arguments for 'gotoTargets' request. */ + export interface GotoTargetsArguments { + /** The source location for which the goto targets are determined. */ + source: Source; + /** The line location for which the goto targets are determined. */ + line: number; + /** An optional column location for which the goto targets are determined. */ + column?: number; + } + + /** Response to 'gotoTargets' request. */ + export interface GotoTargetsResponse extends Response { + body: { + /** The possible goto targets of the specified location. */ + targets: GotoTarget[]; + }; + } + + /** Completions request; value of command field is 'completions'. + Returns a list of possible completions for a given caret position and text. + The CompletionsRequest may only be called if the 'supportsCompletionsRequest' capability exists and is true. + */ + export interface CompletionsRequest extends Request { + // command: 'completions'; + arguments: CompletionsArguments; + } + + /** Arguments for 'completions' request. */ + export interface CompletionsArguments { + /** Returns completions in the scope of this stack frame. If not specified, the completions are returned for the global scope. */ + frameId?: number; + /** One or more source lines. Typically this is the text a user has typed into the debug console before he asked for completion. */ + text: string; + /** The character position for which to determine the completion proposals. */ + column: number; + /** An optional line for which to determine the completion proposals. If missing the first line of the text is assumed. */ + line?: number; + } + + /** Response to 'completions' request. */ + export interface CompletionsResponse extends Response { + body: { + /** The possible completions for . */ + targets: CompletionItem[]; + }; + } + + /** ExceptionInfo request; value of command field is 'exceptionInfo'. + Retrieves the details of the exception that caused this event to be raised. + */ + export interface ExceptionInfoRequest extends Request { + // command: 'exceptionInfo'; + arguments: ExceptionInfoArguments; + } + + /** Arguments for 'exceptionInfo' request. */ + export interface ExceptionInfoArguments { + /** Thread for which exception information should be retrieved. */ + threadId: number; + } + + /** Response to 'exceptionInfo' request. */ + export interface ExceptionInfoResponse extends Response { + body: { + /** ID of the exception that was thrown. */ + exceptionId: string; + /** Descriptive text for the exception provided by the debug adapter. */ + description?: string; + /** Mode that caused the exception notification to be raised. */ + breakMode: ExceptionBreakMode; + /** Detailed information about the exception. */ + details?: ExceptionDetails; + }; + } + + /** ReadMemory request; value of command field is 'readMemory'. + Reads bytes from memory at the provided location. + */ + export interface ReadMemoryRequest extends Request { + // command: 'readMemory'; + arguments: ReadMemoryArguments; + } + + /** Arguments for 'readMemory' request. */ + export interface ReadMemoryArguments { + /** Memory reference to the base location from which data should be read. */ + memoryReference: string; + /** Optional offset (in bytes) to be applied to the reference location before reading data. Can be negative. */ + offset?: number; + /** Number of bytes to read at the specified location and offset. */ + count: number; + } + + /** Response to 'readMemory' request. */ + export interface ReadMemoryResponse extends Response { + body?: { + /** The address of the first byte of data returned. Treated as a hex value if prefixed with '0x', or as a decimal value otherwise. */ + address: string; + /** The number of unreadable bytes encountered after the last successfully read byte. This can be used to determine the number of bytes that must be skipped before a subsequent 'readMemory' request will succeed. */ + unreadableBytes?: number; + /** The bytes read from memory, encoded using base64. */ + data?: string; + }; + } + + /** Disassemble request; value of command field is 'disassemble'. + Disassembles code stored at the provided location. + */ + export interface DisassembleRequest extends Request { + // command: 'disassemble'; + arguments: DisassembleArguments; + } + + /** Arguments for 'disassemble' request. */ + export interface DisassembleArguments { + /** Memory reference to the base location containing the instructions to disassemble. */ + memoryReference: string; + /** Optional offset (in bytes) to be applied to the reference location before disassembling. Can be negative. */ + offset?: number; + /** Optional offset (in instructions) to be applied after the byte offset (if any) before disassembling. Can be negative. */ + instructionOffset?: number; + /** Number of instructions to disassemble starting at the specified location and offset. An adapter must return exactly this number of instructions - any unavailable instructions should be replaced with an implementation-defined 'invalid instruction' value. */ + instructionCount: number; + /** If true, the adapter should attempt to resolve memory addresses and other values to symbolic names. */ + resolveSymbols?: boolean; + } + + /** Response to 'disassemble' request. */ + export interface DisassembleResponse extends Response { + body?: { + /** The list of disassembled instructions. */ + instructions: DisassembledInstruction[]; + }; + } + + /** Information about the capabilities of a debug adapter. */ + export interface Capabilities { + /** The debug adapter supports the 'configurationDone' request. */ + supportsConfigurationDoneRequest?: boolean; + /** The debug adapter supports function breakpoints. */ + supportsFunctionBreakpoints?: boolean; + /** The debug adapter supports conditional breakpoints. */ + supportsConditionalBreakpoints?: boolean; + /** The debug adapter supports breakpoints that break execution after a specified number of hits. */ + supportsHitConditionalBreakpoints?: boolean; + /** The debug adapter supports a (side effect free) evaluate request for data hovers. */ + supportsEvaluateForHovers?: boolean; + /** Available filters or options for the setExceptionBreakpoints request. */ + exceptionBreakpointFilters?: ExceptionBreakpointsFilter[]; + /** The debug adapter supports stepping back via the 'stepBack' and 'reverseContinue' requests. */ + supportsStepBack?: boolean; + /** The debug adapter supports setting a variable to a value. */ + supportsSetVariable?: boolean; + /** The debug adapter supports restarting a frame. */ + supportsRestartFrame?: boolean; + /** The debug adapter supports the 'gotoTargets' request. */ + supportsGotoTargetsRequest?: boolean; + /** The debug adapter supports the 'stepInTargets' request. */ + supportsStepInTargetsRequest?: boolean; + /** The debug adapter supports the 'completions' request. */ + supportsCompletionsRequest?: boolean; + /** The set of characters that should trigger completion in a REPL. If not specified, the UI should assume the '.' character. */ + completionTriggerCharacters?: string[]; + /** The debug adapter supports the 'modules' request. */ + supportsModulesRequest?: boolean; + /** The set of additional module information exposed by the debug adapter. */ + additionalModuleColumns?: ColumnDescriptor[]; + /** Checksum algorithms supported by the debug adapter. */ + supportedChecksumAlgorithms?: ChecksumAlgorithm[]; + /** The debug adapter supports the 'restart' request. In this case a client should not implement 'restart' by terminating and relaunching the adapter but by calling the RestartRequest. */ + supportsRestartRequest?: boolean; + /** The debug adapter supports 'exceptionOptions' on the setExceptionBreakpoints request. */ + supportsExceptionOptions?: boolean; + /** The debug adapter supports a 'format' attribute on the stackTraceRequest, variablesRequest, and evaluateRequest. */ + supportsValueFormattingOptions?: boolean; + /** The debug adapter supports the 'exceptionInfo' request. */ + supportsExceptionInfoRequest?: boolean; + /** The debug adapter supports the 'terminateDebuggee' attribute on the 'disconnect' request. */ + supportTerminateDebuggee?: boolean; + /** The debug adapter supports the delayed loading of parts of the stack, which requires that both the 'startFrame' and 'levels' arguments and the 'totalFrames' result of the 'StackTrace' request are supported. */ + supportsDelayedStackTraceLoading?: boolean; + /** The debug adapter supports the 'loadedSources' request. */ + supportsLoadedSourcesRequest?: boolean; + /** The debug adapter supports logpoints by interpreting the 'logMessage' attribute of the SourceBreakpoint. */ + supportsLogPoints?: boolean; + /** The debug adapter supports the 'terminateThreads' request. */ + supportsTerminateThreadsRequest?: boolean; + /** The debug adapter supports the 'setExpression' request. */ + supportsSetExpression?: boolean; + /** The debug adapter supports the 'terminate' request. */ + supportsTerminateRequest?: boolean; + /** The debug adapter supports data breakpoints. */ + supportsDataBreakpoints?: boolean; + /** The debug adapter supports the 'readMemory' request. */ + supportsReadMemoryRequest?: boolean; + /** The debug adapter supports the 'disassemble' request. */ + supportsDisassembleRequest?: boolean; + /** The debug adapter supports the 'cancel' request. */ + supportsCancelRequest?: boolean; + /** The debug adapter supports the 'breakpointLocations' request. */ + supportsBreakpointLocationsRequest?: boolean; + } + + /** An ExceptionBreakpointsFilter is shown in the UI as an option for configuring how exceptions are dealt with. */ + export interface ExceptionBreakpointsFilter { + /** The internal ID of the filter. This value is passed to the setExceptionBreakpoints request. */ + filter: string; + /** The name of the filter. This will be shown in the UI. */ + label: string; + /** Initial value of the filter. If not specified a value 'false' is assumed. */ + default?: boolean; + } + + /** A structured message object. Used to return errors from requests. */ + export interface Message { + /** Unique identifier for the message. */ + id: number; + /** A format string for the message. Embedded variables have the form '{name}'. + If variable name starts with an underscore character, the variable does not contain user data (PII) and can be safely used for telemetry purposes. + */ + format: string; + /** An object used as a dictionary for looking up the variables in the format string. */ + variables?: { [key: string]: string; }; + /** If true send to telemetry. */ + sendTelemetry?: boolean; + /** If true show user. */ + showUser?: boolean; + /** An optional url where additional information about this message can be found. */ + url?: string; + /** An optional label that is presented to the user as the UI for opening the url. */ + urlLabel?: string; + } + + /** A Module object represents a row in the modules view. + Two attributes are mandatory: an id identifies a module in the modules view and is used in a ModuleEvent for identifying a module for adding, updating or deleting. + The name is used to minimally render the module in the UI. + + Additional attributes can be added to the module. They will show up in the module View if they have a corresponding ColumnDescriptor. + + To avoid an unnecessary proliferation of additional attributes with similar semantics but different names + we recommend to re-use attributes from the 'recommended' list below first, and only introduce new attributes if nothing appropriate could be found. + */ + export interface Module { + /** Unique identifier for the module. */ + id: number | string; + /** A name of the module. */ + name: string; + /** optional but recommended attributes. + always try to use these first before introducing additional attributes. + + Logical full path to the module. The exact definition is implementation defined, but usually this would be a full path to the on-disk file for the module. + */ + path?: string; + /** True if the module is optimized. */ + isOptimized?: boolean; + /** True if the module is considered 'user code' by a debugger that supports 'Just My Code'. */ + isUserCode?: boolean; + /** Version of Module. */ + version?: string; + /** User understandable description of if symbols were found for the module (ex: 'Symbols Loaded', 'Symbols not found', etc. */ + symbolStatus?: string; + /** Logical full path to the symbol file. The exact definition is implementation defined. */ + symbolFilePath?: string; + /** Module created or modified. */ + dateTimeStamp?: string; + /** Address range covered by this module. */ + addressRange?: string; + } + + /** A ColumnDescriptor specifies what module attribute to show in a column of the ModulesView, how to format it, and what the column's label should be. + It is only used if the underlying UI actually supports this level of customization. + */ + export interface ColumnDescriptor { + /** Name of the attribute rendered in this column. */ + attributeName: string; + /** Header UI label of column. */ + label: string; + /** Format to use for the rendered values in this column. TBD how the format strings looks like. */ + format?: string; + /** Datatype of values in this column. Defaults to 'string' if not specified. */ + type?: 'string' | 'number' | 'boolean' | 'unixTimestampUTC'; + /** Width of this column in characters (hint only). */ + width?: number; + } + + /** The ModulesViewDescriptor is the container for all declarative configuration options of a ModuleView. + For now it only specifies the columns to be shown in the modules view. + */ + export interface ModulesViewDescriptor { + columns: ColumnDescriptor[]; + } + + /** A Thread */ + export interface Thread { + /** Unique identifier for the thread. */ + id: number; + /** A name of the thread. */ + name: string; + } + + /** A Source is a descriptor for source code. It is returned from the debug adapter as part of a StackFrame and it is used by clients when specifying breakpoints. */ + export interface Source { + /** The short name of the source. Every source returned from the debug adapter has a name. When sending a source to the debug adapter this name is optional. */ + name?: string; + /** The path of the source to be shown in the UI. It is only used to locate and load the content of the source if no sourceReference is specified (or its value is 0). */ + path?: string; + /** If sourceReference > 0 the contents of the source must be retrieved through the SourceRequest (even if a path is specified). A sourceReference is only valid for a session, so it must not be used to persist a source. The value should be less than or equal to 2147483647 (2^31 - 1). */ + sourceReference?: number; + /** An optional hint for how to present the source in the UI. A value of 'deemphasize' can be used to indicate that the source is not available or that it is skipped on stepping. */ + presentationHint?: 'normal' | 'emphasize' | 'deemphasize'; + /** The (optional) origin of this source: possible values 'internal module', 'inlined content from source map', etc. */ + origin?: string; + /** An optional list of sources that are related to this source. These may be the source that generated this source. */ + sources?: Source[]; + /** Optional data that a debug adapter might want to loop through the client. The client should leave the data intact and persist it across sessions. The client should not interpret the data. */ + adapterData?: any; + /** The checksums associated with this file. */ + checksums?: Checksum[]; + } + + /** A Stackframe contains the source location. */ + export interface StackFrame { + /** An identifier for the stack frame. It must be unique across all threads. This id can be used to retrieve the scopes of the frame with the 'scopesRequest' or to restart the execution of a stackframe. */ + id: number; + /** The name of the stack frame, typically a method name. */ + name: string; + /** The optional source of the frame. */ + source?: Source; + /** The line within the file of the frame. If source is null or doesn't exist, line is 0 and must be ignored. */ + line: number; + /** The column within the line. If source is null or doesn't exist, column is 0 and must be ignored. */ + column: number; + /** An optional end line of the range covered by the stack frame. */ + endLine?: number; + /** An optional end column of the range covered by the stack frame. */ + endColumn?: number; + /** Optional memory reference for the current instruction pointer in this frame. */ + instructionPointerReference?: string; + /** The module associated with this frame, if any. */ + moduleId?: number | string; + /** An optional hint for how to present this frame in the UI. A value of 'label' can be used to indicate that the frame is an artificial frame that is used as a visual label or separator. A value of 'subtle' can be used to change the appearance of a frame in a 'subtle' way. */ + presentationHint?: 'normal' | 'label' | 'subtle'; + } + + /** A Scope is a named container for variables. Optionally a scope can map to a source or a range within a source. */ + export interface Scope { + /** Name of the scope such as 'Arguments', 'Locals', or 'Registers'. This string is shown in the UI as is and can be translated. */ + name: string; + /** An optional hint for how to present this scope in the UI. If this attribute is missing, the scope is shown with a generic UI. + Values: + 'arguments': Scope contains method arguments. + 'locals': Scope contains local variables. + 'registers': Scope contains registers. Only a single 'registers' scope should be returned from a 'scopes' request. + etc. + */ + presentationHint?: string; + /** The variables of this scope can be retrieved by passing the value of variablesReference to the VariablesRequest. */ + variablesReference: number; + /** The number of named variables in this scope. + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. + */ + namedVariables?: number; + /** The number of indexed variables in this scope. + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. + */ + indexedVariables?: number; + /** If true, the number of variables in this scope is large or expensive to retrieve. */ + expensive: boolean; + /** Optional source for this scope. */ + source?: Source; + /** Optional start line of the range covered by this scope. */ + line?: number; + /** Optional start column of the range covered by this scope. */ + column?: number; + /** Optional end line of the range covered by this scope. */ + endLine?: number; + /** Optional end column of the range covered by this scope. */ + endColumn?: number; + } + + /** A Variable is a name/value pair. + Optionally a variable can have a 'type' that is shown if space permits or when hovering over the variable's name. + An optional 'kind' is used to render additional properties of the variable, e.g. different icons can be used to indicate that a variable is public or private. + If the value is structured (has children), a handle is provided to retrieve the children with the VariablesRequest. + If the number of named or indexed children is large, the numbers should be returned via the optional 'namedVariables' and 'indexedVariables' attributes. + The client can use this optional information to present the children in a paged UI and fetch them in chunks. + */ + export interface Variable { + /** The variable's name. */ + name: string; + /** The variable's value. This can be a multi-line text, e.g. for a function the body of a function. */ + value: string; + /** The type of the variable's value. Typically shown in the UI when hovering over the value. */ + type?: string; + /** Properties of a variable that can be used to determine how to render the variable in the UI. */ + presentationHint?: VariablePresentationHint; + /** Optional evaluatable name of this variable which can be passed to the 'EvaluateRequest' to fetch the variable's value. */ + evaluateName?: string; + /** If variablesReference is > 0, the variable is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. */ + variablesReference: number; + /** The number of named child variables. + The client can use this optional information to present the children in a paged UI and fetch them in chunks. + */ + namedVariables?: number; + /** The number of indexed child variables. + The client can use this optional information to present the children in a paged UI and fetch them in chunks. + */ + indexedVariables?: number; + /** Optional memory reference for the variable if the variable represents executable code, such as a function pointer. */ + memoryReference?: string; + } + + /** Optional properties of a variable that can be used to determine how to render the variable in the UI. */ + export interface VariablePresentationHint { + /** The kind of variable. Before introducing additional values, try to use the listed values. + Values: + 'property': Indicates that the object is a property. + 'method': Indicates that the object is a method. + 'class': Indicates that the object is a class. + 'data': Indicates that the object is data. + 'event': Indicates that the object is an event. + 'baseClass': Indicates that the object is a base class. + 'innerClass': Indicates that the object is an inner class. + 'interface': Indicates that the object is an interface. + 'mostDerivedClass': Indicates that the object is the most derived class. + 'virtual': Indicates that the object is virtual, that means it is a synthetic object introduced by the adapter for rendering purposes, e.g. an index range for large arrays. + 'dataBreakpoint': Indicates that a data breakpoint is registered for the object. + etc. + */ + kind?: string; + /** Set of attributes represented as an array of strings. Before introducing additional values, try to use the listed values. + Values: + 'static': Indicates that the object is static. + 'constant': Indicates that the object is a constant. + 'readOnly': Indicates that the object is read only. + 'rawString': Indicates that the object is a raw string. + 'hasObjectId': Indicates that the object can have an Object ID created for it. + 'canHaveObjectId': Indicates that the object has an Object ID associated with it. + 'hasSideEffects': Indicates that the evaluation had side effects. + etc. + */ + attributes?: string[]; + /** Visibility of variable. Before introducing additional values, try to use the listed values. + Values: 'public', 'private', 'protected', 'internal', 'final', etc. + */ + visibility?: string; + } + + /** Properties of a breakpoint location returned from the 'breakpointLocations' request. */ + export interface BreakpointLocation { + /** Start line of breakpoint location. */ + line: number; + /** Optional start column of breakpoint location. */ + column?: number; + /** Optional end line of breakpoint location if the location covers a range. */ + endLine?: number; + /** Optional end column of breakpoint location if the location covers a range. */ + endColumn?: number; + } + + /** Properties of a breakpoint or logpoint passed to the setBreakpoints request. */ + export interface SourceBreakpoint { + /** The source line of the breakpoint or logpoint. */ + line: number; + /** An optional source column of the breakpoint. */ + column?: number; + /** An optional expression for conditional breakpoints. */ + condition?: string; + /** An optional expression that controls how many hits of the breakpoint are ignored. The backend is expected to interpret the expression as needed. */ + hitCondition?: string; + /** If this attribute exists and is non-empty, the backend must not 'break' (stop) but log the message instead. Expressions within {} are interpolated. */ + logMessage?: string; + } + + /** Properties of a breakpoint passed to the setFunctionBreakpoints request. */ + export interface FunctionBreakpoint { + /** The name of the function. */ + name: string; + /** An optional expression for conditional breakpoints. */ + condition?: string; + /** An optional expression that controls how many hits of the breakpoint are ignored. The backend is expected to interpret the expression as needed. */ + hitCondition?: string; + } + + /** This enumeration defines all possible access types for data breakpoints. */ + export type DataBreakpointAccessType = 'read' | 'write' | 'readWrite'; + + /** Properties of a data breakpoint passed to the setDataBreakpoints request. */ + export interface DataBreakpoint { + /** An id representing the data. This id is returned from the dataBreakpointInfo request. */ + dataId: string; + /** The access type of the data. */ + accessType?: DataBreakpointAccessType; + /** An optional expression for conditional breakpoints. */ + condition?: string; + /** An optional expression that controls how many hits of the breakpoint are ignored. The backend is expected to interpret the expression as needed. */ + hitCondition?: string; + } + + /** Information about a Breakpoint created in setBreakpoints or setFunctionBreakpoints. */ + export interface Breakpoint { + /** An optional identifier for the breakpoint. It is needed if breakpoint events are used to update or remove breakpoints. */ + id?: number; + /** If true breakpoint could be set (but not necessarily at the desired location). */ + verified: boolean; + /** An optional message about the state of the breakpoint. This is shown to the user and can be used to explain why a breakpoint could not be verified. */ + message?: string; + /** The source where the breakpoint is located. */ + source?: Source; + /** The start line of the actual range covered by the breakpoint. */ + line?: number; + /** An optional start column of the actual range covered by the breakpoint. */ + column?: number; + /** An optional end line of the actual range covered by the breakpoint. */ + endLine?: number; + /** An optional end column of the actual range covered by the breakpoint. If no end line is given, then the end column is assumed to be in the start line. */ + endColumn?: number; + } + + /** A StepInTarget can be used in the 'stepIn' request and determines into which single target the stepIn request should step. */ + export interface StepInTarget { + /** Unique identifier for a stepIn target. */ + id: number; + /** The name of the stepIn target (shown in the UI). */ + label: string; + } + + /** A GotoTarget describes a code location that can be used as a target in the 'goto' request. + The possible goto targets can be determined via the 'gotoTargets' request. + */ + export interface GotoTarget { + /** Unique identifier for a goto target. This is used in the goto request. */ + id: number; + /** The name of the goto target (shown in the UI). */ + label: string; + /** The line of the goto target. */ + line: number; + /** An optional column of the goto target. */ + column?: number; + /** An optional end line of the range covered by the goto target. */ + endLine?: number; + /** An optional end column of the range covered by the goto target. */ + endColumn?: number; + /** Optional memory reference for the instruction pointer value represented by this target. */ + instructionPointerReference?: string; + } + + /** CompletionItems are the suggestions returned from the CompletionsRequest. */ + export interface CompletionItem { + /** The label of this completion item. By default this is also the text that is inserted when selecting this completion. */ + label: string; + /** If text is not falsy then it is inserted instead of the label. */ + text?: string; + /** A string that should be used when comparing this item with other items. When `falsy` the label is used. */ + sortText?: string; + /** The item's type. Typically the client uses this information to render the item in the UI with an icon. */ + type?: CompletionItemType; + /** This value determines the location (in the CompletionsRequest's 'text' attribute) where the completion text is added. + If missing the text is added at the location specified by the CompletionsRequest's 'column' attribute. + */ + start?: number; + /** This value determines how many characters are overwritten by the completion text. + If missing the value 0 is assumed which results in the completion text being inserted. + */ + length?: number; + } + + /** Some predefined types for the CompletionItem. Please note that not all clients have specific icons for all of them. */ + export type CompletionItemType = 'method' | 'function' | 'constructor' | 'field' | 'variable' | 'class' | 'interface' | 'module' | 'property' | 'unit' | 'value' | 'enum' | 'keyword' | 'snippet' | 'text' | 'color' | 'file' | 'reference' | 'customcolor'; + + /** Names of checksum algorithms that may be supported by a debug adapter. */ + export type ChecksumAlgorithm = 'MD5' | 'SHA1' | 'SHA256' | 'timestamp'; + + /** The checksum of an item calculated by the specified algorithm. */ + export interface Checksum { + /** The algorithm used to calculate this checksum. */ + algorithm: ChecksumAlgorithm; + /** Value of the checksum. */ + checksum: string; + } + + /** Provides formatting information for a value. */ + export interface ValueFormat { + /** Display the value in hex. */ + hex?: boolean; + } + + /** Provides formatting information for a stack frame. */ + export interface StackFrameFormat extends ValueFormat { + /** Displays parameters for the stack frame. */ + parameters?: boolean; + /** Displays the types of parameters for the stack frame. */ + parameterTypes?: boolean; + /** Displays the names of parameters for the stack frame. */ + parameterNames?: boolean; + /** Displays the values of parameters for the stack frame. */ + parameterValues?: boolean; + /** Displays the line number of the stack frame. */ + line?: boolean; + /** Displays the module of the stack frame. */ + module?: boolean; + /** Includes all stack frames, including those the debug adapter might otherwise hide. */ + includeAll?: boolean; + } + + /** An ExceptionOptions assigns configuration options to a set of exceptions. */ + export interface ExceptionOptions { + /** A path that selects a single or multiple exceptions in a tree. If 'path' is missing, the whole tree is selected. By convention the first segment of the path is a category that is used to group exceptions in the UI. */ + path?: ExceptionPathSegment[]; + /** Condition when a thrown exception should result in a break. */ + breakMode: ExceptionBreakMode; + } + + /** This enumeration defines all possible conditions when a thrown exception should result in a break. + never: never breaks, + always: always breaks, + unhandled: breaks when exception unhandled, + userUnhandled: breaks if the exception is not handled by user code. + */ + export type ExceptionBreakMode = 'never' | 'always' | 'unhandled' | 'userUnhandled'; + + /** An ExceptionPathSegment represents a segment in a path that is used to match leafs or nodes in a tree of exceptions. If a segment consists of more than one name, it matches the names provided if 'negate' is false or missing or it matches anything except the names provided if 'negate' is true. */ + export interface ExceptionPathSegment { + /** If false or missing this segment matches the names provided, otherwise it matches anything except the names provided. */ + negate?: boolean; + /** Depending on the value of 'negate' the names that should match or not match. */ + names: string[]; + } + + /** Detailed information about an exception that has occurred. */ + export interface ExceptionDetails { + /** Message contained in the exception. */ + message?: string; + /** Short type name of the exception object. */ + typeName?: string; + /** Fully-qualified type name of the exception object. */ + fullTypeName?: string; + /** Optional expression that can be evaluated in the current scope to obtain the exception object. */ + evaluateName?: string; + /** Stack trace at the time the exception was thrown. */ + stackTrace?: string; + /** Details of the exception contained by this exception, if any. */ + innerException?: ExceptionDetails[]; + } + + /** Represents a single disassembled instruction. */ + export interface DisassembledInstruction { + /** The address of the instruction. Treated as a hex value if prefixed with '0x', or as a decimal value otherwise. */ + address: string; + /** Optional raw bytes representing the instruction and its operands, in an implementation-defined format. */ + instructionBytes?: string; + /** Text representing the instruction and its operands, in an implementation-defined format. */ + instruction: string; + /** Name of the symbol that corresponds with the location of this instruction, if any. */ + symbol?: string; + /** Source location that corresponds to this instruction, if any. Should always be set (if available) on the first instruction returned, but can be omitted afterwards if this instruction maps to the same source file as the previous instruction. */ + location?: Source; + /** The line within the source location that corresponds to this instruction, if any. */ + line?: number; + /** The column within the line that corresponds to this instruction, if any. */ + column?: number; + /** The end line of the range that corresponds to this instruction, if any. */ + endLine?: number; + /** The end column of the range that corresponds to this instruction, if any. */ + endColumn?: number; + } +} + +//------------------------------------------------------------------------------------------------------------------------------ + +export class Message implements DebugProtocol.ProtocolMessage { + seq: number; + type: string; + + public constructor(type: string) { + this.seq = 0; + this.type = type; + } +} + +export class Response extends Message implements DebugProtocol.Response { + request_seq: number; + success: boolean; + command: string; + + public constructor(request: DebugProtocol.Request, message?: string) { + super('response'); + this.request_seq = request.seq; + this.command = request.command; + if (message) { + this.success = false; + (this).message = message; + } else { + this.success = true; + } + } +} + +export class Event extends Message implements DebugProtocol.Event { + event: string; + + public constructor(event: string, body?: any) { + super('event'); + this.event = event; + if (body) { + (this).body = body; + } + } +} + +//-------------------------------------------------------------------------------------------------------------------------------- + +export class ProtocolServer implements vscode.DebugAdapter { + + private close = new vscode.EventEmitter(); + onClose: vscode.Event = this.close.event; + + private error = new vscode.EventEmitter(); + onError: vscode.Event = this.error.event; + + private sendMessage = new vscode.EventEmitter(); + readonly onDidSendMessage: vscode.Event = this.sendMessage.event; + + private _sequence: number = 1; + private _pendingRequests = new Map void>(); + + + public handleMessage(message: DebugProtocol.ProtocolMessage): void { + this.dispatch(message); + } + + public dispose() { + } + + public sendEvent(event: DebugProtocol.Event): void { + this._send('event', event); + } + + public sendResponse(response: DebugProtocol.Response): void { + if (response.seq > 0) { + console.error(`attempt to send more than one response for command ${response.command}`); + } else { + this._send('response', response); + } + } + + public sendRequest(command: string, args: any, timeout: number, cb: (response: DebugProtocol.Response) => void): void { + + const request: any = { + command: command + }; + if (args && Object.keys(args).length > 0) { + request.arguments = args; + } + + this._send('request', request); + + if (cb) { + this._pendingRequests.set(request.seq, cb); + + const timer = setTimeout(() => { + clearTimeout(timer); + const clb = this._pendingRequests.get(request.seq); + if (clb) { + this._pendingRequests.delete(request.seq); + clb(new Response(request, 'timeout')); + } + }, timeout); + } + } + + // ---- protected ---------------------------------------------------------- + + protected dispatchRequest(_request: DebugProtocol.Request): void { + } + + // ---- private ------------------------------------------------------------ + + private dispatch(msg: DebugProtocol.ProtocolMessage) { + if (msg.type === 'request') { + this.dispatchRequest(msg); + } else if (msg.type === 'response') { + const response = msg; + const clb = this._pendingRequests.get(response.request_seq); + if (clb) { + this._pendingRequests.delete(response.request_seq); + clb(response); + } + } + } + + private _send(typ: 'request' | 'response' | 'event', message: DebugProtocol.ProtocolMessage): void { + + message.type = typ; + message.seq = this._sequence++; + + this.sendMessage.fire(message); + } +} + +//------------------------------------------------------------------------------------------------------------------------------- + +export class Source implements DebugProtocol.Source { + name: string; + path?: string; + sourceReference: number; + + public constructor(name: string, path?: string, id: number = 0, origin?: string, data?: any) { + this.name = name; + this.path = path; + this.sourceReference = id; + if (origin) { + (this).origin = origin; + } + if (data) { + (this).adapterData = data; + } + } +} + +export class Scope implements DebugProtocol.Scope { + name: string; + variablesReference: number; + expensive: boolean; + + public constructor(name: string, reference: number, expensive: boolean = false) { + this.name = name; + this.variablesReference = reference; + this.expensive = expensive; + } +} + +export class StackFrame implements DebugProtocol.StackFrame { + id: number; + source?: Source; + line: number; + column: number; + name: string; + + public constructor(i: number, nm: string, src?: Source, ln: number = 0, col: number = 0) { + this.id = i; + this.source = src; + this.line = ln; + this.column = col; + this.name = nm; + } +} + +export class Thread implements DebugProtocol.Thread { + id: number; + name: string; + + public constructor(id: number, name: string) { + this.id = id; + if (name) { + this.name = name; + } else { + this.name = 'Thread #' + id; + } + } +} + +export class Variable implements DebugProtocol.Variable { + name: string; + value: string; + variablesReference: number; + + public constructor(name: string, value: string, ref: number = 0, indexedVariables?: number, namedVariables?: number) { + this.name = name; + this.value = value; + this.variablesReference = ref; + if (typeof namedVariables === 'number') { + (this).namedVariables = namedVariables; + } + if (typeof indexedVariables === 'number') { + (this).indexedVariables = indexedVariables; + } + } +} + +export class Breakpoint implements DebugProtocol.Breakpoint { + verified: boolean; + + public constructor(verified: boolean, line?: number, column?: number, source?: Source) { + this.verified = verified; + const e: DebugProtocol.Breakpoint = this; + if (typeof line === 'number') { + e.line = line; + } + if (typeof column === 'number') { + e.column = column; + } + if (source) { + e.source = source; + } + } +} + +export class Module implements DebugProtocol.Module { + id: number | string; + name: string; + + public constructor(id: number | string, name: string) { + this.id = id; + this.name = name; + } +} + +export class CompletionItem implements DebugProtocol.CompletionItem { + label: string; + start: number; + length: number; + + public constructor(label: string, start: number, length: number = 0) { + this.label = label; + this.start = start; + this.length = length; + } +} + +export class StoppedEvent extends Event implements DebugProtocol.StoppedEvent { + body: { + reason: string; + }; + + public constructor(reason: string, threadId?: number, exceptionText?: string) { + super('stopped'); + this.body = { + reason: reason + }; + if (typeof threadId === 'number') { + (this as DebugProtocol.StoppedEvent).body.threadId = threadId; + } + if (typeof exceptionText === 'string') { + (this as DebugProtocol.StoppedEvent).body.text = exceptionText; + } + } +} + +export class ContinuedEvent extends Event implements DebugProtocol.ContinuedEvent { + body: { + threadId: number; + }; + + public constructor(threadId: number, allThreadsContinued?: boolean) { + super('continued'); + this.body = { + threadId: threadId + }; + + if (typeof allThreadsContinued === 'boolean') { + (this).body.allThreadsContinued = allThreadsContinued; + } + } +} + +export class InitializedEvent extends Event implements DebugProtocol.InitializedEvent { + public constructor() { + super('initialized'); + } +} + +export class TerminatedEvent extends Event implements DebugProtocol.TerminatedEvent { + public constructor(restart?: any) { + super('terminated'); + if (typeof restart === 'boolean' || restart) { + const e: DebugProtocol.TerminatedEvent = this; + e.body = { + restart: restart + }; + } + } +} + +export class OutputEvent extends Event implements DebugProtocol.OutputEvent { + body: { + category: string, + output: string, + data?: any + }; + + public constructor(output: string, category: string = 'console', data?: any) { + super('output'); + this.body = { + category: category, + output: output + }; + if (data !== undefined) { + this.body.data = data; + } + } +} + +export class ThreadEvent extends Event implements DebugProtocol.ThreadEvent { + body: { + reason: string, + threadId: number + }; + + public constructor(reason: string, threadId: number) { + super('thread'); + this.body = { + reason: reason, + threadId: threadId + }; + } +} + +export class BreakpointEvent extends Event implements DebugProtocol.BreakpointEvent { + body: { + reason: string, + breakpoint: Breakpoint + }; + + public constructor(reason: string, breakpoint: Breakpoint) { + super('breakpoint'); + this.body = { + reason: reason, + breakpoint: breakpoint + }; + } +} + +export class ModuleEvent extends Event implements DebugProtocol.ModuleEvent { + body: { + reason: 'new' | 'changed' | 'removed', + module: Module + }; + + public constructor(reason: 'new' | 'changed' | 'removed', module: Module) { + super('module'); + this.body = { + reason: reason, + module: module + }; + } +} + +export class LoadedSourceEvent extends Event implements DebugProtocol.LoadedSourceEvent { + body: { + reason: 'new' | 'changed' | 'removed', + source: Source + }; + + public constructor(reason: 'new' | 'changed' | 'removed', source: Source) { + super('loadedSource'); + this.body = { + reason: reason, + source: source + }; + } +} + +export class CapabilitiesEvent extends Event implements DebugProtocol.CapabilitiesEvent { + body: { + capabilities: DebugProtocol.Capabilities + }; + + public constructor(capabilities: DebugProtocol.Capabilities) { + super('capabilities'); + this.body = { + capabilities: capabilities + }; + } +} + +export enum ErrorDestination { + User = 1, + Telemetry = 2 +} + +export class DebugSession extends ProtocolServer { + + private _debuggerLinesStartAt1: boolean; + private _debuggerColumnsStartAt1: boolean; + private _debuggerPathsAreURIs: boolean; + + private _clientLinesStartAt1: boolean; + private _clientColumnsStartAt1: boolean; + private _clientPathsAreURIs: boolean; + + protected _isServer: boolean; + + public constructor(obsolete_debuggerLinesAndColumnsStartAt1?: boolean, obsolete_isServer?: boolean) { + super(); + + const linesAndColumnsStartAt1 = typeof obsolete_debuggerLinesAndColumnsStartAt1 === 'boolean' ? obsolete_debuggerLinesAndColumnsStartAt1 : false; + this._debuggerLinesStartAt1 = linesAndColumnsStartAt1; + this._debuggerColumnsStartAt1 = linesAndColumnsStartAt1; + this._debuggerPathsAreURIs = false; + + this._clientLinesStartAt1 = true; + this._clientColumnsStartAt1 = true; + this._clientPathsAreURIs = false; + + this._isServer = typeof obsolete_isServer === 'boolean' ? obsolete_isServer : false; + + this.onClose(() => { + this.shutdown(); + }); + this.onError((_error) => { + this.shutdown(); + }); + } + + public setDebuggerPathFormat(format: string) { + this._debuggerPathsAreURIs = format !== 'path'; + } + + public setDebuggerLinesStartAt1(enable: boolean) { + this._debuggerLinesStartAt1 = enable; + } + + public setDebuggerColumnsStartAt1(enable: boolean) { + this._debuggerColumnsStartAt1 = enable; + } + + public setRunAsServer(enable: boolean) { + this._isServer = enable; + } + + public shutdown(): void { + if (this._isServer) { + // shutdown ignored in server mode + } else { + // TODO@AW + /* + // wait a bit before shutting down + setTimeout(() => { + process.exit(0); + }, 100); + */ + } + } + + protected sendErrorResponse(response: DebugProtocol.Response, codeOrMessage: number | DebugProtocol.Message, format?: string, variables?: any, dest: ErrorDestination = ErrorDestination.User): void { + + let msg: DebugProtocol.Message; + if (typeof codeOrMessage === 'number') { + msg = { + id: codeOrMessage, + format: format + }; + if (variables) { + msg.variables = variables; + } + if (dest & ErrorDestination.User) { + msg.showUser = true; + } + if (dest & ErrorDestination.Telemetry) { + msg.sendTelemetry = true; + } + } else { + msg = codeOrMessage; + } + + response.success = false; + response.message = DebugSession.formatPII(msg.format, true, msg.variables); + if (!response.body) { + response.body = {}; + } + response.body.error = msg; + + this.sendResponse(response); + } + + public runInTerminalRequest(args: DebugProtocol.RunInTerminalRequestArguments, timeout: number, cb: (response: DebugProtocol.Response) => void) { + this.sendRequest('runInTerminal', args, timeout, cb); + } + + protected dispatchRequest(request: DebugProtocol.Request): void { + + const response = new Response(request); + + try { + if (request.command === 'initialize') { + const args = request.arguments; + + if (typeof args.linesStartAt1 === 'boolean') { + this._clientLinesStartAt1 = args.linesStartAt1; + } + if (typeof args.columnsStartAt1 === 'boolean') { + this._clientColumnsStartAt1 = args.columnsStartAt1; + } + + if (args.pathFormat !== 'path') { + this.sendErrorResponse(response, 2018, 'debug adapter only supports native paths', null, ErrorDestination.Telemetry); + } else { + const initializeResponse = response; + initializeResponse.body = {}; + this.initializeRequest(initializeResponse, args); + } + + } else if (request.command === 'launch') { + this.launchRequest(response, request.arguments, request); + + } else if (request.command === 'attach') { + this.attachRequest(response, request.arguments, request); + + } else if (request.command === 'disconnect') { + this.disconnectRequest(response, request.arguments, request); + + } else if (request.command === 'terminate') { + this.terminateRequest(response, request.arguments, request); + + } else if (request.command === 'restart') { + this.restartRequest(response, request.arguments, request); + + } else if (request.command === 'setBreakpoints') { + this.setBreakPointsRequest(response, request.arguments, request); + + } else if (request.command === 'setFunctionBreakpoints') { + this.setFunctionBreakPointsRequest(response, request.arguments, request); + + } else if (request.command === 'setExceptionBreakpoints') { + this.setExceptionBreakPointsRequest(response, request.arguments, request); + + } else if (request.command === 'configurationDone') { + this.configurationDoneRequest(response, request.arguments, request); + + } else if (request.command === 'continue') { + this.continueRequest(response, request.arguments, request); + + } else if (request.command === 'next') { + this.nextRequest(response, request.arguments, request); + + } else if (request.command === 'stepIn') { + this.stepInRequest(response, request.arguments, request); + + } else if (request.command === 'stepOut') { + this.stepOutRequest(response, request.arguments, request); + + } else if (request.command === 'stepBack') { + this.stepBackRequest(response, request.arguments, request); + + } else if (request.command === 'reverseContinue') { + this.reverseContinueRequest(response, request.arguments, request); + + } else if (request.command === 'restartFrame') { + this.restartFrameRequest(response, request.arguments, request); + + } else if (request.command === 'goto') { + this.gotoRequest(response, request.arguments, request); + + } else if (request.command === 'pause') { + this.pauseRequest(response, request.arguments, request); + + } else if (request.command === 'stackTrace') { + this.stackTraceRequest(response, request.arguments, request); + + } else if (request.command === 'scopes') { + this.scopesRequest(response, request.arguments, request); + + } else if (request.command === 'variables') { + this.variablesRequest(response, request.arguments, request); + + } else if (request.command === 'setVariable') { + this.setVariableRequest(response, request.arguments, request); + + } else if (request.command === 'setExpression') { + this.setExpressionRequest(response, request.arguments, request); + + } else if (request.command === 'source') { + this.sourceRequest(response, request.arguments, request); + + } else if (request.command === 'threads') { + this.threadsRequest(response, request); + + } else if (request.command === 'terminateThreads') { + this.terminateThreadsRequest(response, request.arguments, request); + + } else if (request.command === 'evaluate') { + this.evaluateRequest(response, request.arguments, request); + + } else if (request.command === 'stepInTargets') { + this.stepInTargetsRequest(response, request.arguments, request); + + } else if (request.command === 'gotoTargets') { + this.gotoTargetsRequest(response, request.arguments, request); + + } else if (request.command === 'completions') { + this.completionsRequest(response, request.arguments, request); + + } else if (request.command === 'exceptionInfo') { + this.exceptionInfoRequest(response, request.arguments, request); + + } else if (request.command === 'loadedSources') { + this.loadedSourcesRequest(response, request.arguments, request); + + } else if (request.command === 'dataBreakpointInfo') { + this.dataBreakpointInfoRequest(response, request.arguments, request); + + } else if (request.command === 'setDataBreakpoints') { + this.setDataBreakpointsRequest(response, request.arguments, request); + + } else if (request.command === 'readMemory') { + this.readMemoryRequest(response, request.arguments, request); + + } else if (request.command === 'disassemble') { + this.disassembleRequest(response, request.arguments, request); + + } else if (request.command === 'cancel') { + this.cancelRequest(response, request.arguments, request); + + } else if (request.command === 'breakpointLocations') { + this.breakpointLocationsRequest(response, request.arguments, request); + + } else { + this.customRequest(request.command, response, request.arguments, request); + } + } catch (e) { + this.sendErrorResponse(response, 1104, '{_stack}', { _exception: e.message, _stack: e.stack }, ErrorDestination.Telemetry); + } + } + + protected initializeRequest(response: DebugProtocol.InitializeResponse, _args: DebugProtocol.InitializeRequestArguments): void { + + response.body = response.body || {}; + + // This default debug adapter does not support conditional breakpoints. + response.body.supportsConditionalBreakpoints = false; + + // This default debug adapter does not support hit conditional breakpoints. + response.body.supportsHitConditionalBreakpoints = false; + + // This default debug adapter does not support function breakpoints. + response.body.supportsFunctionBreakpoints = false; + + // This default debug adapter implements the 'configurationDone' request. + response.body.supportsConfigurationDoneRequest = true; + + // This default debug adapter does not support hovers based on the 'evaluate' request. + response.body.supportsEvaluateForHovers = false; + + // This default debug adapter does not support the 'stepBack' request. + response.body.supportsStepBack = false; + + // This default debug adapter does not support the 'setVariable' request. + response.body.supportsSetVariable = false; + + // This default debug adapter does not support the 'restartFrame' request. + response.body.supportsRestartFrame = false; + + // This default debug adapter does not support the 'stepInTargets' request. + response.body.supportsStepInTargetsRequest = false; + + // This default debug adapter does not support the 'gotoTargets' request. + response.body.supportsGotoTargetsRequest = false; + + // This default debug adapter does not support the 'completions' request. + response.body.supportsCompletionsRequest = false; + + // This default debug adapter does not support the 'restart' request. + response.body.supportsRestartRequest = false; + + // This default debug adapter does not support the 'exceptionOptions' attribute on the 'setExceptionBreakpoints' request. + response.body.supportsExceptionOptions = false; + + // This default debug adapter does not support the 'format' attribute on the 'variables', 'evaluate', and 'stackTrace' request. + response.body.supportsValueFormattingOptions = false; + + // This debug adapter does not support the 'exceptionInfo' request. + response.body.supportsExceptionInfoRequest = false; + + // This debug adapter does not support the 'TerminateDebuggee' attribute on the 'disconnect' request. + response.body.supportTerminateDebuggee = false; + + // This debug adapter does not support delayed loading of stack frames. + response.body.supportsDelayedStackTraceLoading = false; + + // This debug adapter does not support the 'loadedSources' request. + response.body.supportsLoadedSourcesRequest = false; + + // This debug adapter does not support the 'logMessage' attribute of the SourceBreakpoint. + response.body.supportsLogPoints = false; + + // This debug adapter does not support the 'terminateThreads' request. + response.body.supportsTerminateThreadsRequest = false; + + // This debug adapter does not support the 'setExpression' request. + response.body.supportsSetExpression = false; + + // This debug adapter does not support the 'terminate' request. + response.body.supportsTerminateRequest = false; + + // This debug adapter does not support data breakpoints. + response.body.supportsDataBreakpoints = false; + + /** This debug adapter does not support the 'readMemory' request. */ + response.body.supportsReadMemoryRequest = false; + + /** The debug adapter does not support the 'disassemble' request. */ + response.body.supportsDisassembleRequest = false; + + /** The debug adapter does not support the 'cancel' request. */ + response.body.supportsCancelRequest = false; + + /** The debug adapter does not support the 'breakpointLocations' request. */ + response.body.supportsBreakpointLocationsRequest = false; + + this.sendResponse(response); + } + + protected disconnectRequest(response: DebugProtocol.DisconnectResponse, _args: DebugProtocol.DisconnectArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + this.shutdown(); + } + + protected launchRequest(response: DebugProtocol.LaunchResponse, _args: DebugProtocol.LaunchRequestArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected attachRequest(response: DebugProtocol.AttachResponse, _args: DebugProtocol.AttachRequestArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected terminateRequest(response: DebugProtocol.TerminateResponse, _args: DebugProtocol.TerminateArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected restartRequest(response: DebugProtocol.RestartResponse, _args: DebugProtocol.RestartArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, _args: DebugProtocol.SetBreakpointsArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected setFunctionBreakPointsRequest(response: DebugProtocol.SetFunctionBreakpointsResponse, _args: DebugProtocol.SetFunctionBreakpointsArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected setExceptionBreakPointsRequest(response: DebugProtocol.SetExceptionBreakpointsResponse, _args: DebugProtocol.SetExceptionBreakpointsArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse, _args: DebugProtocol.ConfigurationDoneArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected continueRequest(response: DebugProtocol.ContinueResponse, _args: DebugProtocol.ContinueArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected nextRequest(response: DebugProtocol.NextResponse, _args: DebugProtocol.NextArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected stepInRequest(response: DebugProtocol.StepInResponse, _args: DebugProtocol.StepInArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected stepOutRequest(response: DebugProtocol.StepOutResponse, _args: DebugProtocol.StepOutArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected stepBackRequest(response: DebugProtocol.StepBackResponse, _args: DebugProtocol.StepBackArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, _args: DebugProtocol.ReverseContinueArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected restartFrameRequest(response: DebugProtocol.RestartFrameResponse, _args: DebugProtocol.RestartFrameArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected gotoRequest(response: DebugProtocol.GotoResponse, _args: DebugProtocol.GotoArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected pauseRequest(response: DebugProtocol.PauseResponse, _args: DebugProtocol.PauseArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected sourceRequest(response: DebugProtocol.SourceResponse, _args: DebugProtocol.SourceArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected threadsRequest(response: DebugProtocol.ThreadsResponse, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected terminateThreadsRequest(response: DebugProtocol.TerminateThreadsResponse, _args: DebugProtocol.TerminateThreadsArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, _args: DebugProtocol.StackTraceArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected scopesRequest(response: DebugProtocol.ScopesResponse, _args: DebugProtocol.ScopesArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected variablesRequest(response: DebugProtocol.VariablesResponse, _args: DebugProtocol.VariablesArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected setVariableRequest(response: DebugProtocol.SetVariableResponse, _args: DebugProtocol.SetVariableArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected setExpressionRequest(response: DebugProtocol.SetExpressionResponse, _args: DebugProtocol.SetExpressionArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected evaluateRequest(response: DebugProtocol.EvaluateResponse, _args: DebugProtocol.EvaluateArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected stepInTargetsRequest(response: DebugProtocol.StepInTargetsResponse, _args: DebugProtocol.StepInTargetsArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected gotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, _args: DebugProtocol.GotoTargetsArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected completionsRequest(response: DebugProtocol.CompletionsResponse, _args: DebugProtocol.CompletionsArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected exceptionInfoRequest(response: DebugProtocol.ExceptionInfoResponse, _args: DebugProtocol.ExceptionInfoArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected loadedSourcesRequest(response: DebugProtocol.LoadedSourcesResponse, _args: DebugProtocol.LoadedSourcesArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected dataBreakpointInfoRequest(response: DebugProtocol.DataBreakpointInfoResponse, _args: DebugProtocol.DataBreakpointInfoArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected setDataBreakpointsRequest(response: DebugProtocol.SetDataBreakpointsResponse, _args: DebugProtocol.SetDataBreakpointsArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected readMemoryRequest(response: DebugProtocol.ReadMemoryResponse, _args: DebugProtocol.ReadMemoryArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected disassembleRequest(response: DebugProtocol.DisassembleResponse, _args: DebugProtocol.DisassembleArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected cancelRequest(response: DebugProtocol.CancelResponse, _args: DebugProtocol.CancelArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + protected breakpointLocationsRequest(response: DebugProtocol.BreakpointLocationsResponse, _args: DebugProtocol.BreakpointLocationsArguments, _request?: DebugProtocol.Request): void { + this.sendResponse(response); + } + + /** + * Override this hook to implement custom requests. + */ + protected customRequest(_command: string, response: DebugProtocol.Response, _args: any, _request?: DebugProtocol.Request): void { + this.sendErrorResponse(response, 1014, 'unrecognized request', null, ErrorDestination.Telemetry); + } + + //---- protected ------------------------------------------------------------------------------------------------- + + protected convertClientLineToDebugger(line: number): number { + if (this._debuggerLinesStartAt1) { + return this._clientLinesStartAt1 ? line : line + 1; + } + return this._clientLinesStartAt1 ? line - 1 : line; + } + + protected convertDebuggerLineToClient(line: number): number { + if (this._debuggerLinesStartAt1) { + return this._clientLinesStartAt1 ? line : line - 1; + } + return this._clientLinesStartAt1 ? line + 1 : line; + } + + protected convertClientColumnToDebugger(column: number): number { + if (this._debuggerColumnsStartAt1) { + return this._clientColumnsStartAt1 ? column : column + 1; + } + return this._clientColumnsStartAt1 ? column - 1 : column; + } + + protected convertDebuggerColumnToClient(column: number): number { + if (this._debuggerColumnsStartAt1) { + return this._clientColumnsStartAt1 ? column : column - 1; + } + return this._clientColumnsStartAt1 ? column + 1 : column; + } + + protected convertClientPathToDebugger(clientPath: string): string { + if (this._clientPathsAreURIs !== this._debuggerPathsAreURIs) { + if (this._clientPathsAreURIs) { + return DebugSession.uri2path(clientPath); + } else { + return DebugSession.path2uri(clientPath); + } + } + return clientPath; + } + + protected convertDebuggerPathToClient(debuggerPath: string): string { + if (this._debuggerPathsAreURIs !== this._clientPathsAreURIs) { + if (this._debuggerPathsAreURIs) { + return DebugSession.uri2path(debuggerPath); + } else { + return DebugSession.path2uri(debuggerPath); + } + } + return debuggerPath; + } + + //---- private ------------------------------------------------------------------------------- + + private static path2uri(path: string): string { + + path = encodeURI(path); + + let uri = new URL(`file:`); // ignore 'path' for now + uri.pathname = path; // now use 'path' to get the correct percent encoding (see https://url.spec.whatwg.org) + return uri.toString(); + } + + private static uri2path(sourceUri: string): string { + + let uri = new URL(sourceUri); + let s = decodeURIComponent(uri.pathname); + return s; + } + + private static _formatPIIRegexp = /{([^}]+)}/g; + + /* + * If argument starts with '_' it is OK to send its value to telemetry. + */ + private static formatPII(format: string, excludePII: boolean, args?: { [key: string]: string }): string { + return format.replace(DebugSession._formatPIIRegexp, function (match, paramName) { + if (excludePII && paramName.length > 0 && paramName[0] !== '_') { + return match; + } + return args && args[paramName] && args.hasOwnProperty(paramName) ? + args[paramName] : + match; + }); + } +} + +//--------------------------------------------------------------------------- + +export class Handles { + + private START_HANDLE = 1000; + + private _nextHandle: number; + private _handleMap = new Map(); + + public constructor(startHandle?: number) { + this._nextHandle = typeof startHandle === 'number' ? startHandle : this.START_HANDLE; + } + + public reset(): void { + this._nextHandle = this.START_HANDLE; + this._handleMap = new Map(); + } + + public create(value: T): number { + const handle = this._nextHandle++; + this._handleMap.set(handle, value); + return handle; + } + + public get(handle: number, dflt?: T): T | undefined { + return this._handleMap.get(handle) || dflt; + } +} + +//--------------------------------------------------------------------------- + +class MockConfigurationProvider implements vscode.DebugConfigurationProvider { + + /** + * Massage a debug configuration just before a debug session is being launched, + * e.g. add all missing attributes to the debug configuration. + */ + resolveDebugConfiguration(_folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration, _token?: vscode.CancellationToken): vscode.ProviderResult { + + // if launch.json is missing or empty + if (!config.type && !config.request && !config.name) { + const editor = vscode.window.activeTextEditor; + if (editor && editor.document.languageId === 'markdown') { + config.type = 'mock'; + config.name = 'Launch'; + config.request = 'launch'; + config.program = '${file}'; + config.stopOnEntry = true; + } + } + + if (!config.program) { + return vscode.window.showInformationMessage('Cannot find a program to debug').then(_ => { + return undefined; // abort launch + }); + } + + return config; + } +} + +export class MockDebugAdapterDescriptorFactory implements vscode.DebugAdapterDescriptorFactory { + + constructor(private memfs: MemFS) { + } + + createDebugAdapterDescriptor(_session: vscode.DebugSession, _executable: vscode.DebugAdapterExecutable | undefined): vscode.ProviderResult { + return new vscode.DebugAdapterInlineImplementation(new MockDebugSession(this.memfs)); + } +} + +function basename(path: string): string { + const pos = path.lastIndexOf('/'); + if (pos >= 0) { + return path.substring(pos + 1); + } + return path; +} + +function timeout(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +/** + * This interface describes the mock-debug specific launch attributes + * (which are not part of the Debug Adapter Protocol). + * The schema for these attributes lives in the package.json of the mock-debug extension. + * The interface should always match this schema. + */ +interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { + /** An absolute path to the "program" to debug. */ + program: string; + /** Automatically stop target after launch. If not specified, target does not stop. */ + stopOnEntry?: boolean; + /** enable logging the Debug Adapter Protocol */ + trace?: boolean; +} + +export class MockDebugSession extends DebugSession { + + // we don't support multiple threads, so we can use a hardcoded ID for the default thread + private static THREAD_ID = 1; + + // a Mock runtime (or debugger) + private _runtime: MockRuntime; + + private _variableHandles = new Handles(); + + //private _configurationDone = new Subject(); + + private promiseResolve?: () => void; + private _configurationDone = new Promise((r, _e) => { + this.promiseResolve = r; + setTimeout(r, 1000); + }); + + private _cancelationTokens = new Map(); + private _isLongrunning = new Map(); + + /** + * Creates a new debug adapter that is used for one debug session. + * We configure the default implementation of a debug adapter here. + */ + public constructor(memfs: MemFS) { + + super(); + + // this debugger uses zero-based lines and columns + this.setDebuggerLinesStartAt1(false); + this.setDebuggerColumnsStartAt1(false); + + this._runtime = new MockRuntime(memfs); + + // setup event handlers + this._runtime.onStopOnEntry(() => { + this.sendEvent(new StoppedEvent('entry', MockDebugSession.THREAD_ID)); + }); + this._runtime.onStopOnStep(() => { + this.sendEvent(new StoppedEvent('step', MockDebugSession.THREAD_ID)); + }); + this._runtime.onStopOnBreakpoint(() => { + this.sendEvent(new StoppedEvent('breakpoint', MockDebugSession.THREAD_ID)); + }); + this._runtime.onStopOnDataBreakpoint(() => { + this.sendEvent(new StoppedEvent('data breakpoint', MockDebugSession.THREAD_ID)); + }); + this._runtime.onStopOnException(() => { + this.sendEvent(new StoppedEvent('exception', MockDebugSession.THREAD_ID)); + }); + this._runtime.onBreakpointValidated((bp: MockBreakpoint) => { + this.sendEvent(new BreakpointEvent('changed', { verified: bp.verified, id: bp.id })); + }); + this._runtime.onOutput(oe => { + const e: DebugProtocol.OutputEvent = new OutputEvent(`${oe.text}\n`); + e.body.source = this.createSource(oe.filePath); + e.body.line = this.convertDebuggerLineToClient(oe.line); + e.body.column = this.convertDebuggerColumnToClient(oe.column); + this.sendEvent(e); + }); + this._runtime.onEnd(() => { + this.sendEvent(new TerminatedEvent()); + }); + } + + /** + * The 'initialize' request is the first request called by the frontend + * to interrogate the features the debug adapter provides. + */ + protected initializeRequest(response: DebugProtocol.InitializeResponse, _args: DebugProtocol.InitializeRequestArguments): void { + + // build and return the capabilities of this debug adapter: + response.body = response.body || {}; + + // the adapter implements the configurationDoneRequest. + response.body.supportsConfigurationDoneRequest = true; + + // make VS Code to use 'evaluate' when hovering over source + response.body.supportsEvaluateForHovers = true; + + // make VS Code to show a 'step back' button + response.body.supportsStepBack = true; + + // make VS Code to support data breakpoints + response.body.supportsDataBreakpoints = true; + + // make VS Code to support completion in REPL + response.body.supportsCompletionsRequest = true; + response.body.completionTriggerCharacters = ['.', '[']; + + // make VS Code to send cancelRequests + response.body.supportsCancelRequest = true; + + // make VS Code send the breakpointLocations request + response.body.supportsBreakpointLocationsRequest = true; + + this.sendResponse(response); + + // since this debug adapter can accept configuration requests like 'setBreakpoint' at any time, + // we request them early by sending an 'initializeRequest' to the frontend. + // The frontend will end the configuration sequence by calling 'configurationDone' request. + this.sendEvent(new InitializedEvent()); + } + + /** + * Called at the end of the configuration sequence. + * Indicates that all breakpoints etc. have been sent to the DA and that the 'launch' can start. + */ + protected configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse, args: DebugProtocol.ConfigurationDoneArguments): void { + super.configurationDoneRequest(response, args); + + // notify the launchRequest that configuration has finished + //this._configurationDone.notify(); + if (this.promiseResolve) { + this.promiseResolve(); + } + } + + protected async launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments) { + + // make sure to 'Stop' the buffered logging if 'trace' is not set + //logger.setup(args.trace ? Logger.LogLevel.Verbose : Logger.LogLevel.Stop, false); + + // wait until configuration has finished (and configurationDoneRequest has been called) + await this._configurationDone; + + // start the program in the runtime + this._runtime.start(`memfs:${args.program}`, !!args.stopOnEntry); + + this.sendResponse(response); + } + + protected setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void { + + const path = args.source.path; + const clientLines = args.lines || []; + + // clear all breakpoints for this file + this._runtime.clearBreakpoints(path); + + // set and verify breakpoint locations + const actualBreakpoints = clientLines.map(l => { + let { verified, line, id } = this._runtime.setBreakPoint(path, this.convertClientLineToDebugger(l)); + const bp = new Breakpoint(verified, this.convertDebuggerLineToClient(line)); + bp.id = id; + return bp; + }); + + // send back the actual breakpoint positions + response.body = { + breakpoints: actualBreakpoints + }; + this.sendResponse(response); + } + + protected breakpointLocationsRequest(response: DebugProtocol.BreakpointLocationsResponse, args: DebugProtocol.BreakpointLocationsArguments, _request?: DebugProtocol.Request): void { + + if (args.source.path) { + const bps = this._runtime.getBreakpoints(args.source.path, this.convertClientLineToDebugger(args.line)); + response.body = { + breakpoints: bps.map(col => { + return { + line: args.line, + column: this.convertDebuggerColumnToClient(col) + }; + }) + }; + } else { + response.body = { + breakpoints: [] + }; + } + this.sendResponse(response); + } + + protected threadsRequest(response: DebugProtocol.ThreadsResponse): void { + + // runtime supports no threads so just return a default thread. + response.body = { + threads: [ + new Thread(MockDebugSession.THREAD_ID, 'thread 1') + ] + }; + this.sendResponse(response); + } + + protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void { + + const startFrame = typeof args.startFrame === 'number' ? args.startFrame : 0; + const maxLevels = typeof args.levels === 'number' ? args.levels : 1000; + const endFrame = startFrame + maxLevels; + + const stk = this._runtime.stack(startFrame, endFrame); + + response.body = { + stackFrames: stk.frames.map(f => new StackFrame(f.index, f.name, this.createSource(f.file), this.convertDebuggerLineToClient(f.line))), + totalFrames: stk.count + }; + this.sendResponse(response); + } + + protected scopesRequest(response: DebugProtocol.ScopesResponse, _args: DebugProtocol.ScopesArguments): void { + + response.body = { + scopes: [ + new Scope('Local', this._variableHandles.create('local'), false), + new Scope('Global', this._variableHandles.create('global'), true) + ] + }; + this.sendResponse(response); + } + + protected async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments, request?: DebugProtocol.Request) { + + const variables: DebugProtocol.Variable[] = []; + + if (this._isLongrunning.get(args.variablesReference)) { + // long running + + if (request) { + this._cancelationTokens.set(request.seq, false); + } + + for (let i = 0; i < 100; i++) { + await timeout(1000); + variables.push({ + name: `i_${i}`, + type: 'integer', + value: `${i}`, + variablesReference: 0 + }); + if (request && this._cancelationTokens.get(request.seq)) { + break; + } + } + + if (request) { + this._cancelationTokens.delete(request.seq); + } + + } else { + + const id = this._variableHandles.get(args.variablesReference); + + if (id) { + variables.push({ + name: id + '_i', + type: 'integer', + value: '123', + variablesReference: 0 + }); + variables.push({ + name: id + '_f', + type: 'float', + value: '3.14', + variablesReference: 0 + }); + variables.push({ + name: id + '_s', + type: 'string', + value: 'hello world', + variablesReference: 0 + }); + variables.push({ + name: id + '_o', + type: 'object', + value: 'Object', + variablesReference: this._variableHandles.create(id + '_o') + }); + + // cancelation support for long running requests + const nm = id + '_long_running'; + const ref = this._variableHandles.create(id + '_lr'); + variables.push({ + name: nm, + type: 'object', + value: 'Object', + variablesReference: ref + }); + this._isLongrunning.set(ref, true); + } + } + + response.body = { + variables: variables + }; + this.sendResponse(response); + } + + protected continueRequest(response: DebugProtocol.ContinueResponse, _args: DebugProtocol.ContinueArguments): void { + this._runtime.continue(); + this.sendResponse(response); + } + + protected reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, _args: DebugProtocol.ReverseContinueArguments): void { + this._runtime.continue(true); + this.sendResponse(response); + } + + protected nextRequest(response: DebugProtocol.NextResponse, _args: DebugProtocol.NextArguments): void { + this._runtime.step(); + this.sendResponse(response); + } + + protected stepBackRequest(response: DebugProtocol.StepBackResponse, _args: DebugProtocol.StepBackArguments): void { + this._runtime.step(true); + this.sendResponse(response); + } + + protected evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { + + let reply: string | undefined = undefined; + + if (args.context === 'repl') { + // 'evaluate' supports to create and delete breakpoints from the 'repl': + const matches = /new +([0-9]+)/.exec(args.expression); + if (matches && matches.length === 2) { + if (this._runtime.sourceFile) { + const mbp = this._runtime.setBreakPoint(this._runtime.sourceFile, this.convertClientLineToDebugger(parseInt(matches[1]))); + const bp = new Breakpoint(mbp.verified, this.convertDebuggerLineToClient(mbp.line), undefined, this.createSource(this._runtime.sourceFile)); + bp.id = mbp.id; + this.sendEvent(new BreakpointEvent('new', bp)); + reply = `breakpoint created`; + } + } else { + const matches = /del +([0-9]+)/.exec(args.expression); + if (matches && matches.length === 2) { + const mbp = this._runtime.sourceFile ? this._runtime.clearBreakPoint(this._runtime.sourceFile, this.convertClientLineToDebugger(parseInt(matches[1]))) : undefined; + if (mbp) { + const bp = new Breakpoint(false); + bp.id = mbp.id; + this.sendEvent(new BreakpointEvent('removed', bp)); + reply = `breakpoint deleted`; + } + } + } + } + + response.body = { + result: reply ? reply : `evaluate(context: '${args.context}', '${args.expression}')`, + variablesReference: 0 + }; + this.sendResponse(response); + } + + protected dataBreakpointInfoRequest(response: DebugProtocol.DataBreakpointInfoResponse, args: DebugProtocol.DataBreakpointInfoArguments): void { + + response.body = { + dataId: null, + description: 'cannot break on data access', + accessTypes: undefined, + canPersist: false + }; + + if (args.variablesReference && args.name) { + const id = this._variableHandles.get(args.variablesReference); + if (id && id.startsWith('global_')) { + response.body.dataId = args.name; + response.body.description = args.name; + response.body.accessTypes = ['read']; + response.body.canPersist = false; + } + } + + this.sendResponse(response); + } + + protected setDataBreakpointsRequest(response: DebugProtocol.SetDataBreakpointsResponse, args: DebugProtocol.SetDataBreakpointsArguments): void { + + // clear all data breakpoints + this._runtime.clearAllDataBreakpoints(); + + response.body = { + breakpoints: [] + }; + + for (let dbp of args.breakpoints) { + // assume that id is the "address" to break on + const ok = this._runtime.setDataBreakpoint(dbp.dataId); + response.body.breakpoints.push({ + verified: ok + }); + } + + this.sendResponse(response); + } + + protected completionsRequest(response: DebugProtocol.CompletionsResponse, _args: DebugProtocol.CompletionsArguments): void { + + response.body = { + targets: [ + { + label: 'item 10', + sortText: '10' + }, + { + label: 'item 1', + sortText: '01' + }, + { + label: 'item 2', + sortText: '02' + } + ] + }; + this.sendResponse(response); + } + + protected cancelRequest(_response: DebugProtocol.CancelResponse, args: DebugProtocol.CancelArguments) { + if (args.requestId) { + this._cancelationTokens.set(args.requestId, true); + } + } + + //---- helpers + + private createSource(filePath: string): Source { + return new Source(basename(filePath), this.convertDebuggerPathToClient(filePath), undefined, undefined, 'mock-adapter-data'); + } +} + +//------------------------------------------------------------------------------------------------------------------------------------------ + + +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +export interface MockBreakpoint { + id: number; + line: number; + verified: boolean; +} + +export interface MockOutputEvent { + text: string; + filePath: string; + line: number; + column: number; +} + +/** + * A Mock runtime with minimal debugger functionality. + */ +export class MockRuntime { + + private stopOnEntry = new vscode.EventEmitter(); + onStopOnEntry: vscode.Event = this.stopOnEntry.event; + + private stopOnStep = new vscode.EventEmitter(); + onStopOnStep: vscode.Event = this.stopOnStep.event; + + private stopOnBreakpoint = new vscode.EventEmitter(); + onStopOnBreakpoint: vscode.Event = this.stopOnBreakpoint.event; + + private stopOnDataBreakpoint = new vscode.EventEmitter(); + onStopOnDataBreakpoint: vscode.Event = this.stopOnDataBreakpoint.event; + + private stopOnException = new vscode.EventEmitter(); + onStopOnException: vscode.Event = this.stopOnException.event; + + private breakpointValidated = new vscode.EventEmitter(); + onBreakpointValidated: vscode.Event = this.breakpointValidated.event; + + private output = new vscode.EventEmitter(); + onOutput: vscode.Event = this.output.event; + + private end = new vscode.EventEmitter(); + onEnd: vscode.Event = this.end.event; + + + // the initial (and one and only) file we are 'debugging' + private _sourceFile?: string; + public get sourceFile() { + return this._sourceFile; + } + + // the contents (= lines) of the one and only file + private _sourceLines: string[] = []; + + // This is the next line that will be 'executed' + private _currentLine = 0; + + // maps from sourceFile to array of Mock breakpoints + private _breakPoints = new Map(); + + // since we want to send breakpoint events, we will assign an id to every event + // so that the frontend can match events with breakpoints. + private _breakpointId = 1; + + private _breakAddresses = new Set(); + + constructor(private memfs: MemFS) { + } + + /** + * Start executing the given program. + */ + public start(program: string, stopOnEntry: boolean) { + + this.loadSource(program); + this._currentLine = -1; + + if (this._sourceFile) { + this.verifyBreakpoints(this._sourceFile); + } + + if (stopOnEntry) { + // we step once + this.step(false, this.stopOnEntry); + } else { + // we just start to run until we hit a breakpoint or an exception + this.continue(); + } + } + + /** + * Continue execution to the end/beginning. + */ + public continue(reverse = false) { + this.run(reverse, undefined); + } + + /** + * Step to the next/previous non empty line. + */ + public step(reverse = false, event = this.stopOnStep) { + this.run(reverse, event); + } + + /** + * Returns a fake 'stacktrace' where every 'stackframe' is a word from the current line. + */ + public stack(startFrame: number, endFrame: number): { frames: any[], count: number } { + + const words = this._sourceLines[this._currentLine].trim().split(/\s+/); + + const frames = new Array(); + // every word of the current line becomes a stack frame. + for (let i = startFrame; i < Math.min(endFrame, words.length); i++) { + const name = words[i]; // use a word of the line as the stackframe name + frames.push({ + index: i, + name: `${name}(${i})`, + file: this._sourceFile, + line: this._currentLine + }); + } + return { + frames: frames, + count: words.length + }; + } + + public getBreakpoints(_path: string, line: number): number[] { + + const l = this._sourceLines[line]; + + let sawSpace = true; + const bps: number[] = []; + for (let i = 0; i < l.length; i++) { + if (l[i] !== ' ') { + if (sawSpace) { + bps.push(i); + sawSpace = false; + } + } else { + sawSpace = true; + } + } + + return bps; + } + + /* + * Set breakpoint in file with given line. + */ + public setBreakPoint(path: string, line: number): MockBreakpoint { + + const bp = { verified: false, line, id: this._breakpointId++ }; + let bps = this._breakPoints.get(path); + if (!bps) { + bps = new Array(); + this._breakPoints.set(path, bps); + } + bps.push(bp); + + this.verifyBreakpoints(path); + + return bp; + } + + /* + * Clear breakpoint in file with given line. + */ + public clearBreakPoint(path: string, line: number): MockBreakpoint | undefined { + let bps = this._breakPoints.get(path); + if (bps) { + const index = bps.findIndex(bp => bp.line === line); + if (index >= 0) { + const bp = bps[index]; + bps.splice(index, 1); + return bp; + } + } + return undefined; + } + + /* + * Clear all breakpoints for file. + */ + public clearBreakpoints(path: string): void { + this._breakPoints.delete(path); + } + + /* + * Set data breakpoint. + */ + public setDataBreakpoint(address: string): boolean { + if (address) { + this._breakAddresses.add(address); + return true; + } + return false; + } + + /* + * Clear all data breakpoints. + */ + public clearAllDataBreakpoints(): void { + this._breakAddresses.clear(); + } + + // private methods + + private loadSource(file: string) { + if (this._sourceFile !== file) { + this._sourceFile = file; + + const _textDecoder = new TextDecoder(); + + const uri = vscode.Uri.parse(file); + const content = _textDecoder.decode(this.memfs.readFile(uri)); + this._sourceLines = content.split('\n'); + + //this._sourceLines = readFileSync(this._sourceFile).toString().split('\n'); + } + } + + /** + * Run through the file. + * If stepEvent is specified only run a single step and emit the stepEvent. + */ + private run(reverse = false, stepEvent?: vscode.EventEmitter): void { + if (reverse) { + for (let ln = this._currentLine - 1; ln >= 0; ln--) { + if (this.fireEventsForLine(ln, stepEvent)) { + this._currentLine = ln; + return; + } + } + // no more lines: stop at first line + this._currentLine = 0; + this.stopOnEntry.fire(); + } else { + for (let ln = this._currentLine + 1; ln < this._sourceLines.length; ln++) { + if (this.fireEventsForLine(ln, stepEvent)) { + this._currentLine = ln; + return; + } + } + // no more lines: run to end + this.end.fire(); + } + } + + private verifyBreakpoints(path: string): void { + let bps = this._breakPoints.get(path); + if (bps) { + this.loadSource(path); + bps.forEach(bp => { + if (!bp.verified && bp.line < this._sourceLines.length) { + const srcLine = this._sourceLines[bp.line].trim(); + + // if a line is empty or starts with '+' we don't allow to set a breakpoint but move the breakpoint down + if (srcLine.length === 0 || srcLine.indexOf('+') === 0) { + bp.line++; + } + // if a line starts with '-' we don't allow to set a breakpoint but move the breakpoint up + if (srcLine.indexOf('-') === 0) { + bp.line--; + } + // don't set 'verified' to true if the line contains the word 'lazy' + // in this case the breakpoint will be verified 'lazy' after hitting it once. + if (srcLine.indexOf('lazy') < 0) { + bp.verified = true; + this.breakpointValidated.fire(bp); + } + } + }); + } + } + + /** + * Fire events if line has a breakpoint or the word 'exception' is found. + * Returns true is execution needs to stop. + */ + private fireEventsForLine(ln: number, stepEvent?: vscode.EventEmitter): boolean { + + const line = this._sourceLines[ln].trim(); + + // if 'log(...)' found in source -> send argument to debug console + const matches = /log\((.*)\)/.exec(line); + if (matches && matches.length === 2) { + if (this._sourceFile) { + this.output.fire({ text: matches[1], filePath: this._sourceFile, line: ln, column: matches.index }); + } + } + + // if a word in a line matches a data breakpoint, fire a 'dataBreakpoint' event + const words = line.split(' '); + for (let word of words) { + if (this._breakAddresses.has(word)) { + this.stopOnDataBreakpoint.fire(); + return true; + } + } + + // if word 'exception' found in source -> throw exception + if (line.indexOf('exception') >= 0) { + this.stopOnException.fire(); + return true; + } + + // is there a breakpoint? + const breakpoints = this._sourceFile ? this._breakPoints.get(this._sourceFile) : undefined; + if (breakpoints) { + const bps = breakpoints.filter(bp => bp.line === ln); + if (bps.length > 0) { + + // send 'stopped' event + this.stopOnBreakpoint.fire(); + + // the following shows the use of 'breakpoint' events to update properties of a breakpoint in the UI + // if breakpoint is not yet verified, verify it now and send a 'breakpoint' update event + if (!bps[0].verified) { + bps[0].verified = true; + this.breakpointValidated.fire(bps[0]); + } + return true; + } + } + + // non-empty line + if (stepEvent && line.length > 0) { + stepEvent.fire(); + return true; + } + + // nothing interesting found -> continue + return false; + } +} diff --git a/extensions/vscode-web-playground/src/typings/ref.d.ts b/extensions/vscode-web-playground/src/typings/ref.d.ts new file mode 100644 index 00000000000..9abc416f7e8 --- /dev/null +++ b/extensions/vscode-web-playground/src/typings/ref.d.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/// +/// +/// +/// +/// diff --git a/extensions/vscode-web-playground/tsconfig.json b/extensions/vscode-web-playground/tsconfig.json new file mode 100644 index 00000000000..296ddb38fcb --- /dev/null +++ b/extensions/vscode-web-playground/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../shared.tsconfig.json", + "compilerOptions": { + "outDir": "./out" + }, + "include": [ + "src/**/*" + ] +} \ No newline at end of file diff --git a/extensions/vscode-web-playground/yarn.lock b/extensions/vscode-web-playground/yarn.lock new file mode 100644 index 00000000000..e595aafe8cd --- /dev/null +++ b/extensions/vscode-web-playground/yarn.lock @@ -0,0 +1,109 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/mocha@2.2.43": + version "2.2.43" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.43.tgz#03c54589c43ad048cbcbfd63999b55d0424eec27" + integrity sha512-xNlAmH+lRJdUMXClMTI9Y0pRqIojdxfm7DHsIxoB2iTzu3fnPmSMEN8SsSx0cdwV36d02PWCWaDUoZPDSln+xw== + +"@types/node@^12.11.7": + version "12.11.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a" + integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA== + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +charenc@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= + +crypt@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= + +debug@^2.2.0: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + +is-buffer@~1.1.1: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +lodash@^4.16.4: + version "4.17.10" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" + integrity sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg== + +md5@^2.1.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" + integrity sha1-U6s41f48iJG6RlMp6iP6wFQBJvk= + dependencies: + charenc "~0.0.1" + crypt "~0.0.1" + is-buffer "~1.1.1" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +mkdirp@~0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + +mocha-junit-reporter@^1.17.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/mocha-junit-reporter/-/mocha-junit-reporter-1.17.0.tgz#2e5149ed40fc5d2e3ca71e42db5ab1fec9c6d85c" + integrity sha1-LlFJ7UD8XS48px5C21qx/snG2Fw= + dependencies: + debug "^2.2.0" + md5 "^2.1.0" + mkdirp "~0.5.1" + strip-ansi "^4.0.0" + xml "^1.0.0" + +mocha-multi-reporters@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/mocha-multi-reporters/-/mocha-multi-reporters-1.1.7.tgz#cc7f3f4d32f478520941d852abb64d9988587d82" + integrity sha1-zH8/TTL0eFIJQdhSq7ZNmYhYfYI= + dependencies: + debug "^3.1.0" + lodash "^4.16.4" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +xml@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" + integrity sha1-eLpyAgApxbyHuKgaPPzXS0ovweU= diff --git a/package.json b/package.json index bc4220dcc3d..dbafe9f9620 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", - "version": "1.44.0", - "distro": "231a8c6522c74e2302d7a7360e4507b1b5991373", + "version": "1.45.0", + "distro": "67ae591b94d40ce954b1568937319660dff319b7", "author": { "name": "Microsoft Corporation" }, @@ -40,7 +40,8 @@ "https-proxy-agent": "^2.2.3", "iconv-lite": "0.5.0", "jschardet": "2.1.1", - "keytar": "^4.11.0", + "keytar": "github:rmacfarlane/node-keytar#334424bd26414923782f144110f4beda19168d24", + "minimist": "^1.2.5", "native-is-elevated": "0.4.1", "native-keymap": "2.1.1", "native-watchdog": "1.3.0", @@ -50,7 +51,6 @@ "spdlog": "^0.11.1", "sudo-prompt": "9.1.1", "v8-inspect-profiler": "^0.0.20", - "vscode-minimist": "^1.2.2", "vscode-nsfw": "1.2.8", "vscode-proxy-agent": "^0.5.2", "vscode-ripgrep": "^1.5.8", @@ -74,6 +74,7 @@ "@types/http-proxy-agent": "^2.0.1", "@types/iconv-lite": "0.0.1", "@types/keytar": "^4.4.0", + "@types/minimist": "^1.2.0", "@types/mocha": "2.2.39", "@types/node": "^12.11.7", "@types/sinon": "^1.16.36", @@ -141,7 +142,7 @@ "opn": "^6.0.0", "optimist": "0.3.5", "p-all": "^1.0.0", - "playwright": "0.11.0", + "playwright": "0.12.1", "pump": "^1.0.1", "queue": "3.0.6", "rcedit": "^1.1.0", @@ -150,13 +151,13 @@ "source-map": "^0.4.4", "style-loader": "^1.0.0", "ts-loader": "^4.4.2", - "typescript": "3.8.2", + "typescript": "^3.9.0-dev.20200327", "typescript-formatter": "7.1.0", "underscore": "^1.8.2", "vinyl": "^2.0.0", "vinyl-fs": "^3.0.0", "vsce": "1.48.0", - "vscode-debugprotocol": "1.39.0", + "vscode-debugprotocol": "^1.40.0", "vscode-nls-dev": "^3.3.1", "webpack": "^4.16.5", "webpack-cli": "^3.3.8", diff --git a/remote/package.json b/remote/package.json index 7d3a3755dd3..a33464db491 100644 --- a/remote/package.json +++ b/remote/package.json @@ -10,12 +10,12 @@ "https-proxy-agent": "^2.2.3", "iconv-lite": "0.5.0", "jschardet": "2.1.1", + "minimist": "^1.2.5", "native-watchdog": "1.3.0", "node-pty": "^0.10.0-beta2", "onigasm-umd": "2.2.5", "semver-umd": "^5.5.5", "spdlog": "^0.11.1", - "vscode-minimist": "^1.2.2", "vscode-nsfw": "1.2.8", "vscode-proxy-agent": "^0.5.2", "vscode-ripgrep": "^1.5.8", diff --git a/remote/yarn.lock b/remote/yarn.lock index d576786f982..efbec1027a3 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -239,6 +239,11 @@ minimist@0.0.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= +minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + mkdirp@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" @@ -364,11 +369,6 @@ universalify@^0.1.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== -vscode-minimist@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/vscode-minimist/-/vscode-minimist-1.2.2.tgz#65403f44f0c6010d259b2271d36eb5c6f4ad8aab" - integrity sha512-DXMNG2QgrXn1jOP12LzjVfvxVkzxv/0Qa27JrMBj/XP2esj+fJ/wP2T4YUH5derj73Lc96dC8F25WyfDUbTpxQ== - vscode-nsfw@1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/vscode-nsfw/-/vscode-nsfw-1.2.8.tgz#1bf452e72ff1304934de63692870d039a2d972af" diff --git a/scripts/code-web.js b/scripts/code-web.js index 4556e7d7e6d..b7441ea0158 100755 --- a/scripts/code-web.js +++ b/scripts/code-web.js @@ -13,7 +13,7 @@ const fs = require('fs'); const path = require('path'); const util = require('util'); const opn = require('opn'); -const minimist = require('vscode-minimist'); +const minimist = require('minimist'); const APP_ROOT = path.dirname(__dirname); const EXTENSIONS_ROOT = path.join(APP_ROOT, 'extensions'); @@ -145,7 +145,7 @@ async function handleRoot(req, res) { await Promise.all(extensionFolders.map(async extensionFolder => { try { const packageJSON = JSON.parse((await util.promisify(fs.readFile)(path.join(EXTENSIONS_ROOT, extensionFolder, 'package.json'))).toString()); - if (packageJSON.main && packageJSON.name !== 'vscode-api-tests') { + if (packageJSON.main && packageJSON.name !== 'vscode-web-playground') { return; // unsupported } diff --git a/scripts/code.sh b/scripts/code.sh index 0afe96bfcb6..3bc09f54f4e 100755 --- a/scripts/code.sh +++ b/scripts/code.sh @@ -55,6 +55,9 @@ function code() { function code-wsl() { + HOST_IP=$(powershell.exe -Command "& {(Get-NetIPAddress | Where-Object {\$_.InterfaceAlias -like '*WSL*' -and \$_.AddressFamily -eq 'IPv4'}).IPAddress | Write-Host -NoNewline}") + export DISPLAY="$HOST_IP:0" + # in a wsl shell ELECTRON="$ROOT/.build/electron/Code - OSS.exe" if [ -f "$ELECTRON" ]; then diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat index cb7271a9152..ea67daaf83e 100644 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -58,7 +58,7 @@ if %errorlevel% neq 0 exit /b %errorlevel% for /f "delims=" %%i in ('node -p "require('fs').realpathSync.native(require('os').tmpdir())"') do set TEMPDIR=%%i set GITWORKSPACE=%TEMPDIR%\git-%RANDOM% mkdir %GITWORKSPACE% -call "%INTEGRATION_TEST_ELECTRON_PATH%" %GITWORKSPACE% --extensionDevelopmentPath=%~dp0\..\extensions\git --extensionTestsPath=%~dp0\..\extensions\git\out\test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% +call "%INTEGRATION_TEST_ELECTRON_PATH%" %GITWORKSPACE% --extensionDevelopmentPath=%~dp0\..\extensions\git --extensionTestsPath=%~dp0\..\extensions\git\out\test --enable-proposed-api=vscode.git --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% if %errorlevel% neq 0 exit /b %errorlevel% :: Tests in commonJS (HTML, CSS, JSON language server tests...) diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index 6659549f001..003faddce62 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -49,7 +49,7 @@ fi "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-colorize-tests/test --extensionDevelopmentPath=$ROOT/extensions/vscode-colorize-tests --extensionTestsPath=$ROOT/extensions/vscode-colorize-tests/out --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/markdown-language-features/out/test/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/markdown-language-features --extensionTestsPath=$ROOT/extensions/markdown-language-features/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/emmet/out/test/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR -"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $(mktemp -d 2>/dev/null) --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $(mktemp -d 2>/dev/null) --enable-proposed-api=vscode.git --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR # Tests in commonJS cd $ROOT/extensions/css-language-features/server && $ROOT/scripts/node-electron.sh test/index.js diff --git a/src/bootstrap-fork.js b/src/bootstrap-fork.js index e0c6cf34c56..c4c69869d98 100644 --- a/src/bootstrap-fork.js +++ b/src/bootstrap-fork.js @@ -8,6 +8,9 @@ const bootstrap = require('./bootstrap'); +// Remove global paths from the node module lookup +bootstrap.removeGlobalNodeModuleLookupPaths(); + // Enable ASAR in our forked processes bootstrap.enableASARSupport(); @@ -139,13 +142,11 @@ function pipeLoggingToParent() { function handleExceptions() { // Handle uncaught exceptions - // @ts-ignore process.on('uncaughtException', function (err) { console.error('Uncaught Exception: ', err); }); // Handle unhandled promise rejections - // @ts-ignore process.on('unhandledRejection', function (reason) { console.error('Unhandled Promise Rejection: ', reason); }); diff --git a/src/bootstrap-window.js b/src/bootstrap-window.js index 0ab195f924f..ec93f2a6516 100644 --- a/src/bootstrap-window.js +++ b/src/bootstrap-window.js @@ -25,13 +25,12 @@ exports.assign = function assign(destination, source) { */ exports.load = function (modulePaths, resultCallback, options) { - // @ts-ignore const webFrame = require('electron').webFrame; const path = require('path'); const args = parseURLQueryArgs(); /** - * // configuration: IWindowConfiguration + * // configuration: INativeWindowConfiguration * @type {{ * zoomLevel?: number, * extensionDevelopmentPath?: string[], @@ -49,7 +48,6 @@ exports.load = function (modulePaths, resultCallback, options) { } // Error handler - // @ts-ignore process.on('uncaughtException', function (error) { onUnexpectedError(error, enableDeveloperTools); }); @@ -164,7 +162,6 @@ function parseURLQueryArgs() { */ function registerDeveloperKeybindings(disallowReloadKeybinding) { - // @ts-ignore const ipc = require('electron').ipcRenderer; const extractKey = function (e) { @@ -203,7 +200,6 @@ function registerDeveloperKeybindings(disallowReloadKeybinding) { function onUnexpectedError(error, enableDeveloperTools) { - // @ts-ignore const ipc = require('electron').ipcRenderer; if (enableDeveloperTools) { diff --git a/src/bootstrap.js b/src/bootstrap.js index b035adc9f41..cc63fc39422 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -53,6 +53,29 @@ exports.injectNodeModuleLookupPath = function (injectPath) { }; //#endregion +//#region Remove global paths from the node lookup paths + +exports.removeGlobalNodeModuleLookupPaths = function() { + // @ts-ignore + const Module = require('module'); + // @ts-ignore + const globalPaths = Module.globalPaths; + + // @ts-ignore + const originalResolveLookupPaths = Module._resolveLookupPaths; + + // @ts-ignore + Module._resolveLookupPaths = function (moduleName, parent) { + const paths = originalResolveLookupPaths(moduleName, parent); + let commonSuffixLength = 0; + while (commonSuffixLength < paths.length && paths[paths.length - 1 - commonSuffixLength] === globalPaths[globalPaths.length - 1 - commonSuffixLength]) { + commonSuffixLength++; + } + return paths.slice(0, paths.length - commonSuffixLength); + }; +}; +//#endregion + //#region Add support for using node_modules.asar /** * @param {string=} nodeModulesPath diff --git a/src/main.js b/src/main.js index bc268c8cd12..deb3798b087 100644 --- a/src/main.js +++ b/src/main.js @@ -175,7 +175,7 @@ function configureCommandlineSwitchesSync(cliArgs) { app.commandLine.appendSwitch('js-flags', jsFlags); } - // TODO@Ben TODO@Deepak Electron 7 workaround for https://github.com/microsoft/vscode/issues/88873 + // TODO@Deepak Electron 7 workaround for https://github.com/microsoft/vscode/issues/88873 app.commandLine.appendSwitch('disable-features', 'LayoutNG'); return argvConfig; @@ -316,7 +316,7 @@ function getUserDataPath(cliArgs) { * @returns {ParsedArgs} */ function parseCLIArgs() { - const minimist = require('vscode-minimist'); + const minimist = require('minimist'); return minimist(process.argv, { string: [ diff --git a/src/paths.js b/src/paths.js index 33c691bf72b..a6d3c052284 100644 --- a/src/paths.js +++ b/src/paths.js @@ -6,7 +6,7 @@ //@ts-check 'use strict'; -// @ts-ignore +// @ts-expect-error const pkg = require('../package.json'); const path = require('path'); const os = require('os'); @@ -33,4 +33,4 @@ function getDefaultUserDataPath(platform) { } exports.getAppDataPath = getAppDataPath; -exports.getDefaultUserDataPath = getDefaultUserDataPath; \ No newline at end of file +exports.getDefaultUserDataPath = getDefaultUserDataPath; diff --git a/src/tsconfig.base.json b/src/tsconfig.base.json index 73985c6c9af..19165d97b72 100644 --- a/src/tsconfig.base.json +++ b/src/tsconfig.base.json @@ -12,6 +12,14 @@ "vs/*": [ "./vs/*" ] - } + }, + "lib": [ + "ES2015", + "ES2017.String", + "ES2018.Promise", + "DOM", + "DOM.Iterable", + "WebWorker.ImportScripts" + ] } } diff --git a/src/tsconfig.json b/src/tsconfig.json index b8cc1caf2cd..5a2784b630f 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -4,13 +4,8 @@ "removeComments": false, "preserveConstEnums": true, "sourceMap": false, - "outDir": "../out", + "outDir": "../out/vs", "target": "es2017", - "lib": [ - "dom", - "es5", - "es2015.iterable" - ], "types": [ "keytar", "mocha", @@ -22,8 +17,5 @@ "include": [ "./typings", "./vs" - ], - "exclude": [ - "./typings/es6-promise.d.ts" ] } diff --git a/src/tsconfig.monaco.json b/src/tsconfig.monaco.json index a6430a44ccb..825a83761f2 100644 --- a/src/tsconfig.monaco.json +++ b/src/tsconfig.monaco.json @@ -8,25 +8,20 @@ "moduleResolution": "classic", "removeComments": false, "preserveConstEnums": true, - "target": "es5", + "target": "es6", "sourceMap": false, "declaration": true }, "include": [ "typings/require.d.ts", "typings/thenable.d.ts", - "typings/es6-promise.d.ts", - "typings/lib.es2018.promise.d.ts", "typings/lib.array-ext.d.ts", - "typings/lib.ie11_safe_es6.d.ts", "vs/css.d.ts", "vs/monaco.d.ts", "vs/nls.d.ts", "vs/editor/*", "vs/base/common/*", "vs/base/browser/*", - "vs/base/parts/tree/*", - "vs/base/parts/quickopen/*", "vs/platform/*/common/*", "vs/platform/*/browser/*" ], diff --git a/src/typings/es2015-proxy.d.ts b/src/typings/es2015-proxy.d.ts deleted file mode 100644 index 00f7c2b0642..00000000000 --- a/src/typings/es2015-proxy.d.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. - *--------------------------------------------------------------------------------------------*/ - -// from TypeScript: lib.es2015.proxy.d.ts - -interface ProxyHandler { - getPrototypeOf?(target: T): object | null; - setPrototypeOf?(target: T, v: any): boolean; - isExtensible?(target: T): boolean; - preventExtensions?(target: T): boolean; - getOwnPropertyDescriptor?(target: T, p: PropertyKey): PropertyDescriptor | undefined; - has?(target: T, p: PropertyKey): boolean; - get?(target: T, p: PropertyKey, receiver: any): any; - set?(target: T, p: PropertyKey, value: any, receiver: any): boolean; - deleteProperty?(target: T, p: PropertyKey): boolean; - defineProperty?(target: T, p: PropertyKey, attributes: PropertyDescriptor): boolean; - enumerate?(target: T): PropertyKey[]; - ownKeys?(target: T): PropertyKey[]; - apply?(target: T, thisArg: any, argArray?: any): any; - construct?(target: T, argArray: any, newTarget?: any): object; -} - -interface ProxyConstructor { - revocable(target: T, handler: ProxyHandler): { proxy: T; revoke: () => void; }; - new (target: T, handler: ProxyHandler): T; -} -declare var Proxy: ProxyConstructor; diff --git a/src/typings/es6-promise.d.ts b/src/typings/es6-promise.d.ts deleted file mode 100644 index 2d3271e2848..00000000000 --- a/src/typings/es6-promise.d.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Type definitions for es6-promise -// Project: https://github.com/jakearchibald/ES6-Promise -// Definitions by: François de Campredon , vvakame -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped - -interface Thenable { - then(onFulfilled?: (value: T) => U | Thenable, onRejected?: (error: any) => U | Thenable): Thenable; - then(onFulfilled?: (value: T) => U | Thenable, onRejected?: (error: any) => void): Thenable; -} - -declare class Promise implements Thenable { - /** - * If you call resolve in the body of the callback passed to the constructor, - * your promise is fulfilled with result object passed to resolve. - * If you call reject your promise is rejected with the object passed to reject. - * For consistency and debugging (eg stack traces), obj should be an instanceof Error. - * Any errors thrown in the constructor callback will be implicitly passed to reject(). - */ - constructor(callback: (resolve: (value?: T | Thenable) => void, reject: (error?: any) => void) => void); - - /** - * onFulfilled is called when/if "promise" resolves. onRejected is called when/if "promise" rejects. - * Both are optional, if either/both are omitted the next onFulfilled/onRejected in the chain is called. - * Both callbacks have a single parameter , the fulfillment value or rejection reason. - * "then" returns a new promise equivalent to the value you return from onFulfilled/onRejected after being passed through Promise.resolve. - * If an error is thrown in the callback, the returned promise rejects with that error. - * - * @param onFulfilled called when/if "promise" resolves - * @param onRejected called when/if "promise" rejects - */ - then(onFulfilled?: (value: T) => U | Thenable, onRejected?: (error: any) => U | Thenable): Promise; - then(onFulfilled?: (value: T) => U | Thenable, onRejected?: (error: any) => void): Promise; - - /** - * Sugar for promise.then(undefined, onRejected) - * - * @param onRejected called when/if "promise" rejects - */ - catch(onRejected?: (error: any) => U | Thenable): Promise; -} - -declare namespace Promise { - /** - * Make a new promise from the thenable. - * A thenable is promise-like in as far as it has a "then" method. - */ - function resolve(value: T | Thenable): Promise; - - /** - * - */ - function resolve(): Promise; - - /** - * Make a promise that rejects to obj. For consistency and debugging (eg stack traces), obj should be an instanceof Error - */ - function reject(error: any): Promise; - function reject(error: T): Promise; - - /** - * Make a promise that fulfills when every item in the array fulfills, and rejects if (and when) any item rejects. - * the array passed to all can be a mixture of promise-like objects and other objects. - * The fulfillment value is an array (in order) of fulfillment values. The rejection value is the first rejection value. - */ - function all(values: [T1 | Thenable, T2 | Thenable, T3 | Thenable, T4 | Thenable, T5 | Thenable, T6 | Thenable, T7 | Thenable, T8 | Thenable, T9 | Thenable, T10 | Thenable]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>; - function all(values: [T1 | Thenable, T2 | Thenable, T3 | Thenable, T4 | Thenable, T5 | Thenable, T6 | Thenable, T7 | Thenable, T8 | Thenable, T9 | Thenable]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9]>; - function all(values: [T1 | Thenable, T2 | Thenable, T3 | Thenable, T4 | Thenable, T5 | Thenable, T6 | Thenable, T7 | Thenable, T8 | Thenable]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8]>; - function all(values: [T1 | Thenable, T2 | Thenable, T3 | Thenable, T4 | Thenable, T5 | Thenable, T6 | Thenable, T7 | Thenable]): Promise<[T1, T2, T3, T4, T5, T6, T7]>; - function all(values: [T1 | Thenable, T2 | Thenable, T3 | Thenable, T4 | Thenable, T5 | Thenable, T6 | Thenable]): Promise<[T1, T2, T3, T4, T5, T6]>; - function all(values: [T1 | Thenable, T2 | Thenable, T3 | Thenable, T4 | Thenable, T5 | Thenable]): Promise<[T1, T2, T3, T4, T5]>; - function all(values: [T1 | Thenable, T2 | Thenable, T3 | Thenable, T4 | Thenable]): Promise<[T1, T2, T3, T4]>; - function all(values: [T1 | Thenable, T2 | Thenable, T3 | Thenable]): Promise<[T1, T2, T3]>; - function all(values: [T1 | Thenable, T2 | Thenable]): Promise<[T1, T2]>; - function all(values: (T | Thenable)[]): Promise; - - /** - * Make a Promise that fulfills when any item fulfills, and rejects if any item rejects. - */ - function race(promises: (T | Thenable)[]): Promise; -} - -declare module 'es6-promise' { - var foo: typeof Promise; // Temp variable to reference Promise in local context - namespace rsvp { - export var Promise: typeof foo; - export function polyfill(): void; - } - export = rsvp; -} diff --git a/src/typings/lib.es2018.promise.d.ts b/src/typings/lib.es2018.promise.d.ts deleted file mode 100644 index 9f7b2d38cb2..00000000000 --- a/src/typings/lib.es2018.promise.d.ts +++ /dev/null @@ -1,27 +0,0 @@ -/*! ***************************************************************************** -Copyright (c) Microsoft Corporation. All rights reserved. -Licensed under the Apache License, Version 2.0 (the "License"); you may not use -this file except in compliance with the License. You may obtain a copy of the -License at http://www.apache.org/licenses/LICENSE-2.0 - -THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED -WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, -MERCHANTABLITY OR NON-INFRINGEMENT. - -See the Apache Version 2.0 License for specific language governing permissions -and limitations under the License. -***************************************************************************** */ - -/** - * Represents the completion of an asynchronous operation - */ -interface Promise { - /** - * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The - * resolved value cannot be modified from the callback. - * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). - * @returns A Promise for the completion of the callback. - */ - finally(onfinally?: (() => void) | undefined | null): Promise; -} diff --git a/src/typings/lib.ie11_safe_es6.d.ts b/src/typings/lib.ie11_safe_es6.d.ts deleted file mode 100644 index 4d54d3c08ef..00000000000 --- a/src/typings/lib.ie11_safe_es6.d.ts +++ /dev/null @@ -1,821 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// Defined a subset of ES6 built ins that run in IE11 -// CHECK WITH http://kangax.github.io/compat-table/es6/#ie11 - -interface Map { - clear(): void; - delete(key: K): boolean; - forEach(callbackfn: (value: V, index: K, map: Map) => void, thisArg?: any): void; - get(key: K): V | undefined; - has(key: K): boolean; - set(key: K, value: V): Map; - readonly size: number; - - // not supported on IE11: - // entries(): IterableIterator<[K, V]>; - // keys(): IterableIterator; - // values(): IterableIterator; - // [Symbol.iterator]():IterableIterator<[K,V]>; - // [Symbol.toStringTag]: string; -} - -interface MapConstructor { - new (): Map; - readonly prototype: Map; - - // not supported on IE11: - // new (iterable: Iterable<[K, V]>): Map; -} -declare var Map: MapConstructor; - - -interface Set { - add(value: T): Set; - clear(): void; - delete(value: T): boolean; - forEach(callbackfn: (value: T, index: T, set: Set) => void, thisArg?: any): void; - has(value: T): boolean; - readonly size: number; - - // not supported on IE11: - // entries(): IterableIterator<[T, T]>; - // keys(): IterableIterator; - // values(): IterableIterator; - // [Symbol.iterator]():IterableIterator; - // [Symbol.toStringTag]: string; -} - -interface SetConstructor { - new (): Set; - readonly prototype: Set; - - // not supported on IE11: - // new (iterable: Iterable): Set; -} -declare var Set: SetConstructor; - - -interface WeakMap { - delete(key: K): boolean; - get(key: K): V | undefined; - has(key: K): boolean; - // IE11 doesn't return this - // set(key: K, value?: V): this; - set(key: K, value?: V): undefined; -} - -interface WeakMapConstructor { - new(): WeakMap; - new (): WeakMap; - // new (entries?: [K, V][]): WeakMap; - readonly prototype: WeakMap; -} -declare var WeakMap: WeakMapConstructor; - - -// /** -// * Represents a raw buffer of binary data, which is used to store data for the -// * different typed arrays. ArrayBuffers cannot be read from or written to directly, -// * but can be passed to a typed array or DataView Object to interpret the raw -// * buffer as needed. -// */ -// interface ArrayBuffer { -// /** -// * Read-only. The length of the ArrayBuffer (in bytes). -// */ -// readonly byteLength: number; - -// /** -// * Returns a section of an ArrayBuffer. -// */ -// slice(begin: number, end?: number): ArrayBuffer; -// } - -// interface ArrayBufferConstructor { -// readonly prototype: ArrayBuffer; -// new (byteLength: number): ArrayBuffer; -// isView(arg: any): arg is ArrayBufferView; -// } -// declare const ArrayBuffer: ArrayBufferConstructor; - -// interface ArrayBufferView { -// /** -// * The ArrayBuffer instance referenced by the array. -// */ -// buffer: ArrayBuffer; - -// /** -// * The length in bytes of the array. -// */ -// byteLength: number; - -// /** -// * The offset in bytes of the array. -// */ -// byteOffset: number; -// } - -// interface DataView { -// readonly buffer: ArrayBuffer; -// readonly byteLength: number; -// readonly byteOffset: number; -// /** -// * Gets the Float32 value at the specified byte offset from the start of the view. There is -// * no alignment constraint; multi-byte values may be fetched from any offset. -// * @param byteOffset The place in the buffer at which the value should be retrieved. -// */ -// getFloat32(byteOffset: number, littleEndian?: boolean): number; - -// /** -// * Gets the Float64 value at the specified byte offset from the start of the view. There is -// * no alignment constraint; multi-byte values may be fetched from any offset. -// * @param byteOffset The place in the buffer at which the value should be retrieved. -// */ -// getFloat64(byteOffset: number, littleEndian?: boolean): number; - -// /** -// * Gets the Int8 value at the specified byte offset from the start of the view. There is -// * no alignment constraint; multi-byte values may be fetched from any offset. -// * @param byteOffset The place in the buffer at which the value should be retrieved. -// */ -// getInt8(byteOffset: number): number; - -// /** -// * Gets the Int16 value at the specified byte offset from the start of the view. There is -// * no alignment constraint; multi-byte values may be fetched from any offset. -// * @param byteOffset The place in the buffer at which the value should be retrieved. -// */ -// getInt16(byteOffset: number, littleEndian?: boolean): number; -// /** -// * Gets the Int32 value at the specified byte offset from the start of the view. There is -// * no alignment constraint; multi-byte values may be fetched from any offset. -// * @param byteOffset The place in the buffer at which the value should be retrieved. -// */ -// getInt32(byteOffset: number, littleEndian?: boolean): number; - -// /** -// * Gets the Uint8 value at the specified byte offset from the start of the view. There is -// * no alignment constraint; multi-byte values may be fetched from any offset. -// * @param byteOffset The place in the buffer at which the value should be retrieved. -// */ -// getUint8(byteOffset: number): number; - -// /** -// * Gets the Uint16 value at the specified byte offset from the start of the view. There is -// * no alignment constraint; multi-byte values may be fetched from any offset. -// * @param byteOffset The place in the buffer at which the value should be retrieved. -// */ -// getUint16(byteOffset: number, littleEndian?: boolean): number; - -// /** -// * Gets the Uint32 value at the specified byte offset from the start of the view. There is -// * no alignment constraint; multi-byte values may be fetched from any offset. -// * @param byteOffset The place in the buffer at which the value should be retrieved. -// */ -// getUint32(byteOffset: number, littleEndian?: boolean): number; - -// /** -// * Stores an Float32 value at the specified byte offset from the start of the view. -// * @param byteOffset The place in the buffer at which the value should be set. -// * @param value The value to set. -// * @param littleEndian If false or undefined, a big-endian value should be written, -// * otherwise a little-endian value should be written. -// */ -// setFloat32(byteOffset: number, value: number, littleEndian?: boolean): void; - -// /** -// * Stores an Float64 value at the specified byte offset from the start of the view. -// * @param byteOffset The place in the buffer at which the value should be set. -// * @param value The value to set. -// * @param littleEndian If false or undefined, a big-endian value should be written, -// * otherwise a little-endian value should be written. -// */ -// setFloat64(byteOffset: number, value: number, littleEndian?: boolean): void; - -// /** -// * Stores an Int8 value at the specified byte offset from the start of the view. -// * @param byteOffset The place in the buffer at which the value should be set. -// * @param value The value to set. -// */ -// setInt8(byteOffset: number, value: number): void; - -// /** -// * Stores an Int16 value at the specified byte offset from the start of the view. -// * @param byteOffset The place in the buffer at which the value should be set. -// * @param value The value to set. -// * @param littleEndian If false or undefined, a big-endian value should be written, -// * otherwise a little-endian value should be written. -// */ -// setInt16(byteOffset: number, value: number, littleEndian?: boolean): void; - -// /** -// * Stores an Int32 value at the specified byte offset from the start of the view. -// * @param byteOffset The place in the buffer at which the value should be set. -// * @param value The value to set. -// * @param littleEndian If false or undefined, a big-endian value should be written, -// * otherwise a little-endian value should be written. -// */ -// setInt32(byteOffset: number, value: number, littleEndian?: boolean): void; - -// /** -// * Stores an Uint8 value at the specified byte offset from the start of the view. -// * @param byteOffset The place in the buffer at which the value should be set. -// * @param value The value to set. -// */ -// setUint8(byteOffset: number, value: number): void; - -// /** -// * Stores an Uint16 value at the specified byte offset from the start of the view. -// * @param byteOffset The place in the buffer at which the value should be set. -// * @param value The value to set. -// * @param littleEndian If false or undefined, a big-endian value should be written, -// * otherwise a little-endian value should be written. -// */ -// setUint16(byteOffset: number, value: number, littleEndian?: boolean): void; - -// /** -// * Stores an Uint32 value at the specified byte offset from the start of the view. -// * @param byteOffset The place in the buffer at which the value should be set. -// * @param value The value to set. -// * @param littleEndian If false or undefined, a big-endian value should be written, -// * otherwise a little-endian value should be written. -// */ -// setUint32(byteOffset: number, value: number, littleEndian?: boolean): void; -// } - -// interface DataViewConstructor { -// new (buffer: ArrayBuffer, byteOffset?: number, byteLength?: number): DataView; -// } -// declare const DataView: DataViewConstructor; - - -// /** -// * A typed array of 8-bit integer values. The contents are initialized to 0. If the requested -// * number of bytes could not be allocated an exception is raised. -// */ -// interface Int8Array { -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; - -// /** -// * The ArrayBuffer instance referenced by the array. -// */ -// readonly buffer: ArrayBuffer; - -// /** -// * The length in bytes of the array. -// */ -// readonly byteLength: number; - -// /** -// * The offset in bytes of the array. -// */ -// readonly byteOffset: number; - -// /** -// * The length of the array. -// */ -// readonly length: number; - -// /** -// * Sets a value or an array of values. -// * @param index The index of the location to set. -// * @param value The value to set. -// */ -// set(index: number, value: number): void; - -// /** -// * Sets a value or an array of values. -// * @param array A typed or untyped array of values to set. -// * @param offset The index in the current array at which the values are to be written. -// */ -// set(array: ArrayLike, offset?: number): void; - -// /** -// * Converts a number to a string by using the current locale. -// */ -// toLocaleString(): string; - -// /** -// * Returns a string representation of an array. -// */ -// toString(): string; - -// [index: number]: number; -// } -// interface Int8ArrayConstructor { -// readonly prototype: Int8Array; -// new (length: number): Int8Array; -// new (array: ArrayLike): Int8Array; -// new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Int8Array; - -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; - -// } -// declare const Int8Array: Int8ArrayConstructor; - -// /** -// * A typed array of 8-bit unsigned integer values. The contents are initialized to 0. If the -// * requested number of bytes could not be allocated an exception is raised. -// */ -// interface Uint8Array { -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; - -// /** -// * The ArrayBuffer instance referenced by the array. -// */ -// readonly buffer: ArrayBuffer; - -// /** -// * The length in bytes of the array. -// */ -// readonly byteLength: number; - -// /** -// * The offset in bytes of the array. -// */ -// readonly byteOffset: number; - -// /** -// * The length of the array. -// */ -// readonly length: number; - -// /** -// * Sets a value or an array of values. -// * @param index The index of the location to set. -// * @param value The value to set. -// */ -// set(index: number, value: number): void; - -// /** -// * Sets a value or an array of values. -// * @param array A typed or untyped array of values to set. -// * @param offset The index in the current array at which the values are to be written. -// */ -// set(array: ArrayLike, offset?: number): void; - -// /** -// * Converts a number to a string by using the current locale. -// */ -// toLocaleString(): string; - -// /** -// * Returns a string representation of an array. -// */ -// toString(): string; - -// [index: number]: number; -// } - -// interface Uint8ArrayConstructor { -// readonly prototype: Uint8Array; -// new (length: number): Uint8Array; -// new (array: ArrayLike): Uint8Array; -// new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Uint8Array; - -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; - -// } -// declare const Uint8Array: Uint8ArrayConstructor; - - -// /** -// * A typed array of 16-bit signed integer values. The contents are initialized to 0. If the -// * requested number of bytes could not be allocated an exception is raised. -// */ -// interface Int16Array { -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; - -// /** -// * The ArrayBuffer instance referenced by the array. -// */ -// readonly buffer: ArrayBuffer; - -// /** -// * The length in bytes of the array. -// */ -// readonly byteLength: number; - -// /** -// * The offset in bytes of the array. -// */ -// readonly byteOffset: number; - -// /** -// * The length of the array. -// */ -// readonly length: number; - -// /** -// * Sets a value or an array of values. -// * @param index The index of the location to set. -// * @param value The value to set. -// */ -// set(index: number, value: number): void; - -// /** -// * Sets a value or an array of values. -// * @param array A typed or untyped array of values to set. -// * @param offset The index in the current array at which the values are to be written. -// */ -// set(array: ArrayLike, offset?: number): void; - -// /** -// * Converts a number to a string by using the current locale. -// */ -// toLocaleString(): string; - -// /** -// * Returns a string representation of an array. -// */ -// toString(): string; - -// [index: number]: number; -// } - -// interface Int16ArrayConstructor { -// readonly prototype: Int16Array; -// new (length: number): Int16Array; -// new (array: ArrayLike): Int16Array; -// new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Int16Array; - -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; - -// } -// declare const Int16Array: Int16ArrayConstructor; - -// /** -// * A typed array of 16-bit unsigned integer values. The contents are initialized to 0. If the -// * requested number of bytes could not be allocated an exception is raised. -// */ -// interface Uint16Array { -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; - -// /** -// * The ArrayBuffer instance referenced by the array. -// */ -// readonly buffer: ArrayBuffer; - -// /** -// * The length in bytes of the array. -// */ -// readonly byteLength: number; - -// /** -// * The offset in bytes of the array. -// */ -// readonly byteOffset: number; - -// /** -// * The length of the array. -// */ -// readonly length: number; - -// /** -// * Sets a value or an array of values. -// * @param index The index of the location to set. -// * @param value The value to set. -// */ -// set(index: number, value: number): void; - -// /** -// * Sets a value or an array of values. -// * @param array A typed or untyped array of values to set. -// * @param offset The index in the current array at which the values are to be written. -// */ -// set(array: ArrayLike, offset?: number): void; - -// /** -// * Converts a number to a string by using the current locale. -// */ -// toLocaleString(): string; - -// /** -// * Returns a string representation of an array. -// */ -// toString(): string; - -// [index: number]: number; -// } - -// interface Uint16ArrayConstructor { -// readonly prototype: Uint16Array; -// new (length: number): Uint16Array; -// new (array: ArrayLike): Uint16Array; -// new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Uint16Array; - -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; - -// } -// declare const Uint16Array: Uint16ArrayConstructor; -// /** -// * A typed array of 32-bit signed integer values. The contents are initialized to 0. If the -// * requested number of bytes could not be allocated an exception is raised. -// */ -// interface Int32Array { -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; - -// /** -// * The ArrayBuffer instance referenced by the array. -// */ -// readonly buffer: ArrayBuffer; - -// /** -// * The length in bytes of the array. -// */ -// readonly byteLength: number; - -// /** -// * The offset in bytes of the array. -// */ -// readonly byteOffset: number; - -// /** -// * The length of the array. -// */ -// readonly length: number; - -// /** -// * Sets a value or an array of values. -// * @param index The index of the location to set. -// * @param value The value to set. -// */ -// set(index: number, value: number): void; - -// /** -// * Sets a value or an array of values. -// * @param array A typed or untyped array of values to set. -// * @param offset The index in the current array at which the values are to be written. -// */ -// set(array: ArrayLike, offset?: number): void; - -// /** -// * Converts a number to a string by using the current locale. -// */ -// toLocaleString(): string; - -// /** -// * Returns a string representation of an array. -// */ -// toString(): string; - -// [index: number]: number; -// } - -// interface Int32ArrayConstructor { -// readonly prototype: Int32Array; -// new (length: number): Int32Array; -// new (array: ArrayLike): Int32Array; -// new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Int32Array; - -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; -// } - -// declare const Int32Array: Int32ArrayConstructor; - -// /** -// * A typed array of 32-bit unsigned integer values. The contents are initialized to 0. If the -// * requested number of bytes could not be allocated an exception is raised. -// */ -// interface Uint32Array { -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; - -// /** -// * The ArrayBuffer instance referenced by the array. -// */ -// readonly buffer: ArrayBuffer; - -// /** -// * The length in bytes of the array. -// */ -// readonly byteLength: number; - -// /** -// * The offset in bytes of the array. -// */ -// readonly byteOffset: number; - -// /** -// * The length of the array. -// */ -// readonly length: number; - -// /** -// * Sets a value or an array of values. -// * @param index The index of the location to set. -// * @param value The value to set. -// */ -// set(index: number, value: number): void; - -// /** -// * Sets a value or an array of values. -// * @param array A typed or untyped array of values to set. -// * @param offset The index in the current array at which the values are to be written. -// */ -// set(array: ArrayLike, offset?: number): void; - -// /** -// * Converts a number to a string by using the current locale. -// */ -// toLocaleString(): string; - -// /** -// * Returns a string representation of an array. -// */ -// toString(): string; - -// [index: number]: number; -// } - -// interface Uint32ArrayConstructor { -// readonly prototype: Uint32Array; -// new (length: number): Uint32Array; -// new (array: ArrayLike): Uint32Array; -// new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Uint32Array; - -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; -// } - -// declare const Uint32Array: Uint32ArrayConstructor; - -// /** -// * A typed array of 32-bit float values. The contents are initialized to 0. If the requested number -// * of bytes could not be allocated an exception is raised. -// */ -// interface Float32Array { -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; - -// /** -// * The ArrayBuffer instance referenced by the array. -// */ -// readonly buffer: ArrayBuffer; - -// /** -// * The length in bytes of the array. -// */ -// readonly byteLength: number; - -// /** -// * The offset in bytes of the array. -// */ -// readonly byteOffset: number; - -// /** -// * The length of the array. -// */ -// readonly length: number; - -// /** -// * Sets a value or an array of values. -// * @param index The index of the location to set. -// * @param value The value to set. -// */ -// set(index: number, value: number): void; - -// /** -// * Sets a value or an array of values. -// * @param array A typed or untyped array of values to set. -// * @param offset The index in the current array at which the values are to be written. -// */ -// set(array: ArrayLike, offset?: number): void; - -// /** -// * Converts a number to a string by using the current locale. -// */ -// toLocaleString(): string; - -// /** -// * Returns a string representation of an array. -// */ -// toString(): string; - -// [index: number]: number; -// } - -// interface Float32ArrayConstructor { -// readonly prototype: Float32Array; -// new (length: number): Float32Array; -// new (array: ArrayLike): Float32Array; -// new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Float32Array; - -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; - -// } -// declare const Float32Array: Float32ArrayConstructor; - -// /** -// * A typed array of 64-bit float values. The contents are initialized to 0. If the requested -// * number of bytes could not be allocated an exception is raised. -// */ -// interface Float64Array { -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; - -// /** -// * The ArrayBuffer instance referenced by the array. -// */ -// readonly buffer: ArrayBuffer; - -// /** -// * The length in bytes of the array. -// */ -// readonly byteLength: number; - -// /** -// * The offset in bytes of the array. -// */ -// readonly byteOffset: number; - -// /** -// * The length of the array. -// */ -// readonly length: number; - -// /** -// * Sets a value or an array of values. -// * @param index The index of the location to set. -// * @param value The value to set. -// */ -// set(index: number, value: number): void; - -// /** -// * Sets a value or an array of values. -// * @param array A typed or untyped array of values to set. -// * @param offset The index in the current array at which the values are to be written. -// */ -// set(array: ArrayLike, offset?: number): void; - -// /** -// * Converts a number to a string by using the current locale. -// */ -// toLocaleString(): string; - -// /** -// * Returns a string representation of an array. -// */ -// toString(): string; - -// [index: number]: number; -// } - -// interface Float64ArrayConstructor { -// readonly prototype: Float64Array; -// new (length: number): Float64Array; -// new (array: ArrayLike): Float64Array; -// new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Float64Array; - -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; -// } - -// declare const Float64Array: Float64ArrayConstructor; diff --git a/src/typings/lib.webworker.importscripts.d.ts b/src/typings/lib.webworker.importscripts.d.ts deleted file mode 100644 index e84f717c9a4..00000000000 --- a/src/typings/lib.webworker.importscripts.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -/*! ***************************************************************************** -Copyright (c) Microsoft Corporation. All rights reserved. -Licensed under the Apache License, Version 2.0 (the "License"); you may not use -this file except in compliance with the License. You may obtain a copy of the -License at http://www.apache.org/licenses/LICENSE-2.0 - -THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED -WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, -MERCHANTABLITY OR NON-INFRINGEMENT. - -See the Apache Version 2.0 License for specific language governing permissions -and limitations under the License. -***************************************************************************** */ - - - - -///////////////////////////// -/// WorkerGlobalScope APIs -///////////////////////////// -// These are only available in a Web Worker -declare function importScripts(...urls: string[]): void; diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index 715019d3048..5f811db8d6b 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -110,10 +110,7 @@ export const onDidChangeFullscreen = WindowManager.INSTANCE.onDidChangeFullscree const userAgent = navigator.userAgent; -export const isIE = (userAgent.indexOf('Trident') >= 0); export const isEdge = (userAgent.indexOf('Edge/') >= 0); -export const isEdgeOrIE = isIE || isEdge; - export const isOpera = (userAgent.indexOf('Opera') >= 0); export const isFirefox = (userAgent.indexOf('Firefox') >= 0); export const isWebKit = (userAgent.indexOf('AppleWebKit') >= 0); diff --git a/src/vs/base/browser/canIUse.ts b/src/vs/base/browser/canIUse.ts index cf8211e919e..0a425479e59 100644 --- a/src/vs/base/browser/canIUse.ts +++ b/src/vs/base/browser/canIUse.ts @@ -27,10 +27,6 @@ export const BrowserFeatures = { || !!(navigator && navigator.clipboard && navigator.clipboard.readText) ), richText: (() => { - if (browser.isIE) { - return false; - } - if (browser.isEdge) { let index = navigator.userAgent.indexOf('Edge/'); let version = parseInt(navigator.userAgent.substring(index + 5, navigator.userAgent.indexOf('.', index)), 10); diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 69f1d93b2b7..09183772b77 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -8,7 +8,6 @@ import { domEvent } from 'vs/base/browser/event'; import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMouseEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { TimeoutTimer } from 'vs/base/common/async'; -import { CharCode } from 'vs/base/common/charCode'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -49,117 +48,7 @@ interface IDomClassList { toggleClass(node: HTMLElement | SVGElement, className: string, shouldHaveIt?: boolean): void; } -const _manualClassList = new class implements IDomClassList { - - private _lastStart: number = -1; - private _lastEnd: number = -1; - - private _findClassName(node: HTMLElement, className: string): void { - - let classes = node.className; - if (!classes) { - this._lastStart = -1; - return; - } - - className = className.trim(); - - let classesLen = classes.length, - classLen = className.length; - - if (classLen === 0) { - this._lastStart = -1; - return; - } - - if (classesLen < classLen) { - this._lastStart = -1; - return; - } - - if (classes === className) { - this._lastStart = 0; - this._lastEnd = classesLen; - return; - } - - let idx = -1, - idxEnd: number; - - while ((idx = classes.indexOf(className, idx + 1)) >= 0) { - - idxEnd = idx + classLen; - - // a class that is followed by another class - if ((idx === 0 || classes.charCodeAt(idx - 1) === CharCode.Space) && classes.charCodeAt(idxEnd) === CharCode.Space) { - this._lastStart = idx; - this._lastEnd = idxEnd + 1; - return; - } - - // last class - if (idx > 0 && classes.charCodeAt(idx - 1) === CharCode.Space && idxEnd === classesLen) { - this._lastStart = idx - 1; - this._lastEnd = idxEnd; - return; - } - - // equal - duplicate of cmp above - if (idx === 0 && idxEnd === classesLen) { - this._lastStart = 0; - this._lastEnd = idxEnd; - return; - } - } - - this._lastStart = -1; - } - - hasClass(node: HTMLElement, className: string): boolean { - this._findClassName(node, className); - return this._lastStart !== -1; - } - - addClasses(node: HTMLElement, ...classNames: string[]): void { - classNames.forEach(nameValue => nameValue.split(' ').forEach(name => this.addClass(node, name))); - } - - addClass(node: HTMLElement, className: string): void { - if (!node.className) { // doesn't have it for sure - node.className = className; - } else { - this._findClassName(node, className); // see if it's already there - if (this._lastStart === -1) { - node.className = node.className + ' ' + className; - } - } - } - - removeClass(node: HTMLElement, className: string): void { - this._findClassName(node, className); - if (this._lastStart === -1) { - return; // Prevent styles invalidation if not necessary - } else { - node.className = node.className.substring(0, this._lastStart) + node.className.substring(this._lastEnd); - } - } - - removeClasses(node: HTMLElement, ...classNames: string[]): void { - classNames.forEach(nameValue => nameValue.split(' ').forEach(name => this.removeClass(node, name))); - } - - toggleClass(node: HTMLElement, className: string, shouldHaveIt?: boolean): void { - this._findClassName(node, className); - if (this._lastStart !== -1 && (shouldHaveIt === undefined || !shouldHaveIt)) { - this.removeClass(node, className); - } - if (this._lastStart === -1 && (shouldHaveIt === undefined || shouldHaveIt)) { - this.addClass(node, className); - } - } -}; - -const _nativeClassList = new class implements IDomClassList { +const _classList: IDomClassList = new class implements IDomClassList { hasClass(node: HTMLElement, className: string): boolean { return Boolean(className) && node.classList && node.classList.contains(className); } @@ -191,9 +80,6 @@ const _nativeClassList = new class implements IDomClassList { } }; -// In IE11 there is only partial support for `classList` which makes us keep our -// custom implementation. Otherwise use the native implementation, see: http://caniuse.com/#search=classlist -const _classList: IDomClassList = browser.isIE ? _manualClassList : _nativeClassList; export const hasClass: (node: HTMLElement | SVGElement, className: string) => boolean = _classList.hasClass.bind(_classList); export const addClass: (node: HTMLElement | SVGElement, className: string) => void = _classList.addClass.bind(_classList); export const addClasses: (node: HTMLElement | SVGElement, ...classNames: string[]) => void = _classList.addClasses.bind(_classList); @@ -273,6 +159,11 @@ export let addStandardDisposableGenericMouseDownListner = function addStandardDi return addDisposableGenericMouseDownListner(node, wrapHandler, useCapture); }; +export let addStandardDisposableGenericMouseUpListner = function addStandardDisposableListener(node: HTMLElement, handler: (event: any) => void, useCapture?: boolean): IDisposable { + let wrapHandler = _wrapAsStandardMouseEvent(handler); + + return addDisposableGenericMouseUpListner(node, wrapHandler, useCapture); +}; export function addDisposableGenericMouseDownListner(node: EventTarget, handler: (event: any) => void, useCapture?: boolean): IDisposable { return addDisposableListener(node, platform.isIOS && BrowserFeatures.pointerEvents ? EventType.POINTER_DOWN : EventType.MOUSE_DOWN, handler, useCapture); } @@ -606,7 +497,12 @@ class SizeUtils { // ---------------------------------------------------------------------------------------- // Position & Dimension -export class Dimension { +export interface IDimension { + readonly width: number; + readonly height: number; +} + +export class Dimension implements IDimension { constructor( public readonly width: number, diff --git a/src/vs/base/browser/globalMouseMoveMonitor.ts b/src/vs/base/browser/globalMouseMoveMonitor.ts index 8fddd54b7b3..328ecaee03d 100644 --- a/src/vs/base/browser/globalMouseMoveMonitor.ts +++ b/src/vs/base/browser/globalMouseMoveMonitor.ts @@ -5,7 +5,6 @@ import * as dom from 'vs/base/browser/dom'; import * as platform from 'vs/base/common/platform'; -import * as browser from 'vs/base/browser/browser'; import { IframeUtils } from 'vs/base/browser/iframe'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -103,7 +102,7 @@ export class GlobalMouseMoveMonitor implements I for (const element of listenTo) { this._hooks.add(dom.addDisposableThrottledListener(element, mouseMove, (data: R) => { - if (!browser.isIE && data.buttons !== initialButtons) { + if (data.buttons !== initialButtons) { // Buttons state has changed in the meantime this.stopMonitoring(true); return; diff --git a/src/vs/base/browser/keyboardEvent.ts b/src/vs/base/browser/keyboardEvent.ts index 03bdffc95ed..90a84b5890f 100644 --- a/src/vs/base/browser/keyboardEvent.ts +++ b/src/vs/base/browser/keyboardEvent.ts @@ -145,9 +145,7 @@ let INVERSE_KEY_CODE_MAP: KeyCode[] = new Array(KeyCode.MAX_VALUE); */ define(229, KeyCode.KEY_IN_COMPOSITION); - if (browser.isIE) { - define(91, KeyCode.Meta); - } else if (browser.isFirefox) { + if (browser.isFirefox) { define(59, KeyCode.US_SEMICOLON); define(107, KeyCode.US_EQUAL); define(109, KeyCode.US_MINUS); diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index 64213751cdf..b40e1c1e8cd 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -58,12 +58,16 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende return href; // no tranformation performed } if (isDomUri) { - uri = DOM.asDomUri(uri); + // this URI will end up as "src"-attribute of a dom node + // and because of that special rewriting needs to be done + // so that the URI uses a protocol that's understood by + // browsers (like http or https) + return DOM.asDomUri(uri).toString(true); } if (uri.query) { uri = uri.with({ query: _uriMassage(uri.query) }); } - return uri.toString(true); + return uri.toString(); }; // signal to code-block render that the diff --git a/src/vs/base/browser/touch.ts b/src/vs/base/browser/touch.ts index 8eb1262df1f..9cf55c77875 100644 --- a/src/vs/base/browser/touch.ts +++ b/src/vs/base/browser/touch.ts @@ -247,7 +247,7 @@ export class Gesture extends Disposable { } private newGestureEvent(type: string, initialTarget?: EventTarget): GestureEvent { - let event = (document.createEvent('CustomEvent')); + let event = document.createEvent('CustomEvent') as unknown as GestureEvent; event.initEvent(type, false, true); event.initialTarget = initialTarget; event.tapCount = 0; diff --git a/src/vs/base/browser/ui/actionbar/actionbar.css b/src/vs/base/browser/ui/actionbar/actionbar.css index 623ff4d7421..9b304e81a80 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.css +++ b/src/vs/base/browser/ui/actionbar/actionbar.css @@ -45,6 +45,11 @@ display: inline-block; } +.monaco-action-bar .action-item .codicon { + display: flex; + align-items: center; +} + .monaco-action-bar .action-label { font-size: 11px; margin-right: 4px; diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 523a242daaa..d277b9d2fca 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -134,6 +134,18 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { } })); + if (platform.isMacintosh) { + // macOS: allow to trigger the button when holding Ctrl+key and pressing the + // main mouse button. This is for scenarios where e.g. some interaction forces + // the Ctrl+key to be pressed and hold but the user still wants to interact + // with the actions (for example quick access in quick navigation mode). + this._register(DOM.addDisposableListener(element, DOM.EventType.CONTEXT_MENU, e => { + if (e.button === 0 && e.ctrlKey === true) { + this.onClick(e); + } + })); + } + this._register(DOM.addDisposableListener(element, DOM.EventType.CLICK, e => { DOM.EventHelper.stop(e, true); // See https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Interact_with_the_clipboard @@ -510,7 +522,7 @@ export class ActionBar extends Disposable implements IActionRunner { } else if (event.equals(nextKey)) { this.focusNext(); } else if (event.equals(KeyCode.Escape)) { - this.cancel(); + this._onDidCancel.fire(); } else if (this.isTriggerKeyEvent(event)) { // Staying out of the else branch even if not triggered if (this.options.triggerKeys && this.options.triggerKeys.keyDown) { @@ -633,8 +645,7 @@ export class ActionBar extends Disposable implements IActionRunner { // Prevent native context menu on actions this._register(DOM.addDisposableListener(actionViewItemElement, DOM.EventType.CONTEXT_MENU, (e: DOM.EventLike) => { - e.preventDefault(); - e.stopPropagation(); + DOM.EventHelper.stop(e, true); })); let item: IActionViewItem | undefined; @@ -813,14 +824,6 @@ export class ActionBar extends Disposable implements IActionRunner { } } - private cancel(): void { - if (document.activeElement instanceof HTMLElement) { - document.activeElement.blur(); // remove focus from focused action - } - - this._onDidCancel.fire(); - } - run(action: IAction, context?: unknown): Promise { return this._actionRunner.run(action, context); } @@ -881,3 +884,51 @@ export class SelectActionViewItem extends BaseActionViewItem { this.selectBox.render(container); } } + +export function prepareActions(actions: IAction[]): IAction[] { + if (!actions.length) { + return actions; + } + + // Clean up leading separators + let firstIndexOfAction = -1; + for (let i = 0; i < actions.length; i++) { + if (actions[i].id === Separator.ID) { + continue; + } + + firstIndexOfAction = i; + break; + } + + if (firstIndexOfAction === -1) { + return []; + } + + actions = actions.slice(firstIndexOfAction); + + // Clean up trailing separators + for (let h = actions.length - 1; h >= 0; h--) { + const isSeparator = actions[h].id === Separator.ID; + if (isSeparator) { + actions.splice(h, 1); + } else { + break; + } + } + + // Clean up separator duplicates + let foundAction = false; + for (let k = actions.length - 1; k >= 0; k--) { + const isSeparator = actions[k].id === Separator.ID; + if (isSeparator && !foundAction) { + actions.splice(k, 1); + } else if (!isSeparator) { + foundAction = true; + } else if (isSeparator) { + foundAction = false; + } + } + + return actions; +} diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index 5ce81d72e66..b1cffa15e90 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -81,7 +81,8 @@ export class BreadcrumbsWidget { private _dimension: dom.Dimension | undefined; constructor( - container: HTMLElement + container: HTMLElement, + horizontalScrollbarSize: number, ) { this._domNode = document.createElement('div'); this._domNode.className = 'monaco-breadcrumbs'; @@ -90,7 +91,7 @@ export class BreadcrumbsWidget { this._scrollable = new DomScrollableElement(this._domNode, { vertical: ScrollbarVisibility.Hidden, horizontal: ScrollbarVisibility.Auto, - horizontalScrollbarSize: 3, + horizontalScrollbarSize, useShadows: false, scrollYToX: true }); @@ -106,6 +107,12 @@ export class BreadcrumbsWidget { this._disposables.add(focusTracker.onDidFocus(_ => this._onDidChangeFocus.fire(true))); } + setHorizontalScrollbarSize(size: number) { + this._scrollable.updateOptions({ + horizontalScrollbarSize: size + }); + } + dispose(): void { this._disposables.dispose(); dispose(this._pendingLayout); diff --git a/src/vs/base/browser/ui/breadcrumbs/tree-collapsed-dark.svg b/src/vs/base/browser/ui/breadcrumbs/tree-collapsed-dark.svg deleted file mode 100644 index 243be1451cc..00000000000 --- a/src/vs/base/browser/ui/breadcrumbs/tree-collapsed-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/base/browser/ui/breadcrumbs/tree-collapsed-hc.svg b/src/vs/base/browser/ui/breadcrumbs/tree-collapsed-hc.svg deleted file mode 100644 index 40ba72b7086..00000000000 --- a/src/vs/base/browser/ui/breadcrumbs/tree-collapsed-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/base/browser/ui/breadcrumbs/tree-collapsed-light.svg b/src/vs/base/browser/ui/breadcrumbs/tree-collapsed-light.svg deleted file mode 100644 index 0d746558a4f..00000000000 --- a/src/vs/base/browser/ui/breadcrumbs/tree-collapsed-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/standalone/browser/quickOpen/quickOutline.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon-modifications.css similarity index 88% rename from src/vs/editor/standalone/browser/quickOpen/quickOutline.css rename to src/vs/base/browser/ui/codiconLabel/codicon/codicon-modifications.css index 75309ce3318..950493dd6be 100644 --- a/src/vs/editor/standalone/browser/quickOpen/quickOutline.css +++ b/src/vs/base/browser/ui/codiconLabel/codicon/codicon-modifications.css @@ -3,6 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-quick-open-widget { - font-size: 13px; +.codicon-wrench-subaction { + opacity: 0.5; } diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css index b71ef2dafa5..8961f91495c 100644 --- a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css +++ b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css @@ -5,7 +5,7 @@ @font-face { font-family: "codicon"; - src: url("./codicon.ttf?b5dd8f5aa953889dc1f4c9fa9b44d3dd") format("truetype"); + src: url("./codicon.ttf?a76e99e42eab7c1a55601640b708d820") format("truetype"); } .codicon[class*='codicon-'] { @@ -360,6 +360,8 @@ .codicon-symbol-misc:before { content: "\eb63" } .codicon-symbol-operator:before { content: "\eb64" } .codicon-symbol-property:before { content: "\eb65" } +.codicon-wrench:before { content: "\eb65" } +.codicon-wrench-subaction:before { content: "\eb65" } .codicon-symbol-snippet:before { content: "\eb66" } .codicon-tasklist:before { content: "\eb67" } .codicon-telescope:before { content: "\eb68" } @@ -414,7 +416,12 @@ .codicon-feedback:before { content: "\eb96" } .codicon-group-by-ref-type:before { content: "\eb97" } .codicon-ungroup-by-ref-type:before { content: "\eb98" } -.codicon-bell-dot:before { content: "\f101" } -.codicon-bell-progress:before { content: "\f102" } -.codicon-debug-alt-2:before { content: "\f103" } -.codicon-debug-alt:before { content: "\f104" } +.codicon-account:before { content: "\eb99" } +.codicon-bell-dot:before { content: "\eb9a" } +.codicon-debug-console:before { content: "\eb9b" } +.codicon-library:before { content: "\eb9c" } +.codicon-output:before { content: "\eb9d" } +.codicon-run-all:before { content: "\eb9e" } +.codicon-sync-ignored:before { content: "\eb9f" } +.codicon-debug-alt-2:before { content: "\f101" } +.codicon-debug-alt:before { content: "\f102" } diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf index 845205f5b3a..d4358ace622 100644 Binary files a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf and b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf differ diff --git a/src/vs/base/browser/ui/codiconLabel/codiconLabel.ts b/src/vs/base/browser/ui/codiconLabel/codiconLabel.ts index ccec1f655bb..2fce361f76d 100644 --- a/src/vs/base/browser/ui/codiconLabel/codiconLabel.ts +++ b/src/vs/base/browser/ui/codiconLabel/codiconLabel.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./codicon/codicon'; +import 'vs/css!./codicon/codicon-modifications'; import 'vs/css!./codicon/codicon-animations'; import { escape } from 'vs/base/common/strings'; import { renderCodicons } from 'vs/base/common/codicons'; diff --git a/src/vs/base/browser/ui/dialog/dialog.css b/src/vs/base/browser/ui/dialog/dialog.css index 876f2168fe3..911b5616e70 100644 --- a/src/vs/base/browser/ui/dialog/dialog.css +++ b/src/vs/base/browser/ui/dialog/dialog.css @@ -25,7 +25,7 @@ flex-direction: column-reverse; width: min-content; min-width: 500px; - max-width: 90%; + max-width: 90vw; min-height: 75px; padding: 10px; transform: translate3d(0px, 0px, 0px); diff --git a/src/vs/base/browser/ui/dropdown/dropdown.ts b/src/vs/base/browser/ui/dropdown/dropdown.ts index c36015f710b..f54d01606cb 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.ts +++ b/src/vs/base/browser/ui/dropdown/dropdown.ts @@ -271,7 +271,7 @@ export class DropdownMenu extends BaseDropdown { } export class DropdownMenuActionViewItem extends BaseActionViewItem { - private menuActionsOrProvider: any; + private menuActionsOrProvider: ReadonlyArray | IActionProvider; private dropdownMenu: DropdownMenu | undefined; private contextMenuProvider: IContextMenuProvider; private actionViewItemProvider?: IActionViewItemProvider; @@ -317,7 +317,7 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { if (Array.isArray(this.menuActionsOrProvider)) { options.actions = this.menuActionsOrProvider; } else { - options.actionProvider = this.menuActionsOrProvider; + options.actionProvider = this.menuActionsOrProvider as IActionProvider; } this.dropdownMenu = this._register(new DropdownMenu(container, options)); @@ -341,7 +341,7 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { } } - setActionContext(newContext: any): void { + setActionContext(newContext: unknown): void { super.setActionContext(newContext); if (this.dropdownMenu) { diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index af06f86e2fa..93c4230477b 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -23,6 +23,7 @@ export interface IIconLabelValueOptions { hideIcon?: boolean; extraClasses?: string[]; italic?: boolean; + strikethrough?: boolean; matches?: IMatch[]; labelEscapeNewLines?: boolean; descriptionMatches?: IMatch[]; @@ -136,6 +137,10 @@ export class IconLabel extends Disposable { if (options.italic) { classes.push('italic'); } + + if (options.strikethrough) { + classes.push('strikethrough'); + } } this.domNode.className = classes.join(' '); @@ -197,7 +202,7 @@ class Label { const l = label[i]; const id = options?.domId && `${options?.domId}_${i}`; - dom.append(this.container, dom.$('a.label-name', { id, 'data-icon-label-count': label.length, 'data-icon-label-index': i }, l)); + dom.append(this.container, dom.$('a.label-name', { id, 'data-icon-label-count': label.length, 'data-icon-label-index': i, 'role': 'treeitem' }, l)); if (i < label.length - 1) { dom.append(this.container, dom.$('span.label-separator', undefined, options?.separator || '/')); @@ -265,7 +270,7 @@ class LabelWithHighlights { const m = matches ? matches[i] : undefined; const id = options?.domId && `${options?.domId}_${i}`; - const name = dom.$('a.label-name', { id, 'data-icon-label-count': label.length, 'data-icon-label-index': i }); + const name = dom.$('a.label-name', { id, 'data-icon-label-count': label.length, 'data-icon-label-index': i, 'role': 'treeitem' }); const highlightedLabel = new HighlightedLabel(dom.append(this.container, name), this.supportCodicons); highlightedLabel.set(l, m, options?.title, options?.labelEscapeNewLines); diff --git a/src/vs/base/browser/ui/iconLabel/iconlabel.css b/src/vs/base/browser/ui/iconLabel/iconlabel.css index 8ee16195b5c..3c1392a2ff7 100644 --- a/src/vs/base/browser/ui/iconLabel/iconlabel.css +++ b/src/vs/base/browser/ui/iconLabel/iconlabel.css @@ -60,6 +60,11 @@ font-style: italic; } +.monaco-icon-label.strikethrough > .monaco-icon-label-container > .monaco-icon-name-container > .label-name, +.monaco-icon-label.strikethrough > .monaco-icon-description-container > .label-description { + text-decoration: line-through; +} + .monaco-icon-label::after { opacity: 0.75; font-size: 90%; @@ -69,16 +74,12 @@ } /* make sure selection color wins when a label is being selected */ -.monaco-tree.focused .selected .monaco-icon-label, /* tree */ -.monaco-tree.focused .selected .monaco-icon-label::after, .monaco-list:focus .selected .monaco-icon-label, /* list */ .monaco-list:focus .selected .monaco-icon-label::after { color: inherit !important; } -.monaco-tree-row.focused.selected .label-description, -.monaco-tree-row.selected .label-description, .monaco-list-row.focused.selected .label-description, .monaco-list-row.selected .label-description { opacity: .8; diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index cd57d7f772f..b188a00df7e 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -6,7 +6,6 @@ import 'vs/css!./inputBox'; import * as nls from 'vs/nls'; -import * as Bal from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; import { MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer'; import { renderFormattedText, renderText } from 'vs/base/browser/formattedTextRenderer'; @@ -212,14 +211,6 @@ export class InputBox extends Widget { this.onblur(this.input, () => this.onBlur()); this.onfocus(this.input, () => this.onFocus()); - // Add placeholder shim for IE because IE decides to hide the placeholder on focus (we dont want that!) - if (this.placeholder && Bal.isIE) { - this.onclick(this.input, (e) => { - dom.EventHelper.stop(e, true); - this.input.focus(); - }); - } - this.ignoreGesture(this.input); setTimeout(() => this.updateMirror(), 0); @@ -257,6 +248,10 @@ export class InputBox extends Widget { } } + public getAriaLabel(): string { + return this.ariaLabel; + } + public get mirrorElement(): HTMLElement | undefined { return this.mirror; } @@ -300,6 +295,10 @@ export class InputBox extends Widget { } } + public isSelectionAtEnd(): boolean { + return this.input.selectionEnd === this.input.value.length && this.input.selectionStart === this.input.selectionEnd; + } + public enable(): void { this.input.removeAttribute('disabled'); } @@ -378,18 +377,6 @@ export class InputBox extends Widget { const styles = this.stylesForType(this.message.type); this.element.style.border = styles.border ? `1px solid ${styles.border}` : ''; - // ARIA Support - let alertText: string; - if (message.type === MessageType.ERROR) { - alertText = nls.localize('alertErrorMessage', "Error: {0}", message.content); - } else if (message.type === MessageType.WARNING) { - alertText = nls.localize('alertWarningMessage', "Warning: {0}", message.content); - } else { - alertText = nls.localize('alertInfoMessage', "Info: {0}", message.content); - } - - aria.alert(alertText); - if (this.hasFocus() || force) { this._showMessage(); } @@ -490,6 +477,18 @@ export class InputBox extends Widget { layout: layout }); + // ARIA Support + let alertText: string; + if (this.message.type === MessageType.ERROR) { + alertText = nls.localize('alertErrorMessage', "Error: {0}", this.message.content); + } else if (this.message.type === MessageType.WARNING) { + alertText = nls.localize('alertWarningMessage', "Warning: {0}", this.message.content); + } else { + alertText = nls.localize('alertInfoMessage', "Info: {0}", this.message.content); + } + + aria.alert(alertText); + this.state = 'open'; } diff --git a/src/vs/base/browser/ui/list/list.ts b/src/vs/base/browser/ui/list/list.ts index f07e22e1bee..5f86ea5989b 100644 --- a/src/vs/base/browser/ui/list/list.ts +++ b/src/vs/base/browser/ui/list/list.ts @@ -64,6 +64,9 @@ export interface IIdentityProvider { } export enum ListAriaRootRole { + /** default list structure role */ + LIST = 'list', + /** default tree structure role */ TREE = 'tree', diff --git a/src/vs/base/browser/ui/list/listPaging.ts b/src/vs/base/browser/ui/list/listPaging.ts index 352dd6b8e34..1132d9ecfa2 100644 --- a/src/vs/base/browser/ui/list/listPaging.ts +++ b/src/vs/base/browser/ui/list/listPaging.ts @@ -114,16 +114,16 @@ export class PagedList implements IDisposable { return this.list.onDidDispose; } - get onFocusChange(): Event> { - return Event.map(this.list.onFocusChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes })); + get onDidChangeFocus(): Event> { + return Event.map(this.list.onDidChangeFocus, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes })); } - get onOpen(): Event> { + get onDidOpen(): Event> { return Event.map(this.list.onDidOpen, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent })); } - get onSelectionChange(): Event> { - return Event.map(this.list.onSelectionChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes })); + get onDidChangeSelection(): Event> { + return Event.map(this.list.onDidChangeSelection, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes })); } get onPin(): Event> { @@ -191,8 +191,8 @@ export class PagedList implements IDisposable { return this.list.getFocus(); } - setSelection(indexes: number[]): void { - this.list.setSelection(indexes); + setSelection(indexes: number[], browserEvent?: UIEvent): void { + this.list.setSelection(indexes, browserEvent); } getSelection(): number[] { diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index a07235210e3..fa450643665 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -275,22 +275,42 @@ export class ListView implements ISpliceable, IDisposable { this.layout(); } + updateOptions(options: IListViewOptions) { + if (options.additionalScrollHeight !== undefined) { + this.additionalScrollHeight = options.additionalScrollHeight; + } + } + triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) { this.scrollableElement.triggerScrollFromMouseWheelEvent(browserEvent); } - updateElementHeight(index: number, size: number): void { + updateElementHeight(index: number, size: number, anchorIndex: number | null): void { if (this.items[index].size === size) { return; } const lastRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight); - const heightDiff = index < lastRenderRange.start ? size - this.items[index].size : 0; + let heightDiff = 0; + + if (index < lastRenderRange.start) { + // do not scroll the viewport if resized element is out of viewport + heightDiff = size - this.items[index].size; + } else { + if (anchorIndex !== null && anchorIndex > index && anchorIndex <= lastRenderRange.end) { + // anchor in viewport + // resized elemnet in viewport and above the anchor + heightDiff = size - this.items[index].size; + } else { + heightDiff = 0; + } + } + this.rangeMap.splice(index, 1, [{ size: size }]); this.items[index].size = size; - this.render(lastRenderRange, this.lastRenderTop + heightDiff, this.lastRenderHeight, undefined, undefined, true); + this.render(lastRenderRange, Math.max(0, this.lastRenderTop + heightDiff), this.lastRenderHeight, undefined, undefined, true); this.eventuallyUpdateScrollDimensions(); @@ -1134,9 +1154,13 @@ export class ListView implements ISpliceable, IDisposable { return 0; } + if (!!this.virtualDelegate.hasDynamicHeight && !this.virtualDelegate.hasDynamicHeight(item.element)) { + return 0; + } + const size = item.size; - if (item.row && item.row.domNode) { + if (!this.setRowHeight && item.row && item.row.domNode) { let newSize = item.row.domNode.offsetHeight; item.size = newSize; item.lastDynamicHeightWidth = this.renderWidth; diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 29263345a5e..5e415050c0f 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -180,19 +180,21 @@ class Trait implements ISpliceable, IDisposable { } } -class FocusTrait extends Trait { +class SelectionTrait extends Trait { - constructor() { - super('focused'); + constructor(private setAriaSelected: boolean) { + super('selected'); } renderIndex(index: number, container: HTMLElement): void { super.renderIndex(index, container); - if (this.contains(index)) { - container.setAttribute('aria-selected', 'true'); - } else { - container.removeAttribute('aria-selected'); + if (this.setAriaSelected) { + if (this.contains(index)) { + container.setAttribute('aria-selected', 'true'); + } else { + container.setAttribute('aria-selected', 'false'); + } } } } @@ -852,6 +854,7 @@ export interface IListOptions { readonly mouseSupport?: boolean; readonly horizontalScrolling?: boolean; readonly ariaProvider?: IAriaProvider; + readonly additionalScrollHeight?: number; } export interface IListStyles { @@ -901,7 +904,7 @@ const DefaultOptions = { onDragOver() { return false; }, drop() { } }, - ariaRootRole: ListAriaRootRole.TREE + ariaRootRole: ListAriaRootRole.LIST }; // TODO@Joao: move these utils into a SortedArray class @@ -1108,6 +1111,7 @@ class ListViewDragAndDrop implements IListViewDragAndDrop { export interface IListOptionsUpdate { readonly enableKeyboardNavigation?: boolean; readonly automaticKeyboardNavigation?: boolean; + readonly additionalScrollHeight?: number; } export class List implements ISpliceable, IDisposable { @@ -1123,11 +1127,11 @@ export class List implements ISpliceable, IDisposable { protected readonly disposables = new DisposableStore(); - @memoize get onFocusChange(): Event> { + @memoize get onDidChangeFocus(): Event> { return Event.map(this.eventBufferer.wrapEvent(this.focus.onChange), e => this.toListEvent(e)); } - @memoize get onSelectionChange(): Event> { + @memoize get onDidChangeSelection(): Event> { return Event.map(this.eventBufferer.wrapEvent(this.selection.onChange), e => this.toListEvent(e)); } @@ -1198,8 +1202,8 @@ export class List implements ISpliceable, IDisposable { renderers: IListRenderer[], private _options: IListOptions = DefaultOptions ) { - this.focus = new FocusTrait(); - this.selection = new Trait('selected'); + this.selection = new SelectionTrait(this._options.ariaRole !== 'listbox'); + this.focus = new Trait('focused'); mixin(_options, defaultStyles, false); @@ -1225,7 +1229,7 @@ export class List implements ISpliceable, IDisposable { this.view = new ListView(container, virtualDelegate, renderers, viewOptions); if (typeof _options.ariaRole !== 'string') { - this.view.domNode.setAttribute('role', ListAriaRootRole.TREE); + this.view.domNode.setAttribute('role', ListAriaRootRole.LIST); } else { this.view.domNode.setAttribute('role', _options.ariaRole); } @@ -1266,12 +1270,15 @@ export class List implements ISpliceable, IDisposable { this.disposables.add(this.createMouseController(_options)); - this.onFocusChange(this._onFocusChange, this, this.disposables); - this.onSelectionChange(this._onSelectionChange, this, this.disposables); + this.onDidChangeFocus(this._onFocusChange, this, this.disposables); + this.onDidChangeSelection(this._onSelectionChange, this, this.disposables); if (_options.ariaLabel) { this.view.domNode.setAttribute('aria-label', localize('aria list', "{0}. Use the navigation keys to navigate.", _options.ariaLabel)); } + if (_options.multipleSelectionSupport) { + this.view.domNode.setAttribute('aria-multiselectable', 'true'); + } } protected createMouseController(options: IListOptions): MouseController { @@ -1284,6 +1291,10 @@ export class List implements ISpliceable, IDisposable { if (this.typeLabelController) { this.typeLabelController.updateOptions(this._options); } + + if (optionsUpdate.additionalScrollHeight !== undefined) { + this.view.updateOptions(optionsUpdate); + } } get options(): IListOptions { @@ -1310,6 +1321,10 @@ export class List implements ISpliceable, IDisposable { this.view.updateWidth(index); } + updateElementHeight(index: number, size: number): void { + this.view.updateElementHeight(index, size, null); + } + rerender(): void { this.view.rerender(); } @@ -1494,9 +1509,13 @@ export class List implements ISpliceable, IDisposable { } focusFirst(browserEvent?: UIEvent, filter?: (element: T) => boolean): void { + this.focusNth(0, browserEvent, filter); + } + + focusNth(n: number, browserEvent?: UIEvent, filter?: (element: T) => boolean): void { if (this.length === 0) { return; } - const index = this.findNextIndex(0, false, filter); + const index = this.findNextIndex(n, false, filter); if (index > -1) { this.setFocus([index], browserEvent); diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 0adbfe23b90..90d828ba7c3 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -6,7 +6,7 @@ import 'vs/css!./menu'; import * as nls from 'vs/nls'; import * as strings from 'vs/base/common/strings'; -import { IActionRunner, IAction, Action, IActionViewItem } from 'vs/base/common/actions'; +import { IActionRunner, IAction, Action } from 'vs/base/common/actions'; import { ActionBar, IActionViewItemProvider, ActionsOrientation, Separator, ActionViewItem, IActionViewItemOptions, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes'; import { addClass, EventType, EventHelper, EventLike, removeTabIndexAndUpdateFocus, isAncestor, hasClass, addDisposableListener, removeClass, append, $, addClasses, removeClasses, clearNode } from 'vs/base/browser/dom'; @@ -19,6 +19,7 @@ import { ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable'; import { Event } from 'vs/base/common/event'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { isLinux, isMacintosh } from 'vs/base/common/platform'; +import { stripCodicons } from 'vs/base/common/codicons'; export const MENU_MNEMONIC_REGEX = /\(&([^\s&])\)|(^|[^&])&([^\s&])/; export const MENU_ESCAPED_MNEMONIC_REGEX = /(&)?(&)([^\s&])/g; @@ -205,7 +206,7 @@ export class Menu extends ActionBar { container.appendChild(this.scrollableElement.getDomNode()); this.scrollableElement.scanDomNode(); - this.viewItems.filter(item => !(item instanceof MenuSeparatorActionViewItem)).forEach((item: IActionViewItem, index: number, array: any[]) => { + this.viewItems.filter(item => !(item instanceof MenuSeparatorActionViewItem)).forEach((item, index, array) => { (item as BaseMenuActionViewItem).updatePositionInSet(index + 1, array.length); }); } @@ -363,7 +364,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { private cssClass: string; protected menuStyle: IMenuStyles | undefined; - constructor(ctx: any, action: IAction, options: IMenuItemOptions = {}) { + constructor(ctx: unknown, action: IAction, options: IMenuItemOptions = {}) { options.isMenu = true; super(action, action, options); @@ -471,7 +472,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { if (this.options.label) { clearNode(this.label); - let label = this.getAction().label; + let label = stripCodicons(this.getAction().label); if (label) { const cleanLabel = cleanMnemonic(label); if (!this.options.enableMnemonics) { diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index fa92216ab12..29e86602fd6 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -21,6 +21,7 @@ import { asArray } from 'vs/base/common/arrays'; import { ScanCodeUtils, ScanCode } from 'vs/base/common/scanCode'; import { isMacintosh } from 'vs/base/common/platform'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; const $ = DOM.$; @@ -31,6 +32,7 @@ export interface IMenuBarOptions { getKeybinding?: (action: IAction) => ResolvedKeybinding | undefined; alwaysOnMnemonics?: boolean; compactMode?: Direction; + getCompactMenuActions?: () => IAction[] } export interface MenuBarMenu { @@ -490,6 +492,12 @@ export class MenuBar extends Disposable { this.container.insertBefore(this.overflowMenu.buttonElement, this.menuCache[this.numMenusShown].buttonElement); this.overflowMenu.buttonElement.style.visibility = 'visible'; } + + const compactMenuActions = this.options.getCompactMenuActions?.(); + if (compactMenuActions && compactMenuActions.length) { + this.overflowMenu.actions.push(new Separator()); + this.overflowMenu.actions.push(...compactMenuActions); + } } else { DOM.removeNode(this.overflowMenu.buttonElement); this.container.appendChild(this.overflowMenu.buttonElement); diff --git a/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts b/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts index 6658a8ceedd..23bfdb2c7bf 100644 --- a/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts +++ b/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts @@ -259,6 +259,15 @@ export abstract class AbstractScrollbar extends Widget { this._scrollable.setScrollPositionNow(desiredScrollPosition); } + public updateScrollbarSize(scrollbarSize: number): void { + this._updateScrollbarSize(scrollbarSize); + this._scrollbarState.setScrollbarSize(scrollbarSize); + this._shouldRender = true; + if (!this._lazyRender) { + this.render(); + } + } + // ----------------- Overwrite these protected abstract _renderDomNode(largeSize: number, smallSize: number): void; @@ -267,6 +276,7 @@ export abstract class AbstractScrollbar extends Widget { protected abstract _mouseDownRelativePosition(offsetX: number, offsetY: number): number; protected abstract _sliderMousePosition(e: ISimplifiedMouseEvent): number; protected abstract _sliderOrthogonalMousePosition(e: ISimplifiedMouseEvent): number; + protected abstract _updateScrollbarSize(size: number): void; public abstract writeScrollPosition(target: INewScrollPosition, scrollPosition: number): void; } diff --git a/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts b/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts index f68d4434b4d..7a2049b74f1 100644 --- a/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts +++ b/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts @@ -36,7 +36,7 @@ export class HorizontalScrollbar extends AbstractScrollbar { let scrollbarDelta = (options.horizontalScrollbarSize - ARROW_IMG_SIZE) / 2; this._createArrow({ - className: 'left-arrow', + className: 'scra codicon codicon-triangle-left', top: scrollbarDelta, left: arrowDelta, bottom: undefined, @@ -47,7 +47,7 @@ export class HorizontalScrollbar extends AbstractScrollbar { }); this._createArrow({ - className: 'right-arrow', + className: 'scra codicon codicon-triangle-right', top: scrollbarDelta, left: undefined, bottom: undefined, @@ -92,6 +92,10 @@ export class HorizontalScrollbar extends AbstractScrollbar { return e.posy; } + protected _updateScrollbarSize(size: number): void { + this.slider.setHeight(size); + } + public writeScrollPosition(target: INewScrollPosition, scrollPosition: number): void { target.scrollLeft = scrollPosition; } diff --git a/src/vs/base/browser/ui/scrollbar/media/arrow-down-dark.svg b/src/vs/base/browser/ui/scrollbar/media/arrow-down-dark.svg deleted file mode 100644 index 23a6284928b..00000000000 --- a/src/vs/base/browser/ui/scrollbar/media/arrow-down-dark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/base/browser/ui/scrollbar/media/arrow-down.svg b/src/vs/base/browser/ui/scrollbar/media/arrow-down.svg deleted file mode 100644 index cf127c6a098..00000000000 --- a/src/vs/base/browser/ui/scrollbar/media/arrow-down.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/base/browser/ui/scrollbar/media/arrow-left-dark.svg b/src/vs/base/browser/ui/scrollbar/media/arrow-left-dark.svg deleted file mode 100644 index 8a5909bb262..00000000000 --- a/src/vs/base/browser/ui/scrollbar/media/arrow-left-dark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/base/browser/ui/scrollbar/media/arrow-left.svg b/src/vs/base/browser/ui/scrollbar/media/arrow-left.svg deleted file mode 100644 index d4f475e4808..00000000000 --- a/src/vs/base/browser/ui/scrollbar/media/arrow-left.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/base/browser/ui/scrollbar/media/arrow-right-dark.svg b/src/vs/base/browser/ui/scrollbar/media/arrow-right-dark.svg deleted file mode 100644 index 61dddd673cb..00000000000 --- a/src/vs/base/browser/ui/scrollbar/media/arrow-right-dark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/base/browser/ui/scrollbar/media/arrow-right.svg b/src/vs/base/browser/ui/scrollbar/media/arrow-right.svg deleted file mode 100644 index 824671db551..00000000000 --- a/src/vs/base/browser/ui/scrollbar/media/arrow-right.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/base/browser/ui/scrollbar/media/arrow-up-dark.svg b/src/vs/base/browser/ui/scrollbar/media/arrow-up-dark.svg deleted file mode 100644 index 69a83f0f02a..00000000000 --- a/src/vs/base/browser/ui/scrollbar/media/arrow-up-dark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/base/browser/ui/scrollbar/media/arrow-up.svg b/src/vs/base/browser/ui/scrollbar/media/arrow-up.svg deleted file mode 100644 index d2da965deed..00000000000 --- a/src/vs/base/browser/ui/scrollbar/media/arrow-up.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/base/browser/ui/scrollbar/media/scrollbars.css b/src/vs/base/browser/ui/scrollbar/media/scrollbars.css index b05c77eed8a..5d7a2dc705a 100644 --- a/src/vs/base/browser/ui/scrollbar/media/scrollbars.css +++ b/src/vs/base/browser/ui/scrollbar/media/scrollbars.css @@ -4,38 +4,9 @@ *--------------------------------------------------------------------------------------------*/ /* Arrows */ -.monaco-scrollable-element > .scrollbar > .up-arrow { - background: url('arrow-up.svg'); +.monaco-scrollable-element > .scrollbar > .scra { cursor: pointer; -} -.monaco-scrollable-element > .scrollbar > .down-arrow { - background: url('arrow-down.svg'); - cursor: pointer; -} -.monaco-scrollable-element > .scrollbar > .left-arrow { - background: url('arrow-left.svg'); - cursor: pointer; -} -.monaco-scrollable-element > .scrollbar > .right-arrow { - background: url('arrow-right.svg'); - cursor: pointer; -} - -.hc-black .monaco-scrollable-element > .scrollbar > .up-arrow, -.vs-dark .monaco-scrollable-element > .scrollbar > .up-arrow { - background: url('arrow-up-dark.svg'); -} -.hc-black .monaco-scrollable-element > .scrollbar > .down-arrow, -.vs-dark .monaco-scrollable-element > .scrollbar > .down-arrow { - background: url('arrow-down-dark.svg'); -} -.hc-black .monaco-scrollable-element > .scrollbar > .left-arrow, -.vs-dark .monaco-scrollable-element > .scrollbar > .left-arrow { - background: url('arrow-left-dark.svg'); -} -.hc-black .monaco-scrollable-element > .scrollbar > .right-arrow, -.vs-dark .monaco-scrollable-element > .scrollbar > .right-arrow { - background: url('arrow-right-dark.svg'); + font-size: 11px !important; } .monaco-scrollable-element > .visible { @@ -137,4 +108,4 @@ .hc-black .monaco-scrollable-element .shadow.top.left { box-shadow: none; -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index 309db05fe21..c840e64c882 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/scrollbars'; -import { isEdgeOrIE } from 'vs/base/browser/browser'; +import { isEdge } from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; import { IMouseEvent, StandardWheelEvent, IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; @@ -287,12 +287,22 @@ export abstract class AbstractScrollableElement extends Widget { * depend on Editor. */ public updateOptions(newOptions: ScrollableElementChangeOptions): void { - let massagedOptions = resolveOptions(newOptions); - this._options.handleMouseWheel = massagedOptions.handleMouseWheel; - this._options.mouseWheelScrollSensitivity = massagedOptions.mouseWheelScrollSensitivity; - this._options.fastScrollSensitivity = massagedOptions.fastScrollSensitivity; - this._options.scrollPredominantAxis = massagedOptions.scrollPredominantAxis; - this._setListeningToMouseWheel(this._options.handleMouseWheel); + if (typeof newOptions.handleMouseWheel !== 'undefined') { + this._options.handleMouseWheel = newOptions.handleMouseWheel; + this._setListeningToMouseWheel(this._options.handleMouseWheel); + } + if (typeof newOptions.mouseWheelScrollSensitivity !== 'undefined') { + this._options.mouseWheelScrollSensitivity = newOptions.mouseWheelScrollSensitivity; + } + if (typeof newOptions.fastScrollSensitivity !== 'undefined') { + this._options.fastScrollSensitivity = newOptions.fastScrollSensitivity; + } + if (typeof newOptions.scrollPredominantAxis !== 'undefined') { + this._options.scrollPredominantAxis = newOptions.scrollPredominantAxis; + } + if (typeof newOptions.horizontalScrollbarSize !== 'undefined') { + this._horizontalScrollbar.updateScrollbarSize(newOptions.horizontalScrollbarSize); + } if (!this._options.lazyRender) { this._render(); @@ -326,7 +336,7 @@ export abstract class AbstractScrollableElement extends Widget { this._onMouseWheel(new StandardWheelEvent(browserEvent)); }; - this._mouseWheelToDispose.push(dom.addDisposableListener(this._listenOnDomNode, isEdgeOrIE ? 'mousewheel' : 'wheel', onMouseWheel, { passive: false })); + this._mouseWheelToDispose.push(dom.addDisposableListener(this._listenOnDomNode, isEdge ? 'mousewheel' : 'wheel', onMouseWheel, { passive: false })); } } diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts b/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts index 3979dfe5e1e..afb227be73b 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts @@ -119,8 +119,9 @@ export interface ScrollableElementCreationOptions { export interface ScrollableElementChangeOptions { handleMouseWheel?: boolean; mouseWheelScrollSensitivity?: number; - fastScrollSensitivity: number; - scrollPredominantAxis: boolean; + fastScrollSensitivity?: number; + scrollPredominantAxis?: boolean; + horizontalScrollbarSize?: number; } export interface ScrollableElementResolvedOptions { diff --git a/src/vs/base/browser/ui/scrollbar/scrollbarState.ts b/src/vs/base/browser/ui/scrollbar/scrollbarState.ts index a3554658ecb..48e20a5a033 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollbarState.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollbarState.ts @@ -14,7 +14,7 @@ export class ScrollbarState { * For the vertical scrollbar: the width. * For the horizontal scrollbar: the height. */ - private readonly _scrollbarSize: number; + private _scrollbarSize: number; /** * For the vertical scrollbar: the height of the pair horizontal scrollbar. @@ -114,6 +114,10 @@ export class ScrollbarState { return false; } + public setScrollbarSize(scrollbarSize: number): void { + this._scrollbarSize = scrollbarSize; + } + private static _computeValues(oppositeScrollbarSize: number, arrowSize: number, visibleSize: number, scrollSize: number, scrollPosition: number) { const computedAvailableSize = Math.max(0, visibleSize - oppositeScrollbarSize); const computedRepresentableSize = Math.max(0, computedAvailableSize - 2 * arrowSize); diff --git a/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts b/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts index c974029acc2..a6493164370 100644 --- a/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts +++ b/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts @@ -37,7 +37,7 @@ export class VerticalScrollbar extends AbstractScrollbar { let scrollbarDelta = (options.verticalScrollbarSize - ARROW_IMG_SIZE) / 2; this._createArrow({ - className: 'up-arrow', + className: 'scra codicon codicon-triangle-up', top: arrowDelta, left: scrollbarDelta, bottom: undefined, @@ -48,7 +48,7 @@ export class VerticalScrollbar extends AbstractScrollbar { }); this._createArrow({ - className: 'down-arrow', + className: 'scra codicon codicon-triangle-down', top: undefined, left: scrollbarDelta, bottom: arrowDelta, @@ -93,6 +93,10 @@ export class VerticalScrollbar extends AbstractScrollbar { return e.posx; } + protected _updateScrollbarSize(size: number): void { + this.slider.setWidth(size); + } + public writeScrollPosition(target: INewScrollPosition, scrollPosition: number): void { target.scrollTop = scrollPosition; } diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index 07258ce3bef..19233efb75c 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -354,10 +354,9 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi content.push(`.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.option-disabled:hover { background-color: ${this.styles.selectBackground} !important; }`); } - // Match quickOpen outline styles - ignore for disabled options + // Match quick input outline styles - ignore for disabled options if (this.styles.listFocusOutline) { content.push(`.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.focused { outline: 1.6px dotted ${this.styles.listFocusOutline} !important; outline-offset: -1.6px !important; }`); - } if (this.styles.listHoverOutline) { @@ -750,7 +749,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi .on(e => this.onMouseUp(e), this)); this._register(this.selectList.onMouseOver(e => typeof e.index !== 'undefined' && this.selectList.setFocus([e.index]))); - this._register(this.selectList.onFocusChange(e => this.onListFocus(e))); + this._register(this.selectList.onDidChangeFocus(e => this.onListFocus(e))); this._register(dom.addDisposableListener(this.selectDropDownContainer, dom.EventType.FOCUS_OUT, e => { if (!this._isVisible || dom.isAncestor(e.relatedTarget as HTMLElement, this.selectDropDownContainer)) { diff --git a/src/vs/base/browser/ui/selectBox/selectBoxNative.ts b/src/vs/base/browser/ui/selectBox/selectBoxNative.ts index fc18c6d3b18..b5a553b94d5 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxNative.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxNative.ts @@ -51,6 +51,10 @@ export class SelectBoxNative extends Disposable implements ISelectBoxDelegate { })); }); + this._register(dom.addStandardDisposableListener(this.selectElement, 'click', (e) => { + dom.EventHelper.stop(e, true); + })); + this._register(dom.addStandardDisposableListener(this.selectElement, 'change', (e) => { this.selectElement.title = e.target.value; this._onDidSelect.fire({ diff --git a/src/vs/base/browser/ui/splitview/paneview.css b/src/vs/base/browser/ui/splitview/paneview.css index c1397d6c62c..b127f2ed27d 100644 --- a/src/vs/base/browser/ui/splitview/paneview.css +++ b/src/vs/base/browser/ui/splitview/paneview.css @@ -68,6 +68,24 @@ color: inherit; } +.monaco-pane-view .pane > .pane-header .monaco-action-bar .action-item.select-container { + cursor: default; +} + +.monaco-pane-view .pane > .pane-header .action-item .monaco-select-box { + cursor: pointer; + min-width: 110px; + min-height: 18px; + padding: 2px 23px 2px 8px; + background-color: inherit !important; + color: inherit !important; +} + +.linux .monaco-pane-view .pane > .pane-header .action-item .monaco-select-box, +.windows .monaco-pane-view .pane > .pane-header .action-item .monaco-select-box { + padding: 0px 23px 2px 8px; +} + /* Bold font style does not go well with CJK fonts */ .monaco-pane-view:lang(zh-Hans) .pane > .pane-header, .monaco-pane-view:lang(zh-Hant) .pane > .pane-header, @@ -99,3 +117,26 @@ .monaco-pane-view.animated.horizontal .split-view-view { transition-property: width; } + +#monaco-workbench-pane-drop-overlay { + position: absolute; + z-index: 10000; + width: 100%; + height: 100%; + left: 0; + box-sizing: border-box; +} + +#monaco-workbench-pane-drop-overlay > .pane-overlay-indicator { + position: absolute; + width: 100%; + height: 100%; + min-height: 22px; + + pointer-events: none; /* very important to not take events away from the parent */ + transition: opacity 150ms ease-out; +} + +#monaco-workbench-pane-drop-overlay > .pane-overlay-indicator.overlay-move-transition { + transition: top 70ms ease-out, left 70ms ease-out, width 70ms ease-out, height 70ms ease-out, opacity 150ms ease-out; +} diff --git a/src/vs/base/browser/ui/splitview/paneview.ts b/src/vs/base/browser/ui/splitview/paneview.ts index 037ffdce27b..187874c0025 100644 --- a/src/vs/base/browser/ui/splitview/paneview.ts +++ b/src/vs/base/browser/ui/splitview/paneview.ts @@ -9,18 +9,21 @@ import { Event, Emitter } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { $, append, addClass, removeClass, toggleClass, trackFocus, EventHelper } from 'vs/base/browser/dom'; +import { $, append, addClass, removeClass, toggleClass, trackFocus, EventHelper, clearNode } from 'vs/base/browser/dom'; import { firstIndex } from 'vs/base/common/arrays'; import { Color, RGBA } from 'vs/base/common/color'; import { SplitView, IView } from './splitview'; import { isFirefox } from 'vs/base/browser/browser'; import { DataTransfers } from 'vs/base/browser/dnd'; +import { Orientation } from 'vs/base/browser/ui/sash/sash'; +import { localize } from 'vs/nls'; export interface IPaneOptions { - ariaHeaderLabel?: string; minimumBodySize?: number; maximumBodySize?: number; expanded?: boolean; + orientation?: Orientation; + title: string; } export interface IPaneStyles { @@ -28,6 +31,7 @@ export interface IPaneStyles { headerForeground?: Color; headerBackground?: Color; headerBorder?: Color; + leftBorder?: Color; } /** @@ -48,6 +52,7 @@ export abstract class Pane extends Disposable implements IView { private body!: HTMLElement; protected _expanded: boolean; + protected _orientation: Orientation; private expandedSize: number | undefined = undefined; private _headerVisible = true; @@ -101,7 +106,7 @@ export abstract class Pane extends Disposable implements IView { get minimumSize(): number { const headerSize = this.headerSize; const expanded = !this.headerVisible || this.isExpanded(); - const minimumBodySize = expanded ? this._minimumBodySize : 0; + const minimumBodySize = expanded ? this.minimumBodySize : this._orientation === Orientation.HORIZONTAL ? 50 : 0; return headerSize + minimumBodySize; } @@ -109,17 +114,18 @@ export abstract class Pane extends Disposable implements IView { get maximumSize(): number { const headerSize = this.headerSize; const expanded = !this.headerVisible || this.isExpanded(); - const maximumBodySize = expanded ? this._maximumBodySize : 0; + const maximumBodySize = expanded ? this.maximumBodySize : this._orientation === Orientation.HORIZONTAL ? 50 : 0; return headerSize + maximumBodySize; } - width: number = 0; + orthogonalSize: number = 0; - constructor(options: IPaneOptions = {}) { + constructor(options: IPaneOptions) { super(); this._expanded = typeof options.expanded === 'undefined' ? true : !!options.expanded; - this.ariaHeaderLabel = options.ariaHeaderLabel || ''; + this._orientation = typeof options.orientation === 'undefined' ? Orientation.VERTICAL : options.orientation; + this.ariaHeaderLabel = localize('viewSection', "{0} Section", options.title); this._minimumBodySize = typeof options.minimumBodySize === 'number' ? options.minimumBodySize : 120; this._maximumBodySize = typeof options.maximumBodySize === 'number' ? options.maximumBodySize : Number.POSITIVE_INFINITY; @@ -168,6 +174,18 @@ export abstract class Pane extends Disposable implements IView { this._onDidChange.fire(undefined); } + get orientation(): Orientation { + return this._orientation; + } + + set orientation(orientation: Orientation) { + if (this._orientation === orientation) { + return; + } + + this._orientation = orientation; + } + render(): void { this.header = $('.pane-header'); append(this.element, this.header); @@ -183,6 +201,7 @@ export abstract class Pane extends Disposable implements IView { this.updateHeader(); + const onHeaderKeyDown = Event.chain(domEvent(this.header, 'keydown')) .map(e => new StandardKeyboardEvent(e)); @@ -196,24 +215,37 @@ export abstract class Pane extends Disposable implements IView { .event(() => this.setExpanded(true), null)); this._register(domEvent(this.header, 'click') - (() => this.setExpanded(!this.isExpanded()), null)); + (e => { + if (!e.defaultPrevented) { + this.setExpanded(!this.isExpanded()); + } + }, null)); this.body = append(this.element, $('.pane-body')); this.renderBody(this.body); + + if (!this.isExpanded()) { + this.body.remove(); + } } - layout(height: number): void { + layout(size: number): void { const headerSize = this.headerVisible ? Pane.HEADER_SIZE : 0; + const width = this._orientation === Orientation.VERTICAL ? this.orthogonalSize : size; + const height = this._orientation === Orientation.VERTICAL ? size - headerSize : this.orthogonalSize - headerSize; + if (this.isExpanded()) { - this.layoutBody(height - headerSize, this.width); - this.expandedSize = height; + this.layoutBody(height, width); + this.expandedSize = size; } } style(styles: IPaneStyles): void { this.styles = styles; + this.element.style.borderLeft = this.styles.leftBorder && this.orientation === Orientation.HORIZONTAL ? `1px solid ${this.styles.leftBorder}` : ''; + if (!this.header) { return; } @@ -232,7 +264,7 @@ export abstract class Pane extends Disposable implements IView { this.header.style.color = this.styles.headerForeground ? this.styles.headerForeground.toString() : ''; this.header.style.backgroundColor = this.styles.headerBackground ? this.styles.headerBackground.toString() : ''; - this.header.style.borderTop = this.styles.headerBorder ? `1px solid ${this.styles.headerBorder}` : ''; + this.header.style.borderTop = this.styles.headerBorder && this.orientation === Orientation.VERTICAL ? `1px solid ${this.styles.headerBorder}` : ''; this._dropBackground = this.styles.dropBackground; } @@ -371,6 +403,7 @@ export class DefaultPaneDndController implements IPaneDndController { export interface IPaneViewOptions { dnd?: IPaneDndController; + orientation?: Orientation; } interface IPaneItem { @@ -384,21 +417,24 @@ export class PaneView extends Disposable { private dndContext: IDndContext = { draggable: null }; private el: HTMLElement; private paneItems: IPaneItem[] = []; - private width: number = 0; + private orthogonalSize: number = 0; + private size: number = 0; private splitview: SplitView; private animationTimer: number | undefined = undefined; private _onDidDrop = this._register(new Emitter<{ from: Pane, to: Pane }>()); readonly onDidDrop: Event<{ from: Pane, to: Pane }> = this._onDidDrop.event; + orientation: Orientation; readonly onDidSashChange: Event; constructor(container: HTMLElement, options: IPaneViewOptions = {}) { super(); this.dnd = options.dnd; + this.orientation = options.orientation ?? Orientation.VERTICAL; this.el = append(container, $('.monaco-pane-view')); - this.splitview = this._register(new SplitView(this.el)); + this.splitview = this._register(new SplitView(this.el, { orientation: this.orientation })); this.onDidSashChange = this.splitview.onDidSashChange; } @@ -408,7 +444,8 @@ export class PaneView extends Disposable { const paneItem = { pane: pane, disposable: disposables }; this.paneItems.splice(index, 0, paneItem); - pane.width = this.width; + pane.orientation = this.orientation; + pane.orthogonalSize = this.orthogonalSize; this.splitview.addView(pane, size, index); if (this.dnd) { @@ -465,13 +502,40 @@ export class PaneView extends Disposable { } layout(height: number, width: number): void { - this.width = width; + this.orthogonalSize = this.orientation === Orientation.VERTICAL ? width : height; + this.size = this.orientation === Orientation.HORIZONTAL ? width : height; for (const paneItem of this.paneItems) { - paneItem.pane.width = width; + paneItem.pane.orthogonalSize = this.orthogonalSize; } - this.splitview.layout(height); + this.splitview.layout(this.size); + } + + flipOrientation(height: number, width: number): void { + this.orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL; + const paneSizes = this.paneItems.map(pane => this.getPaneSize(pane.pane)); + + this.splitview.dispose(); + clearNode(this.el); + + this.splitview = this._register(new SplitView(this.el, { orientation: this.orientation })); + + const newOrthogonalSize = this.orientation === Orientation.VERTICAL ? width : height; + const newSize = this.orientation === Orientation.HORIZONTAL ? width : height; + + this.paneItems.forEach((pane, index) => { + pane.pane.orthogonalSize = newOrthogonalSize; + pane.pane.orientation = this.orientation; + + const viewSize = this.size === 0 ? 0 : (newSize * paneSizes[index]) / this.size; + this.splitview.addView(pane.pane, viewSize, index); + }); + + this.size = newSize; + this.orthogonalSize = newOrthogonalSize; + + this.splitview.layout(this.size); } private setupAnimation(): void { diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index 6bca3b4ec7b..8224c7be20e 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -110,7 +110,7 @@ abstract class ViewItem { get snap(): boolean { return !!this.view.snap; } set enabled(enabled: boolean) { - this.container.style.pointerEvents = enabled ? null : 'none'; + this.container.style.pointerEvents = enabled ? '' : 'none'; } constructor( diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index fc78cfe1c28..f3f24df20bb 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/tree'; import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IListOptions, List, IListStyles, MouseController, DefaultKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/listWidget'; -import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListEvent, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IKeyboardNavigationLabelProvider, IIdentityProvider, IKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/list'; +import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListEvent, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IKeyboardNavigationLabelProvider, IIdentityProvider, IKeyboardNavigationDelegate, ListAriaRootRole } from 'vs/base/browser/ui/list/list'; import { append, $, toggleClass, getDomNodePagePosition, removeClass, addClass, hasClass, hasParentWithClass, createStyleSheet, clearNode, addClasses, removeClasses } from 'vs/base/browser/dom'; import { Event, Relay, Emitter, EventBufferer } from 'vs/base/common/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -14,7 +14,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { ITreeModel, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeFilter, ITreeNavigator, ICollapseStateChangeEvent, ITreeDragAndDrop, TreeDragOverBubble, TreeVisibility, TreeFilterResult, ITreeModelSpliceEvent, TreeMouseEventTarget } from 'vs/base/browser/ui/tree/tree'; import { ISpliceable } from 'vs/base/common/sequence'; import { IDragAndDropData, StaticDND, DragAndDropData } from 'vs/base/browser/dnd'; -import { range, equals, distinctES6, fromSet } from 'vs/base/common/arrays'; +import { range, equals, distinctES6 } from 'vs/base/common/arrays'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { domEvent } from 'vs/base/browser/event'; import { fuzzyScore, FuzzyScore } from 'vs/base/common/filters'; @@ -197,7 +197,8 @@ function asListOptions(modelProvider: () => ITreeModel { return options.ariaProvider!.getRole!(node.element); } : () => 'treeitem' - } + }, + ariaRole: ListAriaRootRole.TREE }; } @@ -1320,7 +1321,7 @@ export abstract class AbstractTree implements IDisposable set.add(node); } - return fromSet(set); + return values(set); }).event; if (_options.keyboardSupport !== false) { diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 0cd13de8079..35a093fd214 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -5,7 +5,7 @@ import { ComposedTreeDelegate, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree'; import { ObjectTree, IObjectTreeOptions, CompressibleObjectTree, ICompressibleTreeRenderer, ICompressibleKeyboardNavigationLabelProvider, ICompressibleObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree'; -import { IListVirtualDelegate, IIdentityProvider, IListDragAndDrop, IListDragOverReaction } from 'vs/base/browser/ui/list/list'; +import { IListVirtualDelegate, IIdentityProvider, IListDragAndDrop, IListDragOverReaction, ListAriaRootRole } from 'vs/base/browser/ui/list/list'; import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop, TreeError, WeakMapper, ITreeFilter, TreeVisibility, TreeFilterResult } from 'vs/base/browser/ui/tree/tree'; import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; @@ -269,9 +269,10 @@ function asObjectTreeOptions(options?: IAsyncDataTreeOpt return options.ariaProvider!.getRole!(el.element as T); } : () => 'treeitem', isChecked: options.ariaProvider!.isChecked ? (e) => { - return options.ariaProvider?.isChecked!(e.element as T); + return !!(options.ariaProvider?.isChecked!(e.element as T)); } : undefined }, + ariaRole: ListAriaRootRole.TREE, additionalScrollHeight: options.additionalScrollHeight }; } @@ -790,7 +791,7 @@ export class AsyncDataTree implements IDisposable this.refreshPromises.set(node, result); - return result.finally(() => this.refreshPromises.delete(node)); + return result.finally(() => { this.refreshPromises.delete(node); }); } private _onDidChangeCollapseState({ node, deep }: ICollapseStateChangeEvent | null, any>): void { diff --git a/src/vs/base/browser/ui/tree/media/loading-dark.svg b/src/vs/base/browser/ui/tree/media/loading-dark.svg deleted file mode 100644 index 7dc1ebd8cf0..00000000000 --- a/src/vs/base/browser/ui/tree/media/loading-dark.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/vs/base/browser/ui/tree/media/loading-hc.svg b/src/vs/base/browser/ui/tree/media/loading-hc.svg deleted file mode 100644 index c3633c0ddab..00000000000 --- a/src/vs/base/browser/ui/tree/media/loading-hc.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/vs/base/browser/ui/tree/media/loading.svg b/src/vs/base/browser/ui/tree/media/loading.svg deleted file mode 100644 index e762f06d5e6..00000000000 --- a/src/vs/base/browser/ui/tree/media/loading.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/vs/base/browser/ui/tree/media/tree-collapsed-dark.svg b/src/vs/base/browser/ui/tree/media/tree-collapsed-dark.svg deleted file mode 100644 index c2c2298dd5c..00000000000 --- a/src/vs/base/browser/ui/tree/media/tree-collapsed-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/base/browser/ui/tree/media/tree-collapsed-hc.svg b/src/vs/base/browser/ui/tree/media/tree-collapsed-hc.svg deleted file mode 100644 index 3732cbc04b8..00000000000 --- a/src/vs/base/browser/ui/tree/media/tree-collapsed-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/base/browser/ui/tree/media/tree-collapsed-light.svg b/src/vs/base/browser/ui/tree/media/tree-collapsed-light.svg deleted file mode 100644 index 1952ad63f84..00000000000 --- a/src/vs/base/browser/ui/tree/media/tree-collapsed-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/base/browser/ui/tree/media/tree-expanded-dark.svg b/src/vs/base/browser/ui/tree/media/tree-expanded-dark.svg deleted file mode 100644 index 5570923e175..00000000000 --- a/src/vs/base/browser/ui/tree/media/tree-expanded-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/base/browser/ui/tree/media/tree-expanded-hc.svg b/src/vs/base/browser/ui/tree/media/tree-expanded-hc.svg deleted file mode 100644 index b370009330c..00000000000 --- a/src/vs/base/browser/ui/tree/media/tree-expanded-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/base/browser/ui/tree/media/tree-expanded-light.svg b/src/vs/base/browser/ui/tree/media/tree-expanded-light.svg deleted file mode 100644 index 939ebc8b969..00000000000 --- a/src/vs/base/browser/ui/tree/media/tree-expanded-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/base/browser/ui/tree/treeDefaults.ts b/src/vs/base/browser/ui/tree/treeDefaults.ts index e74f249c17d..944efecd207 100644 --- a/src/vs/base/browser/ui/tree/treeDefaults.ts +++ b/src/vs/base/browser/ui/tree/treeDefaults.ts @@ -10,16 +10,14 @@ import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; export class CollapseAllAction extends Action { constructor(private viewer: AsyncDataTree, enabled: boolean) { - super('vs.tree.collapse', nls.localize('collapse all', "Collapse All"), 'monaco-tree-action collapse-all', enabled); + super('vs.tree.collapse', nls.localize('collapse all', "Collapse All"), 'collapse-all', enabled); } - public run(context?: any): Promise { + async run(context?: any): Promise { this.viewer.collapseAll(); this.viewer.setSelection([]); this.viewer.setFocus([]); this.viewer.domFocus(); this.viewer.focusFirst(); - - return Promise.resolve(); } } diff --git a/src/vs/base/common/amd.ts b/src/vs/base/common/amd.ts index b44b19ddd0d..ebea36794d0 100644 --- a/src/vs/base/common/amd.ts +++ b/src/vs/base/common/amd.ts @@ -12,12 +12,3 @@ export function getPathFromAmdModule(requirefn: typeof require, relativePath: st export function getUriFromAmdModule(requirefn: typeof require, relativePath: string): URI { return URI.parse(requirefn.toUrl(relativePath)); } - -/** - * Reference a resource that might be inlined. - * Do not inline icons that will be used by the native mac touchbar. - * Do not rename this method unless you adopt the build scripts. - */ -export function registerAndGetAmdImageURL(absolutePath: string): string { - return require.toUrl(absolutePath); -} diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index 9a9b53a100c..a037cb95c67 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -372,12 +372,6 @@ export function distinctES6(array: ReadonlyArray): T[] { }); } -export function fromSet(set: Set): T[] { - const result: T[] = []; - set.forEach(o => result.push(o)); - return result; -} - export function uniqueFilter(keyFn: (t: T) => string): (t: T) => boolean { const seen: { [key: string]: boolean; } = Object.create(null); @@ -405,6 +399,9 @@ export function lastIndex(array: ReadonlyArray, fn: (item: T) => boolean): return -1; } +/** + * @deprecated ES6: use `Array.findIndex` + */ export function firstIndex(array: ReadonlyArray, fn: (item: T) => boolean): number { for (let i = 0; i < array.length; i++) { const element = array[i]; @@ -417,6 +414,10 @@ export function firstIndex(array: ReadonlyArray, fn: (item: T) => boolean) return -1; } + +/** + * @deprecated ES6: use `Array.find` + */ export function first(array: ReadonlyArray, fn: (item: T) => boolean, notFoundValue: T): T; export function first(array: ReadonlyArray, fn: (item: T) => boolean): T | undefined; export function first(array: ReadonlyArray, fn: (item: T) => boolean, notFoundValue: T | undefined = undefined): T | undefined { @@ -471,14 +472,6 @@ export function range(arg: number, to?: number): number[] { return result; } -export function fill(num: number, value: T, arr: T[] = []): T[] { - for (let i = 0; i < num; i++) { - arr[i] = value; - } - - return arr; -} - export function index(array: ReadonlyArray, indexer: (t: T) => string): { [key: string]: T; }; export function index(array: ReadonlyArray, indexer: (t: T) => string, merger?: (t: T, r: R) => R): { [key: string]: R; }; export function index(array: ReadonlyArray, indexer: (t: T) => string, merger: (t: T, r: R) => R = t => t as any): { [key: string]: R; } { @@ -564,6 +557,10 @@ export function pushToEnd(arr: T[], value: T): void { } } + +/** + * @deprecated ES6: use `Array.find` + */ export function find(arr: ArrayLike, predicate: (value: T, index: number, arr: ArrayLike) => any): T | undefined { for (let i = 0; i < arr.length; i++) { const element = arr[i]; diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index e5ff745c876..9951ca26b7e 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -837,7 +837,7 @@ export class TaskSequentializer { this._pending?.cancel(); } - setPending(taskId: number, promise: Promise, onCancel?: () => void, ): Promise { + setPending(taskId: number, promise: Promise, onCancel?: () => void,): Promise { this._pending = { taskId: taskId, cancel: () => onCancel?.(), promise }; promise.then(() => this.donePending(taskId), () => this.donePending(taskId)); diff --git a/src/vs/base/common/buffer.ts b/src/vs/base/common/buffer.ts index 1126f89c7d8..f5408cded9f 100644 --- a/src/vs/base/common/buffer.ts +++ b/src/vs/base/common/buffer.ts @@ -94,8 +94,14 @@ export class VSBuffer { return new VSBuffer(this.buffer.subarray(start!/*bad lib.d.ts*/, end)); } - set(array: VSBuffer, offset?: number): void { - this.buffer.set(array.buffer, offset); + set(array: VSBuffer, offset?: number): void; + set(array: Uint8Array, offset?: number): void; + set(array: VSBuffer | Uint8Array, offset?: number): void { + if (array instanceof VSBuffer) { + this.buffer.set(array.buffer, offset); + } else { + this.buffer.set(array, offset); + } } readUInt32BE(offset: number): number { @@ -106,6 +112,14 @@ export class VSBuffer { writeUInt32BE(this.buffer, value, offset); } + readUInt32LE(offset: number): number { + return readUInt32LE(this.buffer, offset); + } + + writeUInt32LE(value: number, offset: number): void { + writeUInt32LE(this.buffer, value, offset); + } + readUInt8(offset: number): number { return readUInt8(this.buffer, offset); } @@ -115,6 +129,19 @@ export class VSBuffer { } } +export function readUInt16LE(source: Uint8Array, offset: number): number { + return ( + ((source[offset + 0] << 0) >>> 0) | + ((source[offset + 1] << 8) >>> 0) + ); +} + +export function writeUInt16LE(destination: Uint8Array, value: number, offset: number): void { + destination[offset + 0] = (value & 0b11111111); + value = value >>> 8; + destination[offset + 1] = (value & 0b11111111); +} + export function readUInt32BE(source: Uint8Array, offset: number): number { return ( source[offset] * 2 ** 24 @@ -134,11 +161,30 @@ export function writeUInt32BE(destination: Uint8Array, value: number, offset: nu destination[offset] = value; } -function readUInt8(source: Uint8Array, offset: number): number { +export function readUInt32LE(source: Uint8Array, offset: number): number { + return ( + ((source[offset + 0] << 0) >>> 0) | + ((source[offset + 1] << 8) >>> 0) | + ((source[offset + 2] << 16) >>> 0) | + ((source[offset + 3] << 24) >>> 0) + ); +} + +export function writeUInt32LE(destination: Uint8Array, value: number, offset: number): void { + destination[offset + 0] = (value & 0b11111111); + value = value >>> 8; + destination[offset + 1] = (value & 0b11111111); + value = value >>> 8; + destination[offset + 2] = (value & 0b11111111); + value = value >>> 8; + destination[offset + 3] = (value & 0b11111111); +} + +export function readUInt8(source: Uint8Array, offset: number): number { return source[offset]; } -function writeUInt8(destination: Uint8Array, value: number, offset: number): void { +export function writeUInt8(destination: Uint8Array, value: number, offset: number): void { destination[offset] = value; } diff --git a/src/vs/base/common/cancellation.ts b/src/vs/base/common/cancellation.ts index 75b615de669..cb8c8c6da2d 100644 --- a/src/vs/base/common/cancellation.ts +++ b/src/vs/base/common/cancellation.ts @@ -31,7 +31,7 @@ const shortcutEvent: Event = Object.freeze(function (callback, context?): I export namespace CancellationToken { - export function isCancellationToken(thing: any): thing is CancellationToken { + export function isCancellationToken(thing: unknown): thing is CancellationToken { if (thing === CancellationToken.None || thing === CancellationToken.Cancelled) { return true; } diff --git a/src/vs/base/common/codicon.ts b/src/vs/base/common/codicon.ts index 51189279780..5b3541125e0 100644 --- a/src/vs/base/common/codicon.ts +++ b/src/vs/base/common/codicon.ts @@ -6,7 +6,7 @@ import { matchesFuzzy, IMatch } from 'vs/base/common/filters'; import { ltrim } from 'vs/base/common/strings'; -const codiconStartMarker = '$('; +export const codiconStartMarker = '$('; export interface IParsedCodicons { readonly text: string; diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index 00b2f8b1c17..22f8316ec39 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { codiconStartMarker } from 'vs/base/common/codicon'; + const escapeCodiconsRegex = /(\\)?\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)/gi; export function escapeCodicons(text: string): string { return text.replace(escapeCodiconsRegex, (match, escaped) => escaped ? match : `\\${match}`); @@ -27,3 +29,12 @@ export function renderCodicons(text: string): string { : ``; }); } + +const stripCodiconsRegex = /(\s)?(\\)?\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)(\s)?/gi; +export function stripCodicons(text: string): string { + if (text.indexOf(codiconStartMarker) === -1) { + return text; + } + + return text.replace(stripCodiconsRegex, (match, preWhitespace, escaped, postWhitespace) => escaped ? match : preWhitespace || postWhitespace || ''); +} diff --git a/src/vs/base/common/collections.ts b/src/vs/base/common/collections.ts index f185d439651..aac7c67e807 100644 --- a/src/vs/base/common/collections.ts +++ b/src/vs/base/common/collections.ts @@ -20,7 +20,7 @@ const hasOwnProperty = Object.prototype.hasOwnProperty; /** * Returns an array which contains all values that reside - * in the given set. + * in the given dictionary. */ export function values(from: IStringDictionary | INumberDictionary): T[] { const result: T[] = []; @@ -52,7 +52,7 @@ export function first(from: IStringDictionary | INumberDictionary): T | } /** - * Iterates over each entry in the provided set. The iterator allows + * 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 { @@ -95,11 +95,6 @@ export function fromMap(original: Map): IStringDictionary { return result; } -export function mapValues(map: Map): V[] { - const result: V[] = []; - map.forEach(v => result.push(v)); - return result; -} export class SetMap { diff --git a/src/vs/base/common/comparers.ts b/src/vs/base/common/comparers.ts index d6dbb1784f1..a713092dbda 100644 --- a/src/vs/base/common/comparers.ts +++ b/src/vs/base/common/comparers.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as strings from 'vs/base/common/strings'; import { sep } from 'vs/base/common/path'; import { IdleValue } from 'vs/base/common/async'; @@ -133,8 +132,8 @@ export function compareAnything(one: string, other: string, lookFor: string): nu } // Sort suffix matches over non suffix matches - const elementASuffixMatch = strings.endsWith(elementAName, lookFor); - const elementBSuffixMatch = strings.endsWith(elementBName, lookFor); + const elementASuffixMatch = elementAName.endsWith(lookFor); + const elementBSuffixMatch = elementBName.endsWith(lookFor); if (elementASuffixMatch !== elementBSuffixMatch) { return elementASuffixMatch ? -1 : 1; } @@ -154,8 +153,8 @@ export function compareByPrefix(one: string, other: string, lookFor: string): nu const elementBName = other.toLowerCase(); // Sort prefix matches over non prefix matches - const elementAPrefixMatch = strings.startsWith(elementAName, lookFor); - const elementBPrefixMatch = strings.startsWith(elementBName, lookFor); + const elementAPrefixMatch = elementAName.startsWith(lookFor); + const elementBPrefixMatch = elementBName.startsWith(lookFor); if (elementAPrefixMatch !== elementBPrefixMatch) { return elementAPrefixMatch ? -1 : 1; } diff --git a/src/vs/base/common/errorsWithActions.ts b/src/vs/base/common/errorsWithActions.ts index 133febb8453..fa92b7f4526 100644 --- a/src/vs/base/common/errorsWithActions.ts +++ b/src/vs/base/common/errorsWithActions.ts @@ -13,7 +13,7 @@ export interface IErrorWithActions { actions?: ReadonlyArray; } -export function isErrorWithActions(obj: any): obj is IErrorWithActions { +export function isErrorWithActions(obj: unknown): obj is IErrorWithActions { return obj instanceof Error && Array.isArray((obj as IErrorWithActions).actions); } diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 9da5ca139b0..032f447081e 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -576,8 +576,8 @@ export class Emitter { this._deliveryQueue = new LinkedList(); } - for (let iter = this._listeners.iterator(), e = iter.next(); !e.done; e = iter.next()) { - this._deliveryQueue.push([e.value, event]); + for (let listener of this._listeners) { + this._deliveryQueue.push([listener, event]); } while (this._deliveryQueue.size > 0) { @@ -671,8 +671,8 @@ export class AsyncEmitter extends Emitter { this._asyncDeliveryQueue = new LinkedList(); } - for (let iter = this._listeners.iterator(), e = iter.next(); !e.done; e = iter.next()) { - this._asyncDeliveryQueue.push([e.value, data]); + for (const listener of this._listeners) { + this._asyncDeliveryQueue.push([listener, data]); } while (this._asyncDeliveryQueue.size > 0 && !token.isCancellationRequested) { diff --git a/src/vs/base/common/extpath.ts b/src/vs/base/common/extpath.ts index 3ca76db9a29..e064829c5a0 100644 --- a/src/vs/base/common/extpath.ts +++ b/src/vs/base/common/extpath.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { isWindows } from 'vs/base/common/platform'; -import { startsWithIgnoreCase, equalsIgnoreCase, endsWith, rtrim } from 'vs/base/common/strings'; +import { startsWithIgnoreCase, equalsIgnoreCase, rtrim } from 'vs/base/common/strings'; import { CharCode } from 'vs/base/common/charCode'; import { sep, posix, isAbsolute, join, normalize } from 'vs/base/common/path'; @@ -235,7 +235,7 @@ export function isWindowsDriveLetter(char0: number): boolean { export function sanitizeFilePath(candidate: string, cwd: string): string { // Special case: allow to open a drive letter without trailing backslash - if (isWindows && endsWith(candidate, ':')) { + if (isWindows && candidate.endsWith(':')) { candidate += sep; } @@ -252,7 +252,7 @@ export function sanitizeFilePath(candidate: string, cwd: string): string { candidate = rtrim(candidate, sep); // Special case: allow to open drive root ('C:\') - if (endsWith(candidate, ':')) { + if (candidate.endsWith(':')) { candidate += sep; } diff --git a/src/vs/base/common/filters.ts b/src/vs/base/common/filters.ts index 3740a128f1a..e16f6f4c708 100644 --- a/src/vs/base/common/filters.ts +++ b/src/vs/base/common/filters.ts @@ -392,7 +392,7 @@ export function anyScore(pattern: string, lowPattern: string, _patternPos: numbe //#region --- fuzzyScore --- -export function createMatches(score: undefined | FuzzyScore): IMatch[] { +export function createMatches(score: undefined | FuzzyScore, offset = 0): IMatch[] { if (typeof score === 'undefined') { return []; } @@ -407,7 +407,7 @@ export function createMatches(score: undefined | FuzzyScore): IMatch[] { if (last && last.end === pos) { last.end = pos + 1; } else { - res.push({ start: pos, end: pos + 1 }); + res.push({ start: pos + offset, end: pos + 1 + offset }); } } } diff --git a/src/vs/base/common/functional.ts b/src/vs/base/common/functional.ts index 4587a5b7542..b437cc98c46 100644 --- a/src/vs/base/common/functional.ts +++ b/src/vs/base/common/functional.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export function once(this: any, fn: T): T { +export function once(this: unknown, fn: T): T { const _this = this; let didCall = false; - let result: any; + let result: unknown; return function () { if (didCall) { @@ -17,5 +17,5 @@ export function once(this: any, fn: T): T { result = fn.apply(_this, arguments); return result; - } as any as T; -} \ No newline at end of file + } as unknown as T; +} diff --git a/src/vs/base/parts/quickopen/common/quickOpenScorer.ts b/src/vs/base/common/fuzzyScorer.ts similarity index 62% rename from src/vs/base/parts/quickopen/common/quickOpenScorer.ts rename to src/vs/base/common/fuzzyScorer.ts index 21cb08caace..413a6115dd2 100644 --- a/src/vs/base/parts/quickopen/common/quickOpenScorer.ts +++ b/src/vs/base/common/fuzzyScorer.ts @@ -4,22 +4,24 @@ *--------------------------------------------------------------------------------------------*/ import { compareAnything } from 'vs/base/common/comparers'; -import { matchesPrefix, IMatch, matchesCamelCase, isUpper } from 'vs/base/common/filters'; +import { matchesPrefix, IMatch, matchesCamelCase, isUpper, fuzzyScore, createMatches as createFuzzyMatches } from 'vs/base/common/filters'; import { sep } from 'vs/base/common/path'; import { isWindows, isLinux } from 'vs/base/common/platform'; import { stripWildcards, equalsIgnoreCase } from 'vs/base/common/strings'; import { CharCode } from 'vs/base/common/charCode'; -export type Score = [number /* score */, number[] /* match positions */]; -export type ScorerCache = { [key: string]: IItemScore }; +//#region Fuzzy scorer + +export type FuzzyScore = [number /* score */, number[] /* match positions */]; +export type FuzzyScorerCache = { [key: string]: IItemScore }; const NO_MATCH = 0; -const NO_SCORE: Score = [NO_MATCH, []]; +const NO_SCORE: FuzzyScore = [NO_MATCH, []]; // const DEBUG = false; // const DEBUG_MATRIX = false; -export function score(target: string, query: string, queryLower: string, fuzzy: boolean): Score { +export function scoreFuzzy(target: string, query: string, queryLower: string, fuzzy: boolean): FuzzyScore { if (!target || !query) { return NO_SCORE; // return early if target or query are undefined } @@ -50,7 +52,7 @@ export function score(target: string, query: string, queryLower: string, fuzzy: } } - const res = doScore(query, queryLower, queryLength, target, targetLower, targetLength); + const res = doScoreFuzzy(query, queryLower, queryLength, target, targetLower, targetLength); // if (DEBUG) { // console.log(`%cFinal Score: ${res[0]}`, 'font-weight: bold'); @@ -60,7 +62,7 @@ export function score(target: string, query: string, queryLower: string, fuzzy: return res; } -function doScore(query: string, queryLower: string, queryLength: number, target: string, targetLower: string, targetLength: number): Score { +function doScoreFuzzy(query: string, queryLower: string, queryLength: number, target: string, targetLower: string, targetLength: number): FuzzyScore { const scores: number[] = []; const matches: number[] = []; @@ -257,6 +259,61 @@ function scoreSeparatorAtPos(charCode: number): number { // } // } +//#endregion + + +//#region Alternate fuzzy scorer implementation that is e.g. used for symbols + +export type FuzzyScore2 = [number /* score*/, IMatch[]]; + +const NO_SCORE2: FuzzyScore2 = [NO_MATCH, []]; + +export function scoreFuzzy2(target: string, query: IPreparedQuery, patternStart = 0, matchOffset = 0): FuzzyScore2 { + + // Score: multiple inputs + if (query.values && query.values.length > 1) { + return doScoreFuzzy2Multiple(target, query.values, patternStart, matchOffset); + } + + // Score: single input + return doScoreFuzzy2Single(target, query, patternStart, matchOffset); +} + +function doScoreFuzzy2Multiple(target: string, query: IPreparedQueryPiece[], patternStart: number, matchOffset: number): FuzzyScore2 { + let totalScore = 0; + const totalMatches: IMatch[] = []; + + for (const queryPiece of query) { + const [score, matches] = doScoreFuzzy2Single(target, queryPiece, patternStart, matchOffset); + if (!score) { + // if a single query value does not match, return with + // no score entirely, we require all queries to match + return NO_SCORE2; + } + + totalScore += score; + totalMatches.push(...matches); + } + + // if we have a score, ensure that the positions are + // sorted in ascending order and distinct + return [totalScore, normalizeMatches(totalMatches)]; +} + +function doScoreFuzzy2Single(target: string, query: IPreparedQueryPiece, patternStart: number, matchOffset: number): FuzzyScore2 { + const score = fuzzyScore(query.original, query.originalLowercase, patternStart, target, target.toLowerCase(), 0, true); + if (!score) { + return NO_SCORE2; + } + + return [score[0], createFuzzyMatches(score, matchOffset)]; +} + +//#endregion + + +//#region Item (label, description, path) scorer + /** * Scoring on structural items that have a label and optional description. */ @@ -285,15 +342,15 @@ export interface IItemAccessor { /** * Just the label of the item to score on. */ - getItemLabel(item: T): string | null; + getItemLabel(item: T): string | undefined; /** - * The optional description of the item to score on. Can be null. + * The optional description of the item to score on. */ - getItemDescription(item: T): string | null; + getItemDescription(item: T): string | undefined; /** - * If the item is a file, the path of the file to score on. Can be null. + * If the item is a file, the path of the file to score on. */ getItemPath(file: T): string | undefined; } @@ -303,34 +360,8 @@ const LABEL_PREFIX_SCORE = 1 << 17; const LABEL_CAMELCASE_SCORE = 1 << 16; const LABEL_SCORE_THRESHOLD = 1 << 15; -export interface IPreparedQuery { - original: string; - value: string; - lowercase: string; - containsPathSeparator: boolean; -} - -/** - * Helper function to prepare a search value for scoring in quick open by removing unwanted characters. - */ -export function prepareQuery(original: string): IPreparedQuery { - if (!original) { - original = ''; - } - - let value = stripWildcards(original).replace(/\s/g, ''); // get rid of all wildcards and whitespace - if (isWindows) { - value = value.replace(/\//g, sep); // Help Windows users to search for paths when using slash - } - - const lowercase = value.toLowerCase(); - const containsPathSeparator = value.indexOf(sep) >= 0; - - return { original, value, lowercase, containsPathSeparator }; -} - -export function scoreItem(item: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor, cache: ScorerCache): IItemScore { - if (!item || !query.value) { +export function scoreItemFuzzy(item: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor, cache: FuzzyScorerCache): IItemScore { + if (!item || !query.normalized) { return NO_ITEM_SCORE; // we need an item and query to score on at least } @@ -343,9 +374,9 @@ export function scoreItem(item: T, query: IPreparedQuery, fuzzy: boolean, acc let cacheHash: string; if (description) { - cacheHash = `${label}${description}${query.value}${fuzzy}`; + cacheHash = `${label}${description}${query.normalized}${fuzzy}`; } else { - cacheHash = `${label}${query.value}${fuzzy}`; + cacheHash = `${label}${query.normalized}${fuzzy}`; } const cached = cache[cacheHash]; @@ -353,60 +384,86 @@ export function scoreItem(item: T, query: IPreparedQuery, fuzzy: boolean, acc return cached; } - const itemScore = doScoreItem(label, description, accessor.getItemPath(item), query, fuzzy); + const itemScore = doScoreItemFuzzy(label, description, accessor.getItemPath(item), query, fuzzy); cache[cacheHash] = itemScore; return itemScore; } -function createMatches(offsets: undefined | number[]): IMatch[] { - let ret: IMatch[] = []; - if (!offsets) { - return ret; - } - let last: IMatch | undefined; - for (const pos of offsets) { - if (last && last.end === pos) { - last.end += 1; - } else { - last = { start: pos, end: pos + 1 }; - ret.push(last); - } - } - return ret; -} +function doScoreItemFuzzy(label: string, description: string | undefined, path: string | undefined, query: IPreparedQuery, fuzzy: boolean): IItemScore { + const preferLabelMatches = !path || !query.containsPathSeparator; -function doScoreItem(label: string, description: string | null, path: string | undefined, query: IPreparedQuery, fuzzy: boolean): IItemScore { - - // 1.) treat identity matches on full path highest - if (path && (isLinux ? query.original === path : equalsIgnoreCase(query.original, path))) { + // Treat identity matches on full path highest + if (path && (isLinux ? query.pathNormalized === path : equalsIgnoreCase(query.pathNormalized, path))) { return { score: PATH_IDENTITY_SCORE, labelMatch: [{ start: 0, end: label.length }], descriptionMatch: description ? [{ start: 0, end: description.length }] : undefined }; } - // We only consider label matches if the query is not including file path separators - const preferLabelMatches = !path || !query.containsPathSeparator; + // Score: multiple inputs + if (query.values && query.values.length > 1) { + return doScoreItemFuzzyMultiple(label, description, path, query.values, preferLabelMatches, fuzzy); + } + + // Score: single input + return doScoreItemFuzzySingle(label, description, path, query, preferLabelMatches, fuzzy); +} + +function doScoreItemFuzzyMultiple(label: string, description: string | undefined, path: string | undefined, query: IPreparedQueryPiece[], preferLabelMatches: boolean, fuzzy: boolean): IItemScore { + let totalScore = 0; + const totalLabelMatches: IMatch[] = []; + const totalDescriptionMatches: IMatch[] = []; + + for (const queryPiece of query) { + const { score, labelMatch, descriptionMatch } = doScoreItemFuzzySingle(label, description, path, queryPiece, preferLabelMatches, fuzzy); + if (score === NO_MATCH) { + // if a single query value does not match, return with + // no score entirely, we require all queries to match + return NO_ITEM_SCORE; + } + + totalScore += score; + if (labelMatch) { + totalLabelMatches.push(...labelMatch); + } + + if (descriptionMatch) { + totalDescriptionMatches.push(...descriptionMatch); + } + } + + // if we have a score, ensure that the positions are + // sorted in ascending order and distinct + return { + score: totalScore, + labelMatch: normalizeMatches(totalLabelMatches), + descriptionMatch: normalizeMatches(totalDescriptionMatches) + }; +} + +function doScoreItemFuzzySingle(label: string, description: string | undefined, path: string | undefined, query: IPreparedQueryPiece, preferLabelMatches: boolean, fuzzy: boolean): IItemScore { + + // Prefer label matches if told so if (preferLabelMatches) { - // 2.) treat prefix matches on the label second highest - const prefixLabelMatch = matchesPrefix(query.value, label); + // Treat prefix matches on the label second highest + const prefixLabelMatch = matchesPrefix(query.normalized, label); if (prefixLabelMatch) { return { score: LABEL_PREFIX_SCORE, labelMatch: prefixLabelMatch }; } - // 3.) treat camelcase matches on the label third highest - const camelcaseLabelMatch = matchesCamelCase(query.value, label); + // Treat camelcase matches on the label third highest + const camelcaseLabelMatch = matchesCamelCase(query.normalized, label); if (camelcaseLabelMatch) { return { score: LABEL_CAMELCASE_SCORE, labelMatch: camelcaseLabelMatch }; } - // 4.) prefer scores on the label if any - const [labelScore, labelPositions] = score(label, query.value, query.lowercase, fuzzy); + // Prefer scores on the label if any + const [labelScore, labelPositions] = scoreFuzzy(label, query.normalized, query.normalizedLowercase, fuzzy); if (labelScore) { return { score: labelScore + LABEL_SCORE_THRESHOLD, labelMatch: createMatches(labelPositions) }; } } - // 5.) finally compute description + label scores if we have a description + // Finally compute description + label scores if we have a description if (description) { let descriptionPrefix = description; if (!!path) { @@ -416,7 +473,7 @@ function doScoreItem(label: string, description: string | null, path: string | u const descriptionPrefixLength = descriptionPrefix.length; const descriptionAndLabel = `${descriptionPrefix}${label}`; - const [labelDescriptionScore, labelDescriptionPositions] = score(descriptionAndLabel, query.value, query.lowercase, fuzzy); + const [labelDescriptionScore, labelDescriptionPositions] = scoreFuzzy(descriptionAndLabel, query.normalized, query.normalizedLowercase, fuzzy); if (labelDescriptionScore) { const labelDescriptionMatches = createMatches(labelDescriptionPositions); const labelMatch: IMatch[] = []; @@ -449,9 +506,45 @@ function doScoreItem(label: string, description: string | null, path: string | u return NO_ITEM_SCORE; } -export function compareItemsByScore(itemA: T, itemB: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor, cache: ScorerCache, fallbackComparer = fallbackCompare): number { - const itemScoreA = scoreItem(itemA, query, fuzzy, accessor, cache); - const itemScoreB = scoreItem(itemB, query, fuzzy, accessor, cache); +function createMatches(offsets: number[] | undefined): IMatch[] { + const ret: IMatch[] = []; + if (!offsets) { + return ret; + } + + let last: IMatch | undefined; + for (const pos of offsets) { + if (last && last.end === pos) { + last.end += 1; + } else { + last = { start: pos, end: pos + 1 }; + ret.push(last); + } + } + + return ret; +} + +function normalizeMatches(matches: IMatch[]): IMatch[] { + const positions = new Set(); + + for (const match of matches) { + for (let i = match.start; i < match.end; i++) { + positions.add(i); + } + } + + return createMatches(Array.from(positions.values()).sort((a, b) => a - b)); +} + +//#endregion + + +//#region Comparers + +export function compareItemsByFuzzyScore(itemA: T, itemB: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor, cache: FuzzyScorerCache): number { + const itemScoreA = scoreItemFuzzy(itemA, query, fuzzy, accessor, cache); + const itemScoreB = scoreItemFuzzy(itemB, query, fuzzy, accessor, cache); const scoreA = itemScoreA.score; const scoreB = itemScoreB.score; @@ -515,7 +608,16 @@ export function compareItemsByScore(itemA: T, itemB: T, query: IPreparedQuery return scoreA > scoreB ? -1 : 1; } - // 6.) scores are identical, prefer more compact matches (label and description) + // 6.) prefer matches in label over non-label matches + const itemAHasLabelMatches = Array.isArray(itemScoreA.labelMatch) && itemScoreA.labelMatch.length > 0; + const itemBHasLabelMatches = Array.isArray(itemScoreB.labelMatch) && itemScoreB.labelMatch.length > 0; + if (itemAHasLabelMatches && !itemBHasLabelMatches) { + return -1; + } else if (itemBHasLabelMatches && !itemAHasLabelMatches) { + return 1; + } + + // 7.) scores are identical, prefer more compact matches (label and description) const itemAMatchDistance = computeLabelAndDescriptionMatchDistance(itemA, itemScoreA, accessor); const itemBMatchDistance = computeLabelAndDescriptionMatchDistance(itemB, itemScoreB, accessor); if (itemAMatchDistance && itemBMatchDistance && itemAMatchDistance !== itemBMatchDistance) { @@ -524,7 +626,7 @@ export function compareItemsByScore(itemA: T, itemB: T, query: IPreparedQuery // 7.) at this point, scores are identical and match compactness as well // for both items so we start to use the fallback compare - return fallbackComparer(itemA, itemB, query, accessor); + return fallbackCompare(itemA, itemB, query, accessor); } function computeLabelAndDescriptionMatchDistance(item: T, score: IItemScore, accessor: IItemAccessor): number { @@ -589,7 +691,7 @@ function compareByMatchLength(matchesA?: IMatch[], matchesB?: IMatch[]): number return matchLengthA === matchLengthB ? 0 : matchLengthB < matchLengthA ? 1 : -1; } -export function fallbackCompare(itemA: T, itemB: T, query: IPreparedQuery, accessor: IItemAccessor): number { +function fallbackCompare(itemA: T, itemB: T, query: IPreparedQuery, accessor: IItemAccessor): number { // check for label + description length and prefer shorter const labelA = accessor.getItemLabel(itemA) || ''; @@ -617,19 +719,128 @@ export function fallbackCompare(itemA: T, itemB: T, query: IPreparedQuery, ac // compare by label if (labelA !== labelB) { - return compareAnything(labelA, labelB, query.value); + return compareAnything(labelA, labelB, query.normalized); } // compare by description if (descriptionA && descriptionB && descriptionA !== descriptionB) { - return compareAnything(descriptionA, descriptionB, query.value); + return compareAnything(descriptionA, descriptionB, query.normalized); } // compare by path if (pathA && pathB && pathA !== pathB) { - return compareAnything(pathA, pathB, query.value); + return compareAnything(pathA, pathB, query.normalized); } // equal return 0; } + +//#endregion + + +//#region Query Normalizer + +export interface IPreparedQueryPiece { + + /** + * The original query as provided as input. + */ + original: string; + originalLowercase: string; + + /** + * Original normalized to platform separators: + * - Windows: \ + * - Posix: / + */ + pathNormalized: string; + + /** + * In addition to the normalized path, will have + * whitespace and wildcards removed. + */ + normalized: string; + normalizedLowercase: string; +} + +export interface IPreparedQuery extends IPreparedQueryPiece { + + // Split by spaces + values: IPreparedQueryPiece[] | undefined; + + containsPathSeparator: boolean; +} + +/** + * Helper function to prepare a search value for scoring by removing unwanted characters + * and allowing to score on multiple pieces separated by whitespace character. + */ +const MULTIPLE_QUERY_VALUES_SEPARATOR = ' '; +export function prepareQuery(original: string): IPreparedQuery { + if (typeof original !== 'string') { + original = ''; + } + + const originalLowercase = original.toLowerCase(); + const { pathNormalized, normalized, normalizedLowercase } = normalizeQuery(original); + const containsPathSeparator = pathNormalized.indexOf(sep) >= 0; + + let values: IPreparedQueryPiece[] | undefined = undefined; + + const originalSplit = original.split(MULTIPLE_QUERY_VALUES_SEPARATOR); + if (originalSplit.length > 1) { + for (const originalPiece of originalSplit) { + const { + pathNormalized: pathNormalizedPiece, + normalized: normalizedPiece, + normalizedLowercase: normalizedLowercasePiece + } = normalizeQuery(originalPiece); + + if (normalizedPiece) { + if (!values) { + values = []; + } + + values.push({ + original: originalPiece, + originalLowercase: originalPiece.toLowerCase(), + pathNormalized: pathNormalizedPiece, + normalized: normalizedPiece, + normalizedLowercase: normalizedLowercasePiece + }); + } + } + } + + return { original, originalLowercase, pathNormalized, normalized, normalizedLowercase, values, containsPathSeparator }; +} + +function normalizeQuery(original: string): { pathNormalized: string, normalized: string, normalizedLowercase: string } { + let pathNormalized: string; + if (isWindows) { + pathNormalized = original.replace(/\//g, sep); // Help Windows users to search for paths when using slash + } else { + pathNormalized = original.replace(/\\/g, sep); // Help macOS/Linux users to search for paths when using backslash + } + + const normalized = stripWildcards(pathNormalized).replace(/\s/g, ''); + + return { + pathNormalized, + normalized, + normalizedLowercase: normalized.toLowerCase() + }; +} + +export function pieceToQuery(piece: IPreparedQueryPiece): IPreparedQuery; +export function pieceToQuery(pieces: IPreparedQueryPiece[]): IPreparedQuery; +export function pieceToQuery(arg1: IPreparedQueryPiece | IPreparedQueryPiece[]): IPreparedQuery { + if (Array.isArray(arg1)) { + return prepareQuery(arg1.map(piece => piece.original).join(MULTIPLE_QUERY_VALUES_SEPARATOR)); + } + + return prepareQuery(arg1.original); +} + +//#endregion diff --git a/src/vs/base/common/glob.ts b/src/vs/base/common/glob.ts index 6da220436f6..5947cb70276 100644 --- a/src/vs/base/common/glob.ts +++ b/src/vs/base/common/glob.ts @@ -302,7 +302,7 @@ function parsePattern(arg1: string | IRelativePattern, options: IGlobOptions): P if (T1.test(pattern)) { // common pattern: **/*.txt just need endsWith check const base = pattern.substr(4); // '**/*'.length === 4 parsedPattern = function (path, basename) { - return typeof path === 'string' && strings.endsWith(path, base) ? pattern : null; + return typeof path === 'string' && path.endsWith(base) ? pattern : null; }; } else if (match = T2.exec(trimForExclusions(pattern, options))) { // common pattern: **/some.txt just need basename check parsedPattern = trivia2(match[1], pattern); @@ -339,7 +339,7 @@ function wrapRelativePattern(parsedPattern: ParsedStringPattern, arg2: string | } function trimForExclusions(pattern: string, options: IGlobOptions): string { - return options.trimForExclusions && strings.endsWith(pattern, '/**') ? pattern.substr(0, pattern.length - 2) : pattern; // dropping **, tailing / is dropped later + return options.trimForExclusions && pattern.endsWith('/**') ? pattern.substr(0, pattern.length - 2) : pattern; // dropping **, tailing / is dropped later } // common pattern: **/some.txt just need basename check @@ -353,7 +353,7 @@ function trivia2(base: string, originalPattern: string): ParsedStringPattern { if (basename) { return basename === base ? originalPattern : null; } - return path === base || strings.endsWith(path, slashBase) || strings.endsWith(path, backslashBase) ? originalPattern : null; + return path === base || path.endsWith(slashBase) || path.endsWith(backslashBase) ? originalPattern : null; }; const basenames = [base]; parsedPattern.basenames = basenames; @@ -398,7 +398,7 @@ function trivia4and5(path: string, pattern: string, matchPathEnds: boolean): Par const nativePath = paths.sep !== paths.posix.sep ? path.replace(ALL_FORWARD_SLASHES, paths.sep) : path; const nativePathEnd = paths.sep + nativePath; const parsedPattern: ParsedStringPattern = matchPathEnds ? function (path, basename) { - return typeof path === 'string' && (path === nativePath || strings.endsWith(path, nativePathEnd)) ? pattern : null; + return typeof path === 'string' && (path === nativePath || path.endsWith(nativePathEnd)) ? pattern : null; } : function (path, basename) { return typeof path === 'string' && path === nativePath ? pattern : null; }; diff --git a/src/vs/base/common/hash.ts b/src/vs/base/common/hash.ts index 1902e82c312..4b47073d8e5 100644 --- a/src/vs/base/common/hash.ts +++ b/src/vs/base/common/hash.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as strings from 'vs/base/common/strings'; + /** * Return a hash value for an object. */ @@ -70,3 +72,235 @@ export class Hasher { return this._value; } } + +const enum SHA1Constant { + BLOCK_SIZE = 64, // 512 / 8 + UNICODE_REPLACEMENT = 0xFFFD, +} + +function leftRotate(value: number, bits: number, totalBits: number = 32): number { + // delta + bits = totalBits + const delta = totalBits - bits; + + // All ones, expect `delta` zeros aligned to the right + const mask = ~((1 << delta) - 1); + + // Join (value left-shifted `bits` bits) with (masked value right-shifted `delta` bits) + return ((value << bits) | ((mask & value) >>> delta)) >>> 0; +} + +function fill(dest: Uint8Array, index: number = 0, count: number = dest.byteLength, value: number = 0): void { + for (let i = 0; i < count; i++) { + dest[index + i] = value; + } +} + +function leftPad(value: string, length: number, char: string = '0'): string { + while (value.length < length) { + value = char + value; + } + return value; +} + +function toHexString(value: number, bitsize: number = 32): string { + return leftPad((value >>> 0).toString(16), bitsize / 4); +} + +/** + * A SHA1 implementation that works with strings and does not allocate. + */ +export class StringSHA1 { + private static _bigBlock32 = new DataView(new ArrayBuffer(320)); // 80 * 4 = 320 + + private _h0 = 0x67452301; + private _h1 = 0xEFCDAB89; + private _h2 = 0x98BADCFE; + private _h3 = 0x10325476; + private _h4 = 0xC3D2E1F0; + + private readonly _buff: Uint8Array; + private readonly _buffDV: DataView; + private _buffLen: number; + private _totalLen: number; + private _leftoverHighSurrogate: number; + private _finished: boolean; + + constructor() { + this._buff = new Uint8Array(SHA1Constant.BLOCK_SIZE + 3 /* to fit any utf-8 */); + this._buffDV = new DataView(this._buff.buffer); + this._buffLen = 0; + this._totalLen = 0; + this._leftoverHighSurrogate = 0; + this._finished = false; + } + + public update(str: string): void { + const strLen = str.length; + if (strLen === 0) { + return; + } + + const buff = this._buff; + let buffLen = this._buffLen; + let leftoverHighSurrogate = this._leftoverHighSurrogate; + let charCode: number; + let offset: number; + + if (leftoverHighSurrogate !== 0) { + charCode = leftoverHighSurrogate; + offset = -1; + leftoverHighSurrogate = 0; + } else { + charCode = str.charCodeAt(0); + offset = 0; + } + + while (true) { + let codePoint = charCode; + if (strings.isHighSurrogate(charCode)) { + if (offset + 1 < strLen) { + const nextCharCode = str.charCodeAt(offset + 1); + if (strings.isLowSurrogate(nextCharCode)) { + offset++; + codePoint = strings.computeCodePoint(charCode, nextCharCode); + } else { + // illegal => unicode replacement character + codePoint = SHA1Constant.UNICODE_REPLACEMENT; + } + } else { + // last character is a surrogate pair + leftoverHighSurrogate = charCode; + break; + } + } else if (strings.isLowSurrogate(charCode)) { + // illegal => unicode replacement character + codePoint = SHA1Constant.UNICODE_REPLACEMENT; + } + + buffLen = this._push(buff, buffLen, codePoint); + offset++; + if (offset < strLen) { + charCode = str.charCodeAt(offset); + } else { + break; + } + } + + this._buffLen = buffLen; + this._leftoverHighSurrogate = leftoverHighSurrogate; + } + + private _push(buff: Uint8Array, buffLen: number, codePoint: number): number { + if (codePoint < 0x0080) { + buff[buffLen++] = codePoint; + } else if (codePoint < 0x0800) { + buff[buffLen++] = 0b11000000 | ((codePoint & 0b00000000000000000000011111000000) >>> 6); + buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0); + } else if (codePoint < 0x10000) { + buff[buffLen++] = 0b11100000 | ((codePoint & 0b00000000000000001111000000000000) >>> 12); + buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6); + buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0); + } else { + buff[buffLen++] = 0b11110000 | ((codePoint & 0b00000000000111000000000000000000) >>> 18); + buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000111111000000000000) >>> 12); + buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6); + buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0); + } + + if (buffLen >= SHA1Constant.BLOCK_SIZE) { + this._step(); + buffLen -= SHA1Constant.BLOCK_SIZE; + this._totalLen += SHA1Constant.BLOCK_SIZE; + // take last 3 in case of UTF8 overflow + buff[0] = buff[SHA1Constant.BLOCK_SIZE + 0]; + buff[1] = buff[SHA1Constant.BLOCK_SIZE + 1]; + buff[2] = buff[SHA1Constant.BLOCK_SIZE + 2]; + } + + return buffLen; + } + + public digest(): string { + if (!this._finished) { + this._finished = true; + if (this._leftoverHighSurrogate) { + // illegal => unicode replacement character + this._leftoverHighSurrogate = 0; + this._buffLen = this._push(this._buff, this._buffLen, SHA1Constant.UNICODE_REPLACEMENT); + } + this._totalLen += this._buffLen; + this._wrapUp(); + } + + return toHexString(this._h0) + toHexString(this._h1) + toHexString(this._h2) + toHexString(this._h3) + toHexString(this._h4); + } + + private _wrapUp(): void { + this._buff[this._buffLen++] = 0x80; + fill(this._buff, this._buffLen); + + if (this._buffLen > 56) { + this._step(); + fill(this._buff); + } + + // this will fit because the mantissa can cover up to 52 bits + const ml = 8 * this._totalLen; + + this._buffDV.setUint32(56, Math.floor(ml / 4294967296), false); + this._buffDV.setUint32(60, ml % 4294967296, false); + + this._step(); + } + + private _step(): void { + const bigBlock32 = StringSHA1._bigBlock32; + const data = this._buffDV; + + for (let j = 0; j < 64 /* 16*4 */; j += 4) { + bigBlock32.setUint32(j, data.getUint32(j, false), false); + } + + for (let j = 64; j < 320 /* 80*4 */; j += 4) { + bigBlock32.setUint32(j, leftRotate((bigBlock32.getUint32(j - 12, false) ^ bigBlock32.getUint32(j - 32, false) ^ bigBlock32.getUint32(j - 56, false) ^ bigBlock32.getUint32(j - 64, false)), 1), false); + } + + let a = this._h0; + let b = this._h1; + let c = this._h2; + let d = this._h3; + let e = this._h4; + + let f: number, k: number; + let temp: number; + + for (let j = 0; j < 80; j++) { + if (j < 20) { + f = (b & c) | ((~b) & d); + k = 0x5A827999; + } else if (j < 40) { + f = b ^ c ^ d; + k = 0x6ED9EBA1; + } else if (j < 60) { + f = (b & c) | (b & d) | (c & d); + k = 0x8F1BBCDC; + } else { + f = b ^ c ^ d; + k = 0xCA62C1D6; + } + + temp = (leftRotate(a, 5) + f + e + k + bigBlock32.getUint32(j * 4, false)) & 0xffffffff; + e = d; + d = c; + c = leftRotate(b, 30); + b = a; + a = temp; + } + + this._h0 = (this._h0 + a) & 0xffffffff; + this._h1 = (this._h1 + b) & 0xffffffff; + this._h2 = (this._h2 + c) & 0xffffffff; + this._h3 = (this._h3 + d) & 0xffffffff; + this._h4 = (this._h4 + e) & 0xffffffff; + } +} diff --git a/src/vs/base/common/history.ts b/src/vs/base/common/history.ts index fb146b969f5..d633d611a32 100644 --- a/src/vs/base/common/history.ts +++ b/src/vs/base/common/history.ts @@ -28,11 +28,17 @@ export class HistoryNavigator implements INavigator { } public next(): T | null { - return this._navigator.next(); + if (this._currentPosition() !== this._elements.length - 1) { + return this._navigator.next(); + } + return null; } public previous(): T | null { - return this._navigator.previous(); + if (this._currentPosition() !== 0) { + return this._navigator.previous(); + } + return null; } public current(): T | null { @@ -73,6 +79,15 @@ export class HistoryNavigator implements INavigator { } } + private _currentPosition(): number { + const currentElement = this._navigator.current(); + if (!currentElement) { + return -1; + } + + return this._elements.indexOf(currentElement); + } + private _initialize(history: readonly T[]): void { this._history = new Set(); for (const entry of history) { diff --git a/src/vs/base/common/iterator.ts b/src/vs/base/common/iterator.ts index 6c87c85cf48..7586b3c8507 100644 --- a/src/vs/base/common/iterator.ts +++ b/src/vs/base/common/iterator.ts @@ -34,6 +34,36 @@ export interface NativeIterator { next(): NativeIteratorResult; } +export namespace Iterable { + + export function first(iterable: Iterable): T | undefined { + return iterable[Symbol.iterator]().next().value; + } + + export function some(iterable: Iterable, predicate: (t: T) => boolean): boolean { + for (const element of iterable) { + if (predicate(element)) { + return true; + } + } + return false; + } + + export function* filter(iterable: Iterable, predicate: (t: T) => boolean): Iterable { + for (const element of iterable) { + if (predicate(element)) { + return yield element; + } + } + } + + export function* map(iterable: Iterable, fn: (t: T) => R): Iterable { + for (const element of iterable) { + return yield fn(element); + } + } +} + export module Iterator { const _empty: Iterator = { next() { diff --git a/src/vs/base/common/labels.ts b/src/vs/base/common/labels.ts index 86a05d80658..b60c68d2c36 100644 --- a/src/vs/base/common/labels.ts +++ b/src/vs/base/common/labels.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { sep, posix, normalize, win32 } from 'vs/base/common/path'; -import { endsWith, startsWithIgnoreCase, rtrim, startsWith } from 'vs/base/common/strings'; +import { posix, normalize, win32, sep } from 'vs/base/common/path'; +import { startsWithIgnoreCase, rtrim } from 'vs/base/common/strings'; import { Schemas } from 'vs/base/common/network'; import { isLinux, isWindows, isMacintosh } from 'vs/base/common/platform'; import { isEqual, basename, relativePath } from 'vs/base/common/resources'; @@ -117,7 +117,7 @@ export function tildify(path: string, userHome: string): string { } // Linux: case sensitive, macOS: case insensitive - if (isLinux ? startsWith(path, normalizedUserHome) : startsWithIgnoreCase(path, normalizedUserHome)) { + if (isLinux ? path.startsWith(normalizedUserHome) : startsWithIgnoreCase(path, normalizedUserHome)) { path = `~/${path.substr(normalizedUserHome.length)}`; } @@ -160,7 +160,7 @@ export function untildify(path: string, userHome: string): string { const ellipsis = '\u2026'; const unc = '\\\\'; const home = '~'; -export function shorten(paths: string[]): string[] { +export function shorten(paths: string[], pathSeparator: string = sep): string[] { const shortenedPaths: string[] = new Array(paths.length); // for every path @@ -169,7 +169,7 @@ export function shorten(paths: string[]): string[] { let path = paths[pathIndex]; if (path === '') { - shortenedPaths[pathIndex] = `.${sep}`; + shortenedPaths[pathIndex] = `.${pathSeparator}`; continue; } @@ -185,20 +185,20 @@ export function shorten(paths: string[]): string[] { if (path.indexOf(unc) === 0) { prefix = path.substr(0, path.indexOf(unc) + unc.length); path = path.substr(path.indexOf(unc) + unc.length); - } else if (path.indexOf(sep) === 0) { - prefix = path.substr(0, path.indexOf(sep) + sep.length); - path = path.substr(path.indexOf(sep) + sep.length); + } else if (path.indexOf(pathSeparator) === 0) { + prefix = path.substr(0, path.indexOf(pathSeparator) + pathSeparator.length); + path = path.substr(path.indexOf(pathSeparator) + pathSeparator.length); } else if (path.indexOf(home) === 0) { prefix = path.substr(0, path.indexOf(home) + home.length); path = path.substr(path.indexOf(home) + home.length); } // pick the first shortest subpath found - const segments: string[] = path.split(sep); + const segments: string[] = path.split(pathSeparator); for (let subpathLength = 1; match && subpathLength <= segments.length; subpathLength++) { for (let start = segments.length - subpathLength; match && start >= 0; start--) { match = false; - let subpath = segments.slice(start, start + subpathLength).join(sep); + let subpath = segments.slice(start, start + subpathLength).join(pathSeparator); // that is unique to any other path for (let otherPathIndex = 0; !match && otherPathIndex < paths.length; otherPathIndex++) { @@ -209,8 +209,8 @@ export function shorten(paths: string[]): string[] { // Adding separator as prefix for subpath, such that 'endsWith(src, trgt)' considers subpath as directory name instead of plain string. // prefix is not added when either subpath is root directory or path[otherPathIndex] does not have multiple directories. - const subpathWithSep: string = (start > 0 && paths[otherPathIndex].indexOf(sep) > -1) ? sep + subpath : subpath; - const isOtherPathEnding: boolean = endsWith(paths[otherPathIndex], subpathWithSep); + const subpathWithSep: string = (start > 0 && paths[otherPathIndex].indexOf(pathSeparator) > -1) ? pathSeparator + subpath : subpath; + const isOtherPathEnding: boolean = paths[otherPathIndex].endsWith(subpathWithSep); match = !isSubpathEnding || isOtherPathEnding; } @@ -221,16 +221,16 @@ export function shorten(paths: string[]): string[] { let result = ''; // preserve disk drive or root prefix - if (endsWith(segments[0], ':') || prefix !== '') { + if (segments[0].endsWith(':') || prefix !== '') { if (start === 1) { // extend subpath to include disk drive prefix start = 0; subpathLength++; - subpath = segments[0] + sep + subpath; + subpath = segments[0] + pathSeparator + subpath; } if (start > 0) { - result = segments[0] + sep; + result = segments[0] + pathSeparator; } result = prefix + result; @@ -238,14 +238,14 @@ export function shorten(paths: string[]): string[] { // add ellipsis at the beginning if neeeded if (start > 0) { - result = result + ellipsis + sep; + result = result + ellipsis + pathSeparator; } result = result + subpath; // add ellipsis at the end if needed if (start + subpathLength < segments.length) { - result = result + sep + ellipsis; + result = result + pathSeparator + ellipsis; } shortenedPaths[pathIndex] = result; diff --git a/src/vs/base/common/lazy.ts b/src/vs/base/common/lazy.ts index 7f1bef48d65..ce6339106ef 100644 --- a/src/vs/base/common/lazy.ts +++ b/src/vs/base/common/lazy.ts @@ -21,7 +21,7 @@ export class Lazy { private _didRun: boolean = false; private _value?: T; - private _error: any; + private _error: Error | undefined; constructor( private readonly executor: () => T, diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index a99fc45bd50..7394eab4ce0 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -49,8 +49,7 @@ export interface IDisposable { } export function isDisposable(thing: E): thing is E & IDisposable { - return typeof (thing).dispose === 'function' - && (thing).dispose.length === 0; + return typeof (thing).dispose === 'function' && (thing).dispose.length === 0; } export function dispose(disposable: T): T; @@ -124,7 +123,7 @@ export class DisposableStore implements IDisposable { if (!t) { return t; } - if ((t as any as DisposableStore) === this) { + if ((t as unknown as DisposableStore) === this) { throw new Error('Cannot register a disposable on itself!'); } @@ -158,7 +157,7 @@ export abstract class Disposable implements IDisposable { } protected _register(t: T): T { - if ((t as any as Disposable) === this) { + if ((t as unknown as Disposable) === this) { throw new Error('Cannot register a disposable on itself!'); } return this._store.add(t); diff --git a/src/vs/base/common/linkedList.ts b/src/vs/base/common/linkedList.ts index 54bb9253de6..8ca17bc73f9 100644 --- a/src/vs/base/common/linkedList.ts +++ b/src/vs/base/common/linkedList.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Iterator, IteratorResult, FIN } from 'vs/base/common/iterator'; - class Node { static readonly Undefined = new Node(undefined); @@ -126,24 +124,12 @@ export class LinkedList { this._size -= 1; } - iterator(): Iterator { - let element: { done: false; value: E; }; + *[Symbol.iterator](): Iterator { let node = this._first; - return { - next(): IteratorResult { - if (node === Node.Undefined) { - return FIN; - } - - if (!element) { - element = { done: false, value: node.element }; - } else { - element.value = node.element; - } - node = node.next; - return element; - } - }; + while (node !== Node.Undefined) { + yield node.element; + node = node.next; + } } toArray(): E[] { diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index 4f6b55c3fe5..8b5639ed13b 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -5,9 +5,11 @@ import { URI } from 'vs/base/common/uri'; import { CharCode } from 'vs/base/common/charCode'; -import { Iterator, IteratorResult, FIN } from './iterator'; - +import { FIN } from './iterator'; +/** + * @deprecated ES6: use `[...SetOrMap.values()]` + */ export function values(set: Set): V[]; export function values(map: Map): V[]; export function values(forEachable: { forEach(callback: (value: V, ...more: any[]) => any): void }): V[] { @@ -16,6 +18,9 @@ export function values(forEachable: { forEach(callback: (value: V, ...more: a return result; } +/** + * @deprecated ES6: use `[...map.keys()]` + */ export function keys(map: Map): K[] { const result: K[] = []; map.forEach((_value, key) => result.push(key)); @@ -51,26 +56,6 @@ export function setToString(set: Set): string { return `Set(${set.size}) {${entries.join(', ')}}`; } -export function mapToSerializable(map: Map): [string, string][] { - const serializable: [string, string][] = []; - - map.forEach((value, key) => { - serializable.push([key, value]); - }); - - return serializable; -} - -export function serializableToMap(serializable: [string, string][]): Map { - const items = new Map(); - - for (const [key, value] of serializable) { - items.set(key, value); - } - - return items; -} - export interface IKeyIterator { reset(key: string): this; next(): this; diff --git a/src/vs/base/common/mime.ts b/src/vs/base/common/mime.ts index 0a3a692a307..0bf9ce1cc6b 100644 --- a/src/vs/base/common/mime.ts +++ b/src/vs/base/common/mime.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { basename, posix, extname } from 'vs/base/common/path'; -import { endsWith, startsWithUTF8BOM, startsWith } from 'vs/base/common/strings'; +import { startsWithUTF8BOM } from 'vs/base/common/strings'; import { coalesce } from 'vs/base/common/arrays'; import { match } from 'vs/base/common/glob'; import { URI } from 'vs/base/common/uri'; @@ -185,7 +185,7 @@ function guessMimeTypeByPath(path: string, filename: string, associations: IText // Longest extension match if (association.extension) { if (!extensionMatch || association.extension.length > extensionMatch.extension!.length) { - if (endsWith(filename, association.extensionLowercase!)) { + if (filename.endsWith(association.extensionLowercase!)) { extensionMatch = association; } } @@ -259,11 +259,11 @@ export function suggestFilename(mode: string | undefined, prefix: string): strin .map(assoc => assoc.extension); const extensionsWithDotFirst = coalesce(extensions) - .filter(assoc => startsWith(assoc, '.')); + .filter(assoc => assoc.startsWith('.')); if (extensionsWithDotFirst.length > 0) { const candidateExtension = extensionsWithDotFirst[0]; - if (endsWith(prefix, candidateExtension)) { + if (prefix.endsWith(candidateExtension)) { // do not add the prefix if it already exists // https://github.com/microsoft/vscode/issues/83603 return prefix; diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index a68e020f9f1..e4546b2cf60 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -53,6 +53,12 @@ export namespace Schemas { export const vscodeRemoteResource = 'vscode-remote-resource'; export const userData = 'vscode-userdata'; + + export const vscodeCustomEditor = 'vscode-custom-editor'; + + export const vscodeSettings = 'vscode-settings'; + + export const webviewPanel = 'webview-panel'; } class RemoteAuthoritiesImpl { diff --git a/src/vs/base/common/normalization.ts b/src/vs/base/common/normalization.ts index b6304df31a0..3a94fb716ec 100644 --- a/src/vs/base/common/normalization.ts +++ b/src/vs/base/common/normalization.ts @@ -11,7 +11,7 @@ import { LRUCache } from 'vs/base/common/map'; * * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize} */ -export const canNormalize = typeof (('').normalize) === 'function'; +export const canNormalize = typeof (String.prototype as any /* standalone editor compilation */).normalize === 'function'; const nfcCache = new LRUCache(10000); // bounded to 10000 elements export function normalizeNFC(str: string): string { @@ -46,3 +46,17 @@ function normalize(str: string, form: string, normalizedCache: LRUCache string = (function () { + if (!canNormalize) { + // no ES6 features... + return function (str: string) { return str; }; + } else { + // transform into NFD form and remove accents + // see: https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript/37511463#37511463 + const regex = /[\u0300-\u036f]/g; + return function (str: string) { + return normalizeNFD(str).replace(regex, ''); + }; + } +})(); diff --git a/src/vs/base/common/objects.ts b/src/vs/base/common/objects.ts index 1475bf4a550..4907ac1e4ea 100644 --- a/src/vs/base/common/objects.ts +++ b/src/vs/base/common/objects.ts @@ -113,6 +113,9 @@ export function mixin(destination: any, source: any, overwrite: boolean = true): return destination; } +/** + * @deprecated ES6 + */ export function assign(destination: T): T; export function assign(destination: T, u: U): T & U; export function assign(destination: T, u: U, v: V): T & U & V; diff --git a/src/vs/base/common/path.ts b/src/vs/base/common/path.ts index 5f1739053bb..d12384b6f16 100644 --- a/src/vs/base/common/path.ts +++ b/src/vs/base/common/path.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // NOTE: VSCode's copy of nodejs path library to be usable in common (non-node) namespace -// Copied from: https://github.com/nodejs/node/tree/43dd49c9782848c25e5b03448c8a0f923f13c158 +// Copied from: https://github.com/nodejs/node/blob/v12.8.1/lib/path.js /** * Copyright Joyent, Inc. and other Node contributors. @@ -88,7 +88,7 @@ function normalizeString(path: string, allowAboveRoot: boolean, separator: strin let lastSegmentLength = 0; let lastSlash = -1; let dots = 0; - let code; + let code = 0; for (let i = 0; i <= path.length; ++i) { if (i < path.length) { code = path.charCodeAt(i); @@ -103,7 +103,7 @@ function normalizeString(path: string, allowAboveRoot: boolean, separator: strin if (isPathSeparator(code)) { if (lastSlash === i - 1 || dots === 1) { // NOOP - } else if (lastSlash !== i - 1 && dots === 2) { + } else if (dots === 2) { if (res.length < 2 || lastSegmentLength !== 2 || res.charCodeAt(res.length - 1) !== CHAR_DOT || res.charCodeAt(res.length - 2) !== CHAR_DOT) { @@ -119,7 +119,7 @@ function normalizeString(path: string, allowAboveRoot: boolean, separator: strin lastSlash = i; dots = 0; continue; - } else if (res.length === 2 || res.length === 1) { + } else if (res.length !== 0) { res = ''; lastSegmentLength = 0; lastSlash = i; @@ -128,17 +128,12 @@ function normalizeString(path: string, allowAboveRoot: boolean, separator: strin } } if (allowAboveRoot) { - if (res.length > 0) { - res += `${separator}..`; - } - else { - res = '..'; - } + res += res.length > 0 ? `${separator}..` : '..'; lastSegmentLength = 2; } } else { if (res.length > 0) { - res += separator + path.slice(lastSlash + 1, i); + res += `${separator}${path.slice(lastSlash + 1, i)}`; } else { res = path.slice(lastSlash + 1, i); @@ -157,16 +152,16 @@ function normalizeString(path: string, allowAboveRoot: boolean, separator: strin } function _format(sep: string, pathObject: ParsedPath) { + if (pathObject === null || typeof pathObject !== 'object') { + throw new ErrorInvalidArgType('pathObject', 'Object', pathObject); + } const dir = pathObject.dir || pathObject.root; const base = pathObject.base || - ((pathObject.name || '') + (pathObject.ext || '')); + `${pathObject.name || ''}${pathObject.ext || ''}`; if (!dir) { return base; } - if (dir === pathObject.root) { - return dir + base; - } - return dir + sep + base; + return dir === pathObject.root ? `${dir}${base}` : `${dir}${sep}${base}`; } export interface ParsedPath { @@ -206,7 +201,13 @@ export const win32: IPath = { let path; if (i >= 0) { path = pathSegments[i]; - } else if (!resolvedDevice) { + validateString(path, 'path'); + + // Skip empty entries + if (path.length === 0) { + continue; + } + } else if (resolvedDevice.length === 0) { path = process.cwd(); } else { // Windows has the concept of drive-specific current working @@ -214,24 +215,17 @@ export const win32: IPath = { // absolute path, get cwd for that drive, or the process cwd if // the drive cwd is not available. We're sure the device is not // a UNC path at this points, because UNC paths are always absolute. - path = (process.env as any)['=' + resolvedDevice] || process.cwd(); + path = (process.env as any)[`=${resolvedDevice}`] || process.cwd(); // Verify that a cwd was found and that it actually points // to our drive. If not, default to the drive's root. if (path === undefined || - path.slice(0, 3).toLowerCase() !== - resolvedDevice.toLowerCase() + '\\') { - path = resolvedDevice + '\\'; + path.slice(0, 2).toLowerCase() !== resolvedDevice.toLowerCase() && + path.charCodeAt(2) === CHAR_BACKWARD_SLASH) { + path = `${resolvedDevice}\\`; } } - validateString(path, 'path'); - - // Skip empty entries - if (path.length === 0) { - continue; - } - const len = path.length; let rootEnd = 0; let device = ''; @@ -239,98 +233,86 @@ export const win32: IPath = { const code = path.charCodeAt(0); // Try to match a root - if (len > 1) { + if (len === 1) { if (isPathSeparator(code)) { - // Possible UNC root - - // If we started with a separator, we know we at least have an - // absolute path of some kind (UNC or otherwise) + // `path` contains just a path separator + rootEnd = 1; isAbsolute = true; - - if (isPathSeparator(path.charCodeAt(1))) { - // Matched double path separator at beginning - let j = 2; - let last = j; - // Match 1 or more non-path separators - for (; j < len; ++j) { - if (isPathSeparator(path.charCodeAt(j))) { - break; - } - } - if (j < len && j !== last) { - const firstPart = path.slice(last, j); - // Matched! - last = j; - // Match 1 or more path separators - for (; j < len; ++j) { - if (!isPathSeparator(path.charCodeAt(j))) { - break; - } - } - if (j < len && j !== last) { - // Matched! - last = j; - // Match 1 or more non-path separators - for (; j < len; ++j) { - if (isPathSeparator(path.charCodeAt(j))) { - break; - } - } - if (j === len) { - // We matched a UNC root only - - device = '\\\\' + firstPart + '\\' + path.slice(last); - rootEnd = j; - } else if (j !== last) { - // We matched a UNC root with leftovers - - device = '\\\\' + firstPart + '\\' + path.slice(last, j); - rootEnd = j; - } - } - } - } else { - rootEnd = 1; - } - } else if (isWindowsDeviceRoot(code)) { - // Possible device root - - if (path.charCodeAt(1) === CHAR_COLON) { - device = path.slice(0, 2); - rootEnd = 2; - if (len > 2) { - if (isPathSeparator(path.charCodeAt(2))) { - // Treat separator following drive name as an absolute path - // indicator - isAbsolute = true; - rootEnd = 3; - } - } - } } } else if (isPathSeparator(code)) { - // `path` contains just a path separator - rootEnd = 1; + // Possible UNC root + + // If we started with a separator, we know we at least have an + // absolute path of some kind (UNC or otherwise) isAbsolute = true; + + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + const firstPart = path.slice(last, j); + // Matched! + last = j; + // Match 1 or more path separators + while (j < len && isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j === len || j !== last) { + // We matched a UNC root + device = `\\\\${firstPart}\\${path.slice(last, j)}`; + rootEnd = j; + } + } + } + } else { + rootEnd = 1; + } + } else if (isWindowsDeviceRoot(code) && + path.charCodeAt(1) === CHAR_COLON) { + // Possible device root + device = path.slice(0, 2); + rootEnd = 2; + if (len > 2 && isPathSeparator(path.charCodeAt(2))) { + // Treat separator following drive name as an absolute path + // indicator + isAbsolute = true; + rootEnd = 3; + } } - if (device.length > 0 && - resolvedDevice.length > 0 && - device.toLowerCase() !== resolvedDevice.toLowerCase()) { - // This path points to another device so it is not applicable - continue; + if (device.length > 0) { + if (resolvedDevice.length > 0) { + if (device.toLowerCase() !== resolvedDevice.toLowerCase()) { + // This path points to another device so it is not applicable + continue; + } + } else { + resolvedDevice = device; + } } - if (resolvedDevice.length === 0 && device.length > 0) { - resolvedDevice = device; - } - if (!resolvedAbsolute) { - resolvedTail = path.slice(rootEnd) + '\\' + resolvedTail; + if (resolvedAbsolute) { + if (resolvedDevice.length > 0) { + break; + } + } else { + resolvedTail = `${path.slice(rootEnd)}\\${resolvedTail}`; resolvedAbsolute = isAbsolute; - } - - if (resolvedDevice.length > 0 && resolvedAbsolute) { - break; + if (isAbsolute && resolvedDevice.length > 0) { + break; + } } } @@ -342,8 +324,9 @@ export const win32: IPath = { resolvedTail = normalizeString(resolvedTail, !resolvedAbsolute, '\\', isPathSeparator); - return (resolvedDevice + (resolvedAbsolute ? '\\' : '') + resolvedTail) || - '.'; + return resolvedAbsolute ? + `${resolvedDevice}\\${resolvedTail}` : + `${resolvedDevice}${resolvedTail}` || '.'; }, normalize(path: string): string { @@ -358,89 +341,72 @@ export const win32: IPath = { const code = path.charCodeAt(0); // Try to match a root - if (len > 1) { - if (isPathSeparator(code)) { - // Possible UNC root + if (len === 1) { + // `path` contains just a single char, exit early to avoid + // unnecessary work + return isPosixPathSeparator(code) ? '\\' : path; + } + if (isPathSeparator(code)) { + // Possible UNC root - // If we started with a separator, we know we at least have an absolute - // path of some kind (UNC or otherwise) - isAbsolute = true; + // If we started with a separator, we know we at least have an absolute + // path of some kind (UNC or otherwise) + isAbsolute = true; - if (isPathSeparator(path.charCodeAt(1))) { - // Matched double path separator at beginning - let j = 2; - let last = j; - // Match 1 or more non-path separators - for (; j < len; ++j) { - if (isPathSeparator(path.charCodeAt(j))) { - break; - } + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + const firstPart = path.slice(last, j); + // Matched! + last = j; + // Match 1 or more path separators + while (j < len && isPathSeparator(path.charCodeAt(j))) { + j++; } if (j < len && j !== last) { - const firstPart = path.slice(last, j); // Matched! last = j; - // Match 1 or more path separators - for (; j < len; ++j) { - if (!isPathSeparator(path.charCodeAt(j))) { - break; - } + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; } - if (j < len && j !== last) { - // Matched! - last = j; - // Match 1 or more non-path separators - for (; j < len; ++j) { - if (isPathSeparator(path.charCodeAt(j))) { - break; - } - } - if (j === len) { - // We matched a UNC root only - // Return the normalized version of the UNC root since there - // is nothing left to process - - return '\\\\' + firstPart + '\\' + path.slice(last) + '\\'; - } else if (j !== last) { - // We matched a UNC root with leftovers - - device = '\\\\' + firstPart + '\\' + path.slice(last, j); - rootEnd = j; - } + if (j === len) { + // We matched a UNC root only + // Return the normalized version of the UNC root since there + // is nothing left to process + return `\\\\${firstPart}\\${path.slice(last)}\\`; } - } - } else { - rootEnd = 1; - } - } else if (isWindowsDeviceRoot(code)) { - // Possible device root - - if (path.charCodeAt(1) === CHAR_COLON) { - device = path.slice(0, 2); - rootEnd = 2; - if (len > 2) { - if (isPathSeparator(path.charCodeAt(2))) { - // Treat separator following drive name as an absolute path - // indicator - isAbsolute = true; - rootEnd = 3; + if (j !== last) { + // We matched a UNC root with leftovers + device = `\\\\${firstPart}\\${path.slice(last, j)}`; + rootEnd = j; } } } + } else { + rootEnd = 1; + } + } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) { + // Possible device root + device = path.slice(0, 2); + rootEnd = 2; + if (len > 2 && isPathSeparator(path.charCodeAt(2))) { + // Treat separator following drive name as an absolute path + // indicator + isAbsolute = true; + rootEnd = 3; } - } else if (isPathSeparator(code)) { - // `path` contains just a path separator, exit early to avoid unnecessary - // work - return '\\'; } - let tail; - if (rootEnd < len) { - tail = normalizeString(path.slice(rootEnd), !isAbsolute, '\\', - isPathSeparator); - } else { - tail = ''; - } + let tail = rootEnd < len ? + normalizeString(path.slice(rootEnd), !isAbsolute, '\\', isPathSeparator) : + ''; if (tail.length === 0 && !isAbsolute) { tail = '.'; } @@ -448,30 +414,9 @@ export const win32: IPath = { tail += '\\'; } if (device === undefined) { - if (isAbsolute) { - if (tail.length > 0) { - return '\\' + tail; - } - else { - return '\\'; - } - } else if (tail.length > 0) { - return tail; - } else { - return ''; - } - } else if (isAbsolute) { - if (tail.length > 0) { - return device + '\\' + tail; - } - else { - return device + '\\'; - } - } else if (tail.length > 0) { - return device + tail; - } else { - return device; + return isAbsolute ? `\\${tail}` : tail; } + return isAbsolute ? `${device}\\${tail}` : `${device}${tail}`; }, isAbsolute(path: string): boolean { @@ -482,18 +427,12 @@ export const win32: IPath = { } const code = path.charCodeAt(0); - if (isPathSeparator(code)) { - return true; - } else if (isWindowsDeviceRoot(code)) { + return isPathSeparator(code) || // Possible device root - - if (len > 2 && path.charCodeAt(1) === CHAR_COLON) { - if (isPathSeparator(path.charCodeAt(2))) { - return true; - } - } - } - return false; + len > 2 && + isWindowsDeviceRoot(code) && + path.charCodeAt(1) === CHAR_COLON && + isPathSeparator(path.charCodeAt(2)); }, join(...paths: string[]): string { @@ -511,7 +450,7 @@ export const win32: IPath = { joined = firstPart = arg; } else { - joined += '\\' + arg; + joined += `\\${arg}`; } } } @@ -538,32 +477,28 @@ export const win32: IPath = { if (typeof firstPart === 'string' && isPathSeparator(firstPart.charCodeAt(0))) { ++slashCount; const firstLen = firstPart.length; - if (firstLen > 1) { - if (isPathSeparator(firstPart.charCodeAt(1))) { - ++slashCount; - if (firstLen > 2) { - if (isPathSeparator(firstPart.charCodeAt(2))) { - ++slashCount; - } - else { - // We matched a UNC path in the first part - needsReplace = false; - } + if (firstLen > 1 && isPathSeparator(firstPart.charCodeAt(1))) { + ++slashCount; + if (firstLen > 2) { + if (isPathSeparator(firstPart.charCodeAt(2))) { + ++slashCount; + } else { + // We matched a UNC path in the first part + needsReplace = false; } } } } if (needsReplace) { // Find any more consecutive slashes we need to replace - for (; slashCount < joined.length; ++slashCount) { - if (!isPathSeparator(joined.charCodeAt(slashCount))) { - break; - } + while (slashCount < joined.length && + isPathSeparator(joined.charCodeAt(slashCount))) { + slashCount++; } // Replace the slashes if needed if (slashCount >= 2) { - joined = '\\' + joined.slice(slashCount); + joined = `\\${joined.slice(slashCount)}`; } } @@ -599,111 +534,102 @@ export const win32: IPath = { // Trim any leading backslashes let fromStart = 0; - for (; fromStart < from.length; ++fromStart) { - if (from.charCodeAt(fromStart) !== CHAR_BACKWARD_SLASH) { - break; - } + while (fromStart < from.length && + from.charCodeAt(fromStart) === CHAR_BACKWARD_SLASH) { + fromStart++; } // Trim trailing backslashes (applicable to UNC paths only) let fromEnd = from.length; - for (; fromEnd - 1 > fromStart; --fromEnd) { - if (from.charCodeAt(fromEnd - 1) !== CHAR_BACKWARD_SLASH) { - break; - } + while (fromEnd - 1 > fromStart && + from.charCodeAt(fromEnd - 1) === CHAR_BACKWARD_SLASH) { + fromEnd--; } - const fromLen = (fromEnd - fromStart); + const fromLen = fromEnd - fromStart; // Trim any leading backslashes let toStart = 0; - for (; toStart < to.length; ++toStart) { - if (to.charCodeAt(toStart) !== CHAR_BACKWARD_SLASH) { - break; - } + while (toStart < to.length && + to.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) { + toStart++; } // Trim trailing backslashes (applicable to UNC paths only) let toEnd = to.length; - for (; toEnd - 1 > toStart; --toEnd) { - if (to.charCodeAt(toEnd - 1) !== CHAR_BACKWARD_SLASH) { - break; - } + while (toEnd - 1 > toStart && + to.charCodeAt(toEnd - 1) === CHAR_BACKWARD_SLASH) { + toEnd--; } - const toLen = (toEnd - toStart); + const toLen = toEnd - toStart; // Compare paths to find the longest common path from root - const length = (fromLen < toLen ? fromLen : toLen); + const length = fromLen < toLen ? fromLen : toLen; let lastCommonSep = -1; let i = 0; - for (; i <= length; ++i) { - if (i === length) { - if (toLen > length) { - if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) { - // We get here if `from` is the exact base path for `to`. - // For example: from='C:\\foo\\bar'; to='C:\\foo\\bar\\baz' - return toOrig.slice(toStart + i + 1); - } else if (i === 2) { - // We get here if `from` is the device root. - // For example: from='C:\\'; to='C:\\foo' - return toOrig.slice(toStart + i); - } - } - if (fromLen > length) { - if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) { - // We get here if `to` is the exact base path for `from`. - // For example: from='C:\\foo\\bar'; to='C:\\foo' - lastCommonSep = i; - } else if (i === 2) { - // We get here if `to` is the device root. - // For example: from='C:\\foo\\bar'; to='C:\\' - lastCommonSep = 3; - } - } - break; - } + for (; i < length; i++) { const fromCode = from.charCodeAt(fromStart + i); - const toCode = to.charCodeAt(toStart + i); - if (fromCode !== toCode) { + if (fromCode !== to.charCodeAt(toStart + i)) { break; - } - else if (fromCode === CHAR_BACKWARD_SLASH) { + } else if (fromCode === CHAR_BACKWARD_SLASH) { lastCommonSep = i; } } // We found a mismatch before the first common path separator was seen, so // return the original `to`. - if (i !== length && lastCommonSep === -1) { - return toOrig; + if (i !== length) { + if (lastCommonSep === -1) { + return toOrig; + } + } else { + if (toLen > length) { + if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) { + // We get here if `from` is the exact base path for `to`. + // For example: from='C:\\foo\\bar'; to='C:\\foo\\bar\\baz' + return toOrig.slice(toStart + i + 1); + } + if (i === 2) { + // We get here if `from` is the device root. + // For example: from='C:\\'; to='C:\\foo' + return toOrig.slice(toStart + i); + } + } + if (fromLen > length) { + if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) { + // We get here if `to` is the exact base path for `from`. + // For example: from='C:\\foo\\bar'; to='C:\\foo' + lastCommonSep = i; + } else if (i === 2) { + // We get here if `to` is the device root. + // For example: from='C:\\foo\\bar'; to='C:\\' + lastCommonSep = 3; + } + } + if (lastCommonSep === -1) { + lastCommonSep = 0; + } } let out = ''; - if (lastCommonSep === -1) { - lastCommonSep = 0; - } // Generate the relative path based on the path difference between `to` and // `from` for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { if (i === fromEnd || from.charCodeAt(i) === CHAR_BACKWARD_SLASH) { - if (out.length === 0) { - out += '..'; - } - else { - out += '\\..'; - } + out += out.length === 0 ? '..' : '\\..'; } } + toStart += lastCommonSep; + // Lastly, append the rest of the destination (`to`) path that comes after // the common path parts if (out.length > 0) { - return out + toOrig.slice(toStart + lastCommonSep, toEnd); + return `${out}${toOrig.slice(toStart, toEnd)}`; } - else { - toStart += lastCommonSep; - if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) { - ++toStart; - } - return toOrig.slice(toStart, toEnd); + + if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) { + ++toStart; } + + return toOrig.slice(toStart, toEnd); }, toNamespacedPath(path: string): string { @@ -718,26 +644,24 @@ export const win32: IPath = { const resolvedPath = win32.resolve(path); - if (resolvedPath.length >= 3) { - if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) { - // Possible UNC root + if (resolvedPath.length <= 2) { + return path; + } - if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) { - const code = resolvedPath.charCodeAt(2); - if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) { - // Matched non-long UNC root, convert the path to a long UNC path - return '\\\\?\\UNC\\' + resolvedPath.slice(2); - } - } - } else if (isWindowsDeviceRoot(resolvedPath.charCodeAt(0))) { - // Possible device root - - if (resolvedPath.charCodeAt(1) === CHAR_COLON && - resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH) { - // Matched device root, convert the path to a long UNC path - return '\\\\?\\' + resolvedPath; + if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) { + // Possible UNC root + if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) { + const code = resolvedPath.charCodeAt(2); + if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) { + // Matched non-long UNC root, convert the path to a long UNC path + return `\\\\?\\UNC\\${resolvedPath.slice(2)}`; } } + } else if (isWindowsDeviceRoot(resolvedPath.charCodeAt(0)) && + resolvedPath.charCodeAt(1) === CHAR_COLON && + resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH) { + // Matched device root, convert the path to a long UNC path + return `\\\\?\\${resolvedPath}`; } return path; @@ -750,78 +674,65 @@ export const win32: IPath = { return '.'; } let rootEnd = -1; - let end = -1; - let matchedSlash = true; let offset = 0; const code = path.charCodeAt(0); + if (len === 1) { + // `path` contains just a path separator, exit early to avoid + // unnecessary work or a dot. + return isPathSeparator(code) ? path : '.'; + } + // Try to match a root - if (len > 1) { - if (isPathSeparator(code)) { - // Possible UNC root + if (isPathSeparator(code)) { + // Possible UNC root - rootEnd = offset = 1; + rootEnd = offset = 1; - if (isPathSeparator(path.charCodeAt(1))) { - // Matched double path separator at beginning - let j = 2; - let last = j; - // Match 1 or more non-path separators - for (; j < len; ++j) { - if (isPathSeparator(path.charCodeAt(j))) { - break; - } + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more path separators + while (j < len && isPathSeparator(path.charCodeAt(j))) { + j++; } if (j < len && j !== last) { // Matched! last = j; - // Match 1 or more path separators - for (; j < len; ++j) { - if (!isPathSeparator(path.charCodeAt(j))) { - break; - } + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; } - if (j < len && j !== last) { - // Matched! - last = j; - // Match 1 or more non-path separators - for (; j < len; ++j) { - if (isPathSeparator(path.charCodeAt(j))) { - break; - } - } - if (j === len) { - // We matched a UNC root only - return path; - } - if (j !== last) { - // We matched a UNC root with leftovers - - // Offset by 1 to include the separator after the UNC root to - // treat it as a "normal root" on top of a (UNC) root - rootEnd = offset = j + 1; - } + if (j === len) { + // We matched a UNC root only + return path; } - } - } - } else if (isWindowsDeviceRoot(code)) { - // Possible device root + if (j !== last) { + // We matched a UNC root with leftovers - if (path.charCodeAt(1) === CHAR_COLON) { - rootEnd = offset = 2; - if (len > 2) { - if (isPathSeparator(path.charCodeAt(2))) { - rootEnd = offset = 3; + // Offset by 1 to include the separator after the UNC root to + // treat it as a "normal root" on top of a (UNC) root + rootEnd = offset = j + 1; } } } } - } else if (isPathSeparator(code)) { - // `path` contains just a path separator, exit early to avoid - // unnecessary work - return path; + // Possible device root + } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) { + rootEnd = len > 2 && isPathSeparator(path.charCodeAt(2)) ? 3 : 2; + offset = rootEnd; } + let end = -1; + let matchedSlash = true; for (let i = len - 1; i >= offset; --i) { if (isPathSeparator(path.charCodeAt(i))) { if (!matchedSlash) { @@ -838,9 +749,8 @@ export const win32: IPath = { if (rootEnd === -1) { return '.'; } - else { - end = rootEnd; - } + + end = rootEnd; } return path.slice(0, end); }, @@ -858,17 +768,14 @@ export const win32: IPath = { // Check for a drive letter prefix so as not to mistake the following // path separator as an extra separator at the end of the path that can be // disregarded - if (path.length >= 2) { - const drive = path.charCodeAt(0); - if (isWindowsDeviceRoot(drive)) { - if (path.charCodeAt(1) === CHAR_COLON) { - start = 2; - } - } + if (path.length >= 2 && + isWindowsDeviceRoot(path.charCodeAt(0)) && + path.charCodeAt(1) === CHAR_COLON) { + start = 2; } if (ext !== undefined && ext.length > 0 && ext.length <= path.length) { - if (ext.length === path.length && ext === path) { + if (ext === path) { return ''; } let extIdx = ext.length - 1; @@ -909,33 +816,31 @@ export const win32: IPath = { if (start === end) { end = firstNonSlashEnd; - } - else if (end === -1) { + } else if (end === -1) { end = path.length; } return path.slice(start, end); - } else { - for (i = path.length - 1; i >= start; --i) { - if (isPathSeparator(path.charCodeAt(i))) { - // If we reached a path separator that was not part of a set of path - // separators at the end of the string, stop now - if (!matchedSlash) { - start = i + 1; - break; - } - } else if (end === -1) { - // We saw the first non-path separator, mark this as the end of our - // path component - matchedSlash = false; - end = i + 1; - } - } - - if (end === -1) { - return ''; - } - return path.slice(start, end); } + for (i = path.length - 1; i >= start; --i) { + if (isPathSeparator(path.charCodeAt(i))) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // path component + matchedSlash = false; + end = i + 1; + } + } + + if (end === -1) { + return ''; + } + return path.slice(start, end); }, extname(path: string): string { @@ -1004,14 +909,7 @@ export const win32: IPath = { return path.slice(startDot, end); }, - format(pathObject): string { - if (pathObject === null || typeof pathObject !== 'object') { - throw new ErrorInvalidArgType('pathObject', 'Object', pathObject); - } - - return _format('\\', pathObject); - }, - + format: _format.bind(null, '\\'), parse(path) { validateString(path, 'path'); @@ -1025,82 +923,72 @@ export const win32: IPath = { let rootEnd = 0; let code = path.charCodeAt(0); - // Try to match a root - if (len > 1) { + if (len === 1) { if (isPathSeparator(code)) { - // Possible UNC root + // `path` contains just a path separator, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + return ret; + } + ret.base = ret.name = path; + return ret; + } + // Try to match a root + if (isPathSeparator(code)) { + // Possible UNC root - rootEnd = 1; - if (isPathSeparator(path.charCodeAt(1))) { - // Matched double path separator at beginning - let j = 2; - let last = j; - // Match 1 or more non-path separators - for (; j < len; ++j) { - if (isPathSeparator(path.charCodeAt(j))) { - break; - } + rootEnd = 1; + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more path separators + while (j < len && isPathSeparator(path.charCodeAt(j))) { + j++; } if (j < len && j !== last) { // Matched! last = j; - // Match 1 or more path separators - for (; j < len; ++j) { - if (!isPathSeparator(path.charCodeAt(j))) { - break; - } + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; } - if (j < len && j !== last) { - // Matched! - last = j; - // Match 1 or more non-path separators - for (; j < len; ++j) { - if (isPathSeparator(path.charCodeAt(j))) { - break; - } - } - if (j === len) { - // We matched a UNC root only - - rootEnd = j; - } else if (j !== last) { - // We matched a UNC root with leftovers - - rootEnd = j + 1; - } + if (j === len) { + // We matched a UNC root only + rootEnd = j; + } else if (j !== last) { + // We matched a UNC root with leftovers + rootEnd = j + 1; } } } - } else if (isWindowsDeviceRoot(code)) { - // Possible device root - - if (path.charCodeAt(1) === CHAR_COLON) { - rootEnd = 2; - if (len > 2) { - if (isPathSeparator(path.charCodeAt(2))) { - if (len === 3) { - // `path` contains just a drive root, exit early to avoid - // unnecessary work - ret.root = ret.dir = path; - return ret; - } - rootEnd = 3; - } - } else { - // `path` contains just a drive root, exit early to avoid - // unnecessary work - ret.root = ret.dir = path; - return ret; - } - } } - } else if (isPathSeparator(code)) { - // `path` contains just a path separator, exit early to avoid - // unnecessary work - ret.root = ret.dir = path; - return ret; + } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) { + // Possible device root + if (len <= 2) { + // `path` contains just a drive root, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + return ret; + } + rootEnd = 2; + if (isPathSeparator(path.charCodeAt(2))) { + if (len === 3) { + // `path` contains just a drive root, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + return ret; + } + rootEnd = 3; + } } - if (rootEnd > 0) { ret.root = path.slice(0, rootEnd); } @@ -1137,8 +1025,7 @@ export const win32: IPath = { // If this is our first dot, mark it as the start of our extension if (startDot === -1) { startDot = i; - } - else if (preDotState !== 1) { + } else if (preDotState !== 1) { preDotState = 1; } } else if (startDot !== -1) { @@ -1148,21 +1035,20 @@ export const win32: IPath = { } } - if (startDot === -1 || - end === -1 || - // We saw a non-dot character immediately before the dot - preDotState === 0 || - // The (right-most) trimmed path component is exactly '..' - (preDotState === 1 && - startDot === end - 1 && - startDot === startPart + 1)) { - if (end !== -1) { + if (end !== -1) { + if (startDot === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && + startDot === end - 1 && + startDot === startPart + 1)) { ret.base = ret.name = path.slice(startPart, end); + } else { + ret.name = path.slice(startPart, startDot); + ret.base = path.slice(startPart, end); + ret.ext = path.slice(startDot, end); } - } else { - ret.name = path.slice(startPart, startDot); - ret.base = path.slice(startPart, end); - ret.ext = path.slice(startDot, end); } // If the directory is the root, use the entire root as the `dir` including @@ -1170,8 +1056,7 @@ export const win32: IPath = { // trailing slash (`C:\abc\def` -> `C:\abc`). if (startPart > 0 && startPart !== rootEnd) { ret.dir = path.slice(0, startPart - 1); - } - else { + } else { ret.dir = ret.root; } @@ -1191,13 +1076,7 @@ export const posix: IPath = { let resolvedAbsolute = false; for (let i = pathSegments.length - 1; i >= -1 && !resolvedAbsolute; i--) { - let path; - if (i >= 0) { - path = pathSegments[i]; - } - else { - path = process.cwd(); - } + const path = i >= 0 ? pathSegments[i] : process.cwd(); validateString(path, 'path'); @@ -1206,7 +1085,7 @@ export const posix: IPath = { continue; } - resolvedPath = path + '/' + resolvedPath; + resolvedPath = `${path}/${resolvedPath}`; resolvedAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; } @@ -1218,17 +1097,9 @@ export const posix: IPath = { isPosixPathSeparator); if (resolvedAbsolute) { - if (resolvedPath.length > 0) { - return '/' + resolvedPath; - } - else { - return '/'; - } - } else if (resolvedPath.length > 0) { - return resolvedPath; - } else { - return '.'; + return `/${resolvedPath}`; } + return resolvedPath.length > 0 ? resolvedPath : '.'; }, normalize(path: string): string { @@ -1245,17 +1116,17 @@ export const posix: IPath = { // Normalize the path path = normalizeString(path, !isAbsolute, '/', isPosixPathSeparator); - if (path.length === 0 && !isAbsolute) { - path = '.'; + if (path.length === 0) { + if (isAbsolute) { + return '/'; + } + return trailingSeparator ? './' : '.'; } - if (path.length > 0 && trailingSeparator) { + if (trailingSeparator) { path += '/'; } - if (isAbsolute) { - return '/' + path; - } - return path; + return isAbsolute ? `/${path}` : path; }, isAbsolute(path: string): boolean { @@ -1269,14 +1140,13 @@ export const posix: IPath = { } let joined; for (let i = 0; i < paths.length; ++i) { - const arg = arguments[i]; + const arg = paths[i]; validateString(arg, 'path'); if (arg.length > 0) { if (joined === undefined) { joined = arg; - } - else { - joined += '/' + arg; + } else { + joined += `/${arg}`; } } } @@ -1294,6 +1164,7 @@ export const posix: IPath = { return ''; } + // Trim leading forward slashes. from = posix.resolve(from); to = posix.resolve(to); @@ -1301,91 +1172,61 @@ export const posix: IPath = { return ''; } - // Trim any leading backslashes - let fromStart = 1; - for (; fromStart < from.length; ++fromStart) { - if (from.charCodeAt(fromStart) !== CHAR_FORWARD_SLASH) { - break; - } - } + const fromStart = 1; const fromEnd = from.length; - const fromLen = (fromEnd - fromStart); - - // Trim any leading backslashes - let toStart = 1; - for (; toStart < to.length; ++toStart) { - if (to.charCodeAt(toStart) !== CHAR_FORWARD_SLASH) { - break; - } - } - const toEnd = to.length; - const toLen = (toEnd - toStart); + const fromLen = fromEnd - fromStart; + const toStart = 1; + const toLen = to.length - toStart; // Compare paths to find the longest common path from root const length = (fromLen < toLen ? fromLen : toLen); let lastCommonSep = -1; let i = 0; - for (; i <= length; ++i) { - if (i === length) { - if (toLen > length) { - if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) { - // We get here if `from` is the exact base path for `to`. - // For example: from='/foo/bar'; to='/foo/bar/baz' - return to.slice(toStart + i + 1); - } else if (i === 0) { - // We get here if `from` is the root - // For example: from='/'; to='/foo' - return to.slice(toStart + i); - } - } else if (fromLen > length) { - if (from.charCodeAt(fromStart + i) === CHAR_FORWARD_SLASH) { - // We get here if `to` is the exact base path for `from`. - // For example: from='/foo/bar/baz'; to='/foo/bar' - lastCommonSep = i; - } else if (i === 0) { - // We get here if `to` is the root. - // For example: from='/foo'; to='/' - lastCommonSep = 0; - } - } - break; - } + for (; i < length; i++) { const fromCode = from.charCodeAt(fromStart + i); - const toCode = to.charCodeAt(toStart + i); - if (fromCode !== toCode) { + if (fromCode !== to.charCodeAt(toStart + i)) { break; - } - else if (fromCode === CHAR_FORWARD_SLASH) { + } else if (fromCode === CHAR_FORWARD_SLASH) { lastCommonSep = i; } } + if (i === length) { + if (toLen > length) { + if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) { + // We get here if `from` is the exact base path for `to`. + // For example: from='/foo/bar'; to='/foo/bar/baz' + return to.slice(toStart + i + 1); + } + if (i === 0) { + // We get here if `from` is the root + // For example: from='/'; to='/foo' + return to.slice(toStart + i); + } + } else if (fromLen > length) { + if (from.charCodeAt(fromStart + i) === CHAR_FORWARD_SLASH) { + // We get here if `to` is the exact base path for `from`. + // For example: from='/foo/bar/baz'; to='/foo/bar' + lastCommonSep = i; + } else if (i === 0) { + // We get here if `to` is the root. + // For example: from='/foo/bar'; to='/' + lastCommonSep = 0; + } + } + } let out = ''; // Generate the relative path based on the path difference between `to` - // and `from` + // and `from`. for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { if (i === fromEnd || from.charCodeAt(i) === CHAR_FORWARD_SLASH) { - if (out.length === 0) { - out += '..'; - } - else { - out += '/..'; - } + out += out.length === 0 ? '..' : '/..'; } } // Lastly, append the rest of the destination (`to`) path that comes after - // the common path parts - if (out.length > 0) { - return out + to.slice(toStart + lastCommonSep); - } - else { - toStart += lastCommonSep; - if (to.charCodeAt(toStart) === CHAR_FORWARD_SLASH) { - ++toStart; - } - return to.slice(toStart); - } + // the common path parts. + return `${out}${to.slice(toStart + lastCommonSep)}`; }, toNamespacedPath(path: string): string { @@ -1434,7 +1275,7 @@ export const posix: IPath = { let i; if (ext !== undefined && ext.length > 0 && ext.length <= path.length) { - if (ext.length === path.length && ext === path) { + if (ext === path) { return ''; } let extIdx = ext.length - 1; @@ -1475,33 +1316,31 @@ export const posix: IPath = { if (start === end) { end = firstNonSlashEnd; - } - else if (end === -1) { + } else if (end === -1) { end = path.length; } return path.slice(start, end); - } else { - for (i = path.length - 1; i >= 0; --i) { - if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) { - // If we reached a path separator that was not part of a set of path - // separators at the end of the string, stop now - if (!matchedSlash) { - start = i + 1; - break; - } - } else if (end === -1) { - // We saw the first non-path separator, mark this as the end of our - // path component - matchedSlash = false; - end = i + 1; - } - } - - if (end === -1) { - return ''; - } - return path.slice(start, end); } + for (i = path.length - 1; i >= 0; --i) { + if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // path component + matchedSlash = false; + end = i + 1; + } + } + + if (end === -1) { + return ''; + } + return path.slice(start, end); }, extname(path: string): string { @@ -1558,13 +1397,7 @@ export const posix: IPath = { return path.slice(startDot, end); }, - format(pathObject): string { - if (pathObject === null || typeof pathObject !== 'object') { - throw new ErrorInvalidArgType('pathObject', 'Object', pathObject); - } - - return _format('/', pathObject); - }, + format: _format.bind(null, '/'), parse(path: string): ParsedPath { validateString(path, 'path'); @@ -1613,8 +1446,7 @@ export const posix: IPath = { // If this is our first dot, mark it as the start of our extension if (startDot === -1) { startDot = i; - } - else if (preDotState !== 1) { + } else if (preDotState !== 1) { preDotState = 1; } } else if (startDot !== -1) { @@ -1624,37 +1456,26 @@ export const posix: IPath = { } } - if (startDot === -1 || - end === -1 || - // We saw a non-dot character immediately before the dot - preDotState === 0 || - // The (right-most) trimmed path component is exactly '..' - (preDotState === 1 && - startDot === end - 1 && - startDot === startPart + 1)) { - if (end !== -1) { - if (startPart === 0 && isAbsolute) { - ret.base = ret.name = path.slice(1, end); - } - else { - ret.base = ret.name = path.slice(startPart, end); - } - } - } else { - if (startPart === 0 && isAbsolute) { - ret.name = path.slice(1, startDot); - ret.base = path.slice(1, end); + if (end !== -1) { + const start = startPart === 0 && isAbsolute ? 1 : startPart; + if (startDot === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && + startDot === end - 1 && + startDot === startPart + 1)) { + ret.base = ret.name = path.slice(start, end); } else { - ret.name = path.slice(startPart, startDot); - ret.base = path.slice(startPart, end); + ret.name = path.slice(start, startDot); + ret.base = path.slice(start, end); + ret.ext = path.slice(startDot, end); } - ret.ext = path.slice(startDot, end); } if (startPart > 0) { ret.dir = path.slice(0, startPart - 1); - } - else if (isAbsolute) { + } else if (isAbsolute) { ret.dir = '/'; } diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts index 5a631e0b395..2c30aaa188f 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -209,3 +209,17 @@ export const enum OperatingSystem { Linux = 3 } export const OS = (_isMacintosh ? OperatingSystem.Macintosh : (_isWindows ? OperatingSystem.Windows : OperatingSystem.Linux)); + +let _isLittleEndian = true; +let _isLittleEndianComputed = false; +export function isLittleEndian(): boolean { + if (!_isLittleEndianComputed) { + _isLittleEndianComputed = true; + const test = new Uint8Array(2); + test[0] = 1; + test[1] = 2; + const view = new Uint16Array(test.buffer); + _isLittleEndian = (view[0] === (2 << 8) + 1); + } + return _isLittleEndian; +} diff --git a/src/vs/base/common/resourceTree.ts b/src/vs/base/common/resourceTree.ts index 4adca4ca4f2..2de60729275 100644 --- a/src/vs/base/common/resourceTree.ts +++ b/src/vs/base/common/resourceTree.ts @@ -8,8 +8,7 @@ import * as paths from 'vs/base/common/path'; import { Iterator } from 'vs/base/common/iterator'; import { relativePath, joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { mapValues } from 'vs/base/common/collections'; -import { PathIterator } from 'vs/base/common/map'; +import { PathIterator, values } from 'vs/base/common/map'; export interface IResourceNode { readonly uri: URI; @@ -32,7 +31,7 @@ class Node implements IResourceNode { } get children(): Iterator> { - return Iterator.fromArray(mapValues(this._children)); + return Iterator.fromArray(values(this._children)); } @memoize diff --git a/src/vs/base/common/resources.ts b/src/vs/base/common/resources.ts index 05610a4750b..acd7ae00bfc 100644 --- a/src/vs/base/common/resources.ts +++ b/src/vs/base/common/resources.ts @@ -13,8 +13,39 @@ import { CharCode } from 'vs/base/common/charCode'; import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob'; import { TernarySearchTree } from 'vs/base/common/map'; -export function getComparisonKey(resource: URI): string { - return hasToIgnoreCase(resource) ? resource.toString().toLowerCase() : resource.toString(); +export function originalFSPath(uri: URI): string { + let value: string; + const uriPath = uri.path; + if (uri.authority && uriPath.length > 1 && uri.scheme === 'file') { + // unc path: file://shares/c$/far/boo + value = `//${uri.authority}${uriPath}`; + } else if ( + isWindows + && uriPath.charCodeAt(0) === CharCode.Slash + && extpath.isWindowsDriveLetter(uriPath.charCodeAt(1)) + && uriPath.charCodeAt(2) === CharCode.Colon + ) { + value = uriPath.substr(1); + } else { + // other path + value = uriPath; + } + if (isWindows) { + value = value.replace(/\//g, '\\'); + } + return value; +} + +/** + * Creates a key from a resource URI to be used to resource comparison and for resource maps. + * URI queries are included, fragments are ignored. + */ +export function getComparisonKey(resource: URI, caseInsensitivePath = hasToIgnoreCase(resource)): string { + let path = resource.path || '/'; + if (caseInsensitivePath) { + path = path.toLowerCase(); + } + return resource.with({ authority: resource.authority.toLowerCase(), path: path, fragment: null }).toString(); } export function hasToIgnoreCase(resource: URI | undefined): boolean { @@ -29,29 +60,33 @@ export function basenameOrAuthority(resource: URI): string { /** * Tests whether a `candidate` URI is a parent or equal of a given `base` URI. + * URI queries must match, fragments are ignored. * @param base A uri which is "longer" * @param parentCandidate A uri which is "shorter" then `base` */ export function isEqualOrParent(base: URI, parentCandidate: URI, ignoreCase = hasToIgnoreCase(base)): boolean { if (base.scheme === parentCandidate.scheme) { if (base.scheme === Schemas.file) { - return extpath.isEqualOrParent(originalFSPath(base), originalFSPath(parentCandidate), ignoreCase); + return extpath.isEqualOrParent(originalFSPath(base), originalFSPath(parentCandidate), ignoreCase) && base.query === parentCandidate.query; } if (isEqualAuthority(base.authority, parentCandidate.authority)) { - return extpath.isEqualOrParent(base.path, parentCandidate.path, ignoreCase, '/'); + return extpath.isEqualOrParent(base.path || '/', parentCandidate.path || '/', ignoreCase, '/') && base.query === parentCandidate.query; } } return false; } /** - * Tests wheter the two authorities are the same + * Tests whether the two authorities are the same */ export function isEqualAuthority(a1: string, a2: string) { return a1 === a2 || equalsIgnoreCase(a1, a2); } -export function isEqual(first: URI | undefined, second: URI | undefined, ignoreCase = hasToIgnoreCase(first)): boolean { +/** + * Tests whether two resources are the same. URI queries must match, fragments are ignored unless requested. + */ +export function isEqual(first: URI | undefined, second: URI | undefined, caseInsensitivePath = hasToIgnoreCase(first), ignoreFragment = true): boolean { if (first === second) { return true; } @@ -65,7 +100,7 @@ export function isEqual(first: URI | undefined, second: URI | undefined, ignoreC } const p1 = first.path || '/', p2 = second.path || '/'; - return p1 === p2 || ignoreCase && equalsIgnoreCase(p1 || '/', p2 || '/'); + return (p1 === p2 || caseInsensitivePath && equalsIgnoreCase(p1, p2)) && first.query === second.query && (ignoreFragment || first.fragment === second.fragment); } export function basename(resource: URI): string { @@ -86,13 +121,15 @@ export function dirname(resource: URI): URI { if (resource.path.length === 0) { return resource; } + let dirname; if (resource.scheme === Schemas.file) { - return URI.file(paths.dirname(originalFSPath(resource))); - } - let dirname = paths.posix.dirname(resource.path); - if (resource.authority && dirname.length && dirname.charCodeAt(0) !== CharCode.Slash) { - console.error(`dirname("${resource.toString})) resulted in a relative path`); - dirname = '/'; // If a URI contains an authority component, then the path component must either be empty or begin with a CharCode.Slash ("/") character + dirname = URI.file(paths.dirname(originalFSPath(resource))).path; + } else { + dirname = paths.posix.dirname(resource.path); + if (resource.authority && dirname.length && dirname.charCodeAt(0) !== CharCode.Slash) { + console.error(`dirname("${resource.toString})) resulted in a relative path`); + dirname = '/'; // If a URI contains an authority component, then the path component must either be empty or begin with a CharCode.Slash ("/") character + } } return resource.with({ path: dirname @@ -108,7 +145,7 @@ export function dirname(resource: URI): URI { */ export function joinPath(resource: URI, ...pathFragment: string[]): URI { let joinedPath: string; - if (resource.scheme === Schemas.file) { + if (resource.scheme === 'file') { joinedPath = URI.file(paths.join(originalFSPath(resource), ...pathFragment)).path; } else { joinedPath = paths.posix.join(resource.path || '/', ...pathFragment); @@ -139,33 +176,6 @@ export function normalizePath(resource: URI): URI { }); } -/** - * Returns the fsPath of an URI where the drive letter is not normalized. - * See #56403. - */ -export function originalFSPath(uri: URI): string { - let value: string; - const uriPath = uri.path; - if (uri.authority && uriPath.length > 1 && uri.scheme === Schemas.file) { - // unc path: file://shares/c$/far/boo - value = `//${uri.authority}${uriPath}`; - } else if ( - isWindows - && uriPath.charCodeAt(0) === CharCode.Slash - && extpath.isWindowsDriveLetter(uriPath.charCodeAt(1)) - && uriPath.charCodeAt(2) === CharCode.Colon - ) { - value = uriPath.substr(1); - } else { - // other path - value = uriPath; - } - if (isWindows) { - value = value.replace(/\//g, '\\'); - } - return value; -} - /** * Returns true if the URI path is absolute. */ @@ -222,7 +232,7 @@ export function addTrailingPathSeparator(resource: URI, sep: string = paths.sep) * Returns a relative path between two URIs. If the URIs don't have the same schema or authority, `undefined` is returned. * The returned relative path always uses forward slashes. */ -export function relativePath(from: URI, to: URI, ignoreCase = hasToIgnoreCase(from)): string | undefined { +export function relativePath(from: URI, to: URI, caseInsensitivePath = hasToIgnoreCase(from)): string | undefined { if (from.scheme !== to.scheme || !isEqualAuthority(from.authority, to.authority)) { return undefined; } @@ -231,7 +241,7 @@ export function relativePath(from: URI, to: URI, ignoreCase = hasToIgnoreCase(fr return isWindows ? extpath.toSlashes(relativePath) : relativePath; } let fromPath = from.path || '/', toPath = to.path || '/'; - if (ignoreCase) { + if (caseInsensitivePath) { // make casing of fromPath match toPath let i = 0; for (const len = Math.min(fromPath.length, toPath.length); i < len; i++) { diff --git a/src/vs/base/common/scrollable.ts b/src/vs/base/common/scrollable.ts index faee10bed56..6d9b9ca8ddd 100644 --- a/src/vs/base/common/scrollable.ts +++ b/src/vs/base/common/scrollable.ts @@ -33,6 +33,9 @@ export interface ScrollEvent { export class ScrollState implements IScrollDimensions, IScrollPosition { _scrollStateBrand: void; + public readonly rawScrollLeft: number; + public readonly rawScrollTop: number; + public readonly width: number; public readonly scrollWidth: number; public readonly scrollLeft: number; @@ -55,6 +58,9 @@ export class ScrollState implements IScrollDimensions, IScrollPosition { scrollHeight = scrollHeight | 0; scrollTop = scrollTop | 0; + this.rawScrollLeft = scrollLeft; // before validation + this.rawScrollTop = scrollTop; // before validation + if (width < 0) { width = 0; } @@ -85,7 +91,9 @@ export class ScrollState implements IScrollDimensions, IScrollPosition { public equals(other: ScrollState): boolean { return ( - this.width === other.width + this.rawScrollLeft === other.rawScrollLeft + && this.rawScrollTop === other.rawScrollTop + && this.width === other.width && this.scrollWidth === other.scrollWidth && this.scrollLeft === other.scrollLeft && this.height === other.height @@ -98,10 +106,10 @@ export class ScrollState implements IScrollDimensions, IScrollPosition { return new ScrollState( (typeof update.width !== 'undefined' ? update.width : this.width), (typeof update.scrollWidth !== 'undefined' ? update.scrollWidth : this.scrollWidth), - this.scrollLeft, + this.rawScrollLeft, (typeof update.height !== 'undefined' ? update.height : this.height), (typeof update.scrollHeight !== 'undefined' ? update.scrollHeight : this.scrollHeight), - this.scrollTop + this.rawScrollTop ); } @@ -109,10 +117,10 @@ export class ScrollState implements IScrollDimensions, IScrollPosition { return new ScrollState( this.width, this.scrollWidth, - (typeof update.scrollLeft !== 'undefined' ? update.scrollLeft : this.scrollLeft), + (typeof update.scrollLeft !== 'undefined' ? update.scrollLeft : this.rawScrollLeft), this.height, this.scrollHeight, - (typeof update.scrollTop !== 'undefined' ? update.scrollTop : this.scrollTop) + (typeof update.scrollTop !== 'undefined' ? update.scrollTop : this.rawScrollTop) ); } diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index 4857a4896c2..21277fc2e70 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -14,7 +14,7 @@ export function isFalsyOrWhitespace(str: string | undefined): boolean { } /** - * @returns the provided number with the given number of preceding zeros. + * @deprecated ES6: use `String.padStart` */ export function pad(n: number, l: number, char: string = '0'): string { const str = '' + n; @@ -145,7 +145,7 @@ export function stripWildcards(pattern: string): string { } /** - * Determines if haystack starts with needle. + * @deprecated ES6: use `String.startsWith` */ export function startsWith(haystack: string, needle: string): boolean { if (haystack.length < needle.length) { @@ -166,7 +166,7 @@ export function startsWith(haystack: string, needle: string): boolean { } /** - * Determines if haystack ends with needle. + * @deprecated ES6: use `String.endsWith` */ export function endsWith(haystack: string, needle: string): boolean { const diff = haystack.length - needle.length; @@ -428,66 +428,27 @@ export function commonSuffixLength(a: string, b: string): number { return len; } -function substrEquals(a: string, aStart: number, aEnd: number, b: string, bStart: number, bEnd: number): boolean { - while (aStart < aEnd && bStart < bEnd) { - if (a[aStart] !== b[bStart]) { - return false; - } - aStart += 1; - bStart += 1; - } - return true; -} - /** - * Return the overlap between the suffix of `a` and the prefix of `b`. - * For instance `overlap("foobar", "arr, I'm a pirate") === 2`. + * See http://en.wikipedia.org/wiki/Surrogate_pair */ -export function overlap(a: string, b: string): number { - const aEnd = a.length; - let bEnd = b.length; - let aStart = aEnd - bEnd; - - if (aStart === 0) { - return a === b ? aEnd : 0; - } else if (aStart < 0) { - bEnd += aStart; - aStart = 0; - } - - while (aStart < aEnd && bEnd > 0) { - if (substrEquals(a, aStart, aEnd, b, 0, bEnd)) { - return bEnd; - } - bEnd -= 1; - aStart += 1; - } - return 0; -} - -// --- unicode -// http://en.wikipedia.org/wiki/Surrogate_pair -// Returns the code point starting at a specified index in a string -// Code points U+0000 to U+D7FF and U+E000 to U+FFFF are represented on a single character -// Code points U+10000 to U+10FFFF are represented on two consecutive characters -//export function getUnicodePoint(str:string, index:number, len:number):number { -// const chrCode = str.charCodeAt(index); -// if (0xD800 <= chrCode && chrCode <= 0xDBFF && index + 1 < len) { -// const nextChrCode = str.charCodeAt(index + 1); -// if (0xDC00 <= nextChrCode && nextChrCode <= 0xDFFF) { -// return (chrCode - 0xD800) << 10 + (nextChrCode - 0xDC00) + 0x10000; -// } -// } -// return chrCode; -//} export function isHighSurrogate(charCode: number): boolean { return (0xD800 <= charCode && charCode <= 0xDBFF); } +/** + * See http://en.wikipedia.org/wiki/Surrogate_pair + */ export function isLowSurrogate(charCode: number): boolean { return (0xDC00 <= charCode && charCode <= 0xDFFF); } +/** + * See http://en.wikipedia.org/wiki/Surrogate_pair + */ +export function computeCodePoint(highSurrogate: number, lowSurrogate: number): number { + return ((highSurrogate - 0xD800) << 10) + (lowSurrogate - 0xDC00) + 0x10000; +} + /** * get the code point that begins at offset `offset` */ @@ -496,7 +457,7 @@ export function getNextCodePoint(str: string, len: number, offset: number): numb if (isHighSurrogate(charCode) && offset + 1 < len) { const nextCharCode = str.charCodeAt(offset + 1); if (isLowSurrogate(nextCharCode)) { - return ((charCode - 0xD800) << 10) + (nextCharCode - 0xDC00) + 0x10000; + return computeCodePoint(charCode, nextCharCode); } } return charCode; @@ -510,7 +471,7 @@ function getPrevCodePoint(str: string, offset: number): number { if (isLowSurrogate(charCode) && offset > 1) { const prevCharCode = str.charCodeAt(offset - 2); if (isHighSurrogate(prevCharCode)) { - return ((prevCharCode - 0xD800) << 10) + (charCode - 0xDC00) + 0x10000; + return computeCodePoint(prevCharCode, charCode); } } return charCode; @@ -852,21 +813,6 @@ export function removeAnsiEscapeCodes(str: string): string { return str; } -export const removeAccents: (str: string) => string = (function () { - if (typeof (String.prototype as any /* standalone editor compilation */).normalize !== 'function') { - // ☹️ no ES6 features... - return function (str: string) { return str; }; - } else { - // transform into NFD form and remove accents - // see: https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript/37511463#37511463 - const regex = /[\u0300-\u036f]/g; - return function (str: string) { - return (str as any /* standalone editor compilation */).normalize('NFD').replace(regex, ''); - }; - } -})(); - - // -- UTF-8 BOM export const UTF8_BOM_CHARACTER = String.fromCharCode(CharCode.UTF8_BOM); diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index 980c686c9ad..42eea76fe40 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -5,45 +5,25 @@ import { URI, UriComponents } from 'vs/base/common/uri'; -const _typeof = { - number: 'number', - string: 'string', - undefined: 'undefined', - object: 'object', - function: 'function' -}; - /** * @returns whether the provided parameter is a JavaScript Array or not. */ export function isArray(array: any): array is any[] { - if (Array.isArray) { - return Array.isArray(array); - } - - if (array && typeof (array.length) === _typeof.number && array.constructor === Array) { - return true; - } - - return false; + return Array.isArray(array); } /** * @returns whether the provided parameter is a JavaScript String or not. */ export function isString(str: any): str is string { - if (typeof (str) === _typeof.string || str instanceof String) { - return true; - } - - return false; + return (typeof str === 'string'); } /** * @returns whether the provided parameter is a JavaScript Array and each element in the array is a string. */ export function isStringArray(value: any): value is string[] { - return isArray(value) && (value).every(elem => isString(elem)); + return Array.isArray(value) && (value).every(elem => isString(elem)); } /** @@ -55,7 +35,7 @@ export function isObject(obj: any): obj is Object { // The method can't do a type cast since there are type (like strings) which // are subclasses of any put not positvely matched by the function. Hence type // narrowing results in wrong results. - return typeof obj === _typeof.object + return typeof obj === 'object' && obj !== null && !Array.isArray(obj) && !(obj instanceof RegExp) @@ -67,32 +47,28 @@ export function isObject(obj: any): obj is Object { * @returns whether the provided parameter is a JavaScript Number or not. */ export function isNumber(obj: any): obj is number { - if ((typeof (obj) === _typeof.number || obj instanceof Number) && !isNaN(obj)) { - return true; - } - - return false; + return (typeof obj === 'number' && !isNaN(obj)); } /** * @returns whether the provided parameter is a JavaScript Boolean or not. */ export function isBoolean(obj: any): obj is boolean { - return obj === true || obj === false; + return (obj === true || obj === false); } /** * @returns whether the provided parameter is undefined. */ export function isUndefined(obj: any): obj is undefined { - return typeof (obj) === _typeof.undefined; + return (typeof obj === 'undefined'); } /** * @returns whether the provided parameter is undefined or null. */ export function isUndefinedOrNull(obj: any): obj is undefined | null { - return isUndefined(obj) || obj === null; + return (isUndefined(obj) || obj === null); } @@ -158,7 +134,7 @@ export function isEmptyObject(obj: any): obj is any { * @returns whether the provided parameter is a JavaScript Function or not. */ export function isFunction(obj: any): obj is Function { - return typeof obj === _typeof.function; + return (typeof obj === 'function'); } /** diff --git a/src/vs/base/common/uri.ts b/src/vs/base/common/uri.ts index 78c9fda1d71..134d506e009 100644 --- a/src/vs/base/common/uri.ts +++ b/src/vs/base/common/uri.ts @@ -5,6 +5,7 @@ import { isWindows } from 'vs/base/common/platform'; import { CharCode } from 'vs/base/common/charCode'; +import * as paths from 'vs/base/common/path'; const _schemePattern = /^\w[\w\d+.-]*$/; const _singleSlashStart = /^\//; @@ -83,6 +84,7 @@ const _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/; * (http://tools.ietf.org/html/rfc3986#section-3) with minimal validation * and encoding. * + * ```txt * foo://example.com:8042/over/there?name=ferret#nose * \_/ \______________/\_________/ \_________/ \__/ * | | | | | @@ -90,6 +92,7 @@ const _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/; * | _____________________|__ * / \ / \ * urn:example:animal:ferret:nose + * ``` */ export class URI implements UriComponents { @@ -202,7 +205,7 @@ export class URI implements UriComponents { // if (this.scheme !== 'file') { // console.warn(`[UriError] calling fsPath with scheme ${this.scheme}`); // } - return _makeFsPath(this); + return _makeFsPath(this, false); } // ---- modify to new ------------------------- @@ -333,6 +336,26 @@ export class URI implements UriComponents { ); } + /** + * Join a URI path with path fragments and normalizes the resulting path. + * + * @param uri The input URI. + * @param pathFragment The path fragment to add to the URI path. + * @returns The resulting URI. + */ + static joinPath(uri: URI, ...pathFragment: string[]): URI { + if (!uri.path) { + throw new Error(`[UriError]: cannot call joinPaths on URI without path`); + } + let newPath: string; + if (isWindows && uri.scheme === 'file') { + newPath = URI.file(paths.win32.join(_makeFsPath(uri, true), ...pathFragment)).path; + } else { + newPath = paths.posix.join(uri.path, ...pathFragment); + } + return uri.with({ path: newPath }); + } + // ---- printing/externalize --------------------------- /** @@ -397,7 +420,7 @@ class _URI extends URI { get fsPath(): string { if (!this._fsPath) { - this._fsPath = _makeFsPath(this); + this._fsPath = _makeFsPath(this, false); } return this._fsPath; } @@ -553,7 +576,7 @@ function encodeURIComponentMinimal(path: string): string { /** * Compute `fsPath` for the given uri */ -function _makeFsPath(uri: URI): string { +function _makeFsPath(uri: URI, keepDriveLetterCasing: boolean): string { let value: string; if (uri.authority && uri.path.length > 1 && uri.scheme === 'file') { @@ -564,8 +587,12 @@ function _makeFsPath(uri: URI): string { && (uri.path.charCodeAt(1) >= CharCode.A && uri.path.charCodeAt(1) <= CharCode.Z || uri.path.charCodeAt(1) >= CharCode.a && uri.path.charCodeAt(1) <= CharCode.z) && uri.path.charCodeAt(2) === CharCode.Colon ) { - // windows drive letter: file:///c:/far/boo - value = uri.path[1].toLowerCase() + uri.path.substr(2); + if (!keepDriveLetterCasing) { + // windows drive letter: file:///c:/far/boo + value = uri.path[1].toLowerCase() + uri.path.substr(2); + } else { + value = uri.path.substr(1); + } } else { // other path value = uri.path; diff --git a/src/vs/base/common/uuid.ts b/src/vs/base/common/uuid.ts index 3aceb053cdf..57d9db69de1 100644 --- a/src/vs/base/common/uuid.ts +++ b/src/vs/base/common/uuid.ts @@ -3,87 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/** - * Represents a UUID as defined by rfc4122. - */ -export interface UUID { - - /** - * @returns the canonical representation in sets of hexadecimal numbers separated by dashes. - */ - asHex(): string; -} - -class ValueUUID implements UUID { - - constructor(public _value: string) { - // empty - } - - public asHex(): string { - return this._value; - } -} - -class V4UUID extends ValueUUID { - - private static readonly _chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']; - - private static readonly _timeHighBits = ['8', '9', 'a', 'b']; - - private static _oneOf(array: string[]): string { - return array[Math.floor(array.length * Math.random())]; - } - - private static _randomHex(): string { - return V4UUID._oneOf(V4UUID._chars); - } - - constructor() { - super([ - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - '-', - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - '-', - '4', - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - '-', - V4UUID._oneOf(V4UUID._timeHighBits), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - '-', - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - ].join('')); - } -} - -export function v4(): UUID { - return new V4UUID(); -} const _UUIDPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; @@ -91,18 +10,52 @@ export function isUUID(value: string): boolean { return _UUIDPattern.test(value); } -/** - * Parses a UUID that is of the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. - * @param value A uuid string. - */ -export function parse(value: string): UUID { - if (!isUUID(value)) { - throw new Error('invalid uuid'); - } - - return new ValueUUID(value); +// prep-work +const _data = new Uint8Array(16); +const _hex: string[] = []; +for (let i = 0; i < 256; i++) { + _hex.push(i.toString(16).padStart(2, '0')); } +// todo@joh node nodejs use `crypto#randomBytes`, see: https://nodejs.org/docs/latest/api/crypto.html#crypto_crypto_randombytes_size_callback +// todo@joh use browser-crypto +const _fillRandomValues = function (bucket: Uint8Array): Uint8Array { + for (let i = 0; i < bucket.length; i++) { + bucket[i] = Math.floor(Math.random() * 256); + } + return bucket; +}; + export function generateUuid(): string { - return v4().asHex(); + // get data + _fillRandomValues(_data); + + // set version bits + _data[6] = (_data[6] & 0x0f) | 0x40; + _data[8] = (_data[8] & 0x3f) | 0x80; + + // print as string + let i = 0; + let result = ''; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + return result; } diff --git a/src/vs/base/node/pfs.ts b/src/vs/base/node/pfs.ts index 491c760df4b..b26e67c4b76 100644 --- a/src/vs/base/node/pfs.ts +++ b/src/vs/base/node/pfs.ts @@ -9,7 +9,6 @@ import * as fs from 'fs'; import * as os from 'os'; import * as platform from 'vs/base/common/platform'; import { Event } from 'vs/base/common/event'; -import { endsWith } from 'vs/base/common/strings'; import { promisify } from 'util'; import { isRootOrDriveLetter } from 'vs/base/common/extpath'; import { generateUuid } from 'vs/base/common/uuid'; @@ -292,7 +291,7 @@ export function writeFile(path: string, data: string | Buffer | Uint8Array, opti function toQueueKey(path: string): string { let queueKey = path; if (platform.isWindows || platform.isMacintosh) { - queueKey = queueKey.toLowerCase(); // accomodate for case insensitive file systems + queueKey = queueKey.toLowerCase(); // accommodate for case insensitive file systems } return queueKey; @@ -492,7 +491,7 @@ export async function move(source: string, target: string): Promise { // // 2.) The user tries to rename a file/folder that ends with a dot. This is not // really possible to move then, at least on UNC devices. - if (source.toLowerCase() !== target.toLowerCase() && error.code === 'EXDEV' || endsWith(source, '.')) { + if (source.toLowerCase() !== target.toLowerCase() && error.code === 'EXDEV' || source.endsWith('.')) { await copy(source, target); await rimraf(source, RimRafMode.MOVE); await updateMtime(target); diff --git a/src/vs/base/parts/composite/browser/compositeDnd.ts b/src/vs/base/parts/composite/browser/compositeDnd.ts deleted file mode 100644 index 6091c8a10d3..00000000000 --- a/src/vs/base/parts/composite/browser/compositeDnd.ts +++ /dev/null @@ -1,25 +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 { IDragAndDropData } from 'vs/base/browser/dnd'; - -export class CompositeDragAndDropData implements IDragAndDropData { - constructor(private type: 'view' | 'composite', private id: string) { } - update(dataTransfer: DataTransfer): void { - // no-op - } - getData(): { - type: 'view' | 'composite'; - id: string; - } { - return { type: this.type, id: this.id }; - } -} - -export interface ICompositeDragAndDrop { - drop(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent): void; - onDragOver(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent): boolean; - onDragEnter(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent): boolean; -} diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index 8dea0ab03da..8acfc6bce4e 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -526,7 +526,7 @@ export class ChannelClient implements IChannelClient, IDisposable { this.activeRequests.add(disposable); }); - return result.finally(() => this.activeRequests.delete(disposable)); + return result.finally(() => { this.activeRequests.delete(disposable); }); } private requestEvent(channelName: string, name: string, arg?: any): Event { diff --git a/src/vs/base/parts/quickinput/browser/media/arrow-left-dark.svg b/src/vs/base/parts/quickinput/browser/media/arrow-left-dark.svg deleted file mode 100644 index 0d24c218ae1..00000000000 --- a/src/vs/base/parts/quickinput/browser/media/arrow-left-dark.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - arrow-left - Created with Sketch. - - - - - - - \ No newline at end of file diff --git a/src/vs/base/parts/quickinput/browser/media/arrow-left-light.svg b/src/vs/base/parts/quickinput/browser/media/arrow-left-light.svg deleted file mode 100644 index b8362b27458..00000000000 --- a/src/vs/base/parts/quickinput/browser/media/arrow-left-light.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - arrow-left - Created with Sketch. - - - - - - - \ No newline at end of file diff --git a/src/vs/base/parts/quickinput/browser/media/quickInput.css b/src/vs/base/parts/quickinput/browser/media/quickInput.css index 1a5f4d35f6c..23367ed8221 100644 --- a/src/vs/base/parts/quickinput/browser/media/quickInput.css +++ b/src/vs/base/parts/quickinput/browser/media/quickInput.css @@ -55,6 +55,12 @@ margin-bottom: -2px; } +.quick-input-widget.hidden-input .quick-input-header { + /* reduce margins and paddings when input box hidden */ + padding: 0; + margin-bottom: 0; +} + .quick-input-and-message { display: flex; flex-direction: column; @@ -126,6 +132,10 @@ margin-top: 6px; } +.quick-input-widget.hidden-input .quick-input-list { + margin-top: 0; /* reduce margins when input box hidden */ +} + .quick-input-list .monaco-list { overflow: hidden; max-height: calc(20 * 22px); @@ -186,6 +196,11 @@ align-items: center; } +.quick-input-list .quick-input-list-rows > .quick-input-list-row .monaco-icon-label, +.quick-input-list .quick-input-list-rows > .quick-input-list-row .monaco-icon-label .monaco-icon-label-container > .monaco-icon-name-container { + flex: 1; /* make sure the icon label grows within the row */ +} + .quick-input-list .quick-input-list-rows > .quick-input-list-row .codicon { vertical-align: sub; } @@ -194,6 +209,10 @@ opacity: 1; } +.quick-input-list .quick-input-list-entry .quick-input-list-entry-keybinding { + margin-right: 8px; /* separate from the separator label or scrollbar if any */ +} + .quick-input-list .quick-input-list-label-meta { opacity: 0.7; line-height: normal; @@ -205,25 +224,28 @@ font-weight: bold; } -.quick-input-list .quick-input-list-separator { - margin-right: 18px; -} - -.quick-input-list .quick-input-list-entry.has-actions:hover .quick-input-list-separator, -.quick-input-list .monaco-list-row.focused .quick-input-list-entry.has-actions .quick-input-list-separator { - margin-right: 0; +.quick-input-list .quick-input-list-entry .quick-input-list-separator { + margin-right: 8px; /* separate from keybindings or actions */ } .quick-input-list .quick-input-list-entry-action-bar { - display: none; + display: flex; flex: 0; overflow: visible; } +.quick-input-list .quick-input-list-entry-action-bar .action-label { + /* + * By default, actions in the quick input action bar are hidden + * until hovered over them or selected. + */ + display: none; +} + .quick-input-list .quick-input-list-entry-action-bar .action-label.codicon { margin: 0; - width: 19px; height: 100%; + padding: 0 2px; vertical-align: middle; } @@ -231,15 +253,16 @@ margin-top: 1px; } -.quick-input-list .quick-input-list-entry-action-bar ul:first-child .action-label.codicon { - margin-left: 2px; +.quick-input-list .quick-input-list-entry-action-bar { + margin-right: 4px; /* separate from scrollbar */ } -.quick-input-list .quick-input-list-entry-action-bar ul:last-child .action-label.codicon { - margin-right: 8px; +.quick-input-list .quick-input-list-entry-action-bar .action-label.codicon { + margin-right: 4px; /* separate actions */ } -.quick-input-list .quick-input-list-entry:hover .quick-input-list-entry-action-bar, -.quick-input-list .monaco-list-row.focused .quick-input-list-entry-action-bar { +.quick-input-list .quick-input-list-entry .quick-input-list-entry-action-bar .action-label.always-visible, +.quick-input-list .quick-input-list-entry:hover .quick-input-list-entry-action-bar .action-label, +.quick-input-list .monaco-list-row.focused .quick-input-list-entry-action-bar .action-label { display: flex; } diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index d3d94acb6ad..269f96ba390 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/quickInput'; -import { IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInput, IQuickInputButton, IInputBox, IQuickPickItemButtonEvent, QuickPickInput, IQuickPickSeparator, IKeyMods } from 'vs/base/parts/quickinput/common/quickInput'; +import { IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInput, IQuickInputButton, IInputBox, IQuickPickItemButtonEvent, QuickPickInput, IQuickPickSeparator, IKeyMods, IQuickPickAcceptEvent, NO_KEY_MODS, ItemActivation } from 'vs/base/parts/quickinput/common/quickInput'; import * as dom from 'vs/base/browser/dom'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { QuickInputList } from './quickInputList'; +import { QuickInputList, QuickInputListFocus } from './quickInputList'; import { QuickInputBox } from './quickInputBox'; import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -20,11 +20,9 @@ import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import Severity from 'vs/base/common/severity'; import { ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action } from 'vs/base/common/actions'; -import { URI } from 'vs/base/common/uri'; import { equals } from 'vs/base/common/arrays'; import { TimeoutTimer } from 'vs/base/common/async'; import { getIconClass } from 'vs/base/parts/quickinput/browser/quickInputUtils'; -import { registerAndGetAmdImageURL } from 'vs/base/common/amd'; import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; import { List, IListOptions, IListStyles } from 'vs/base/browser/ui/list/listWidget'; import { IInputBoxStyles } from 'vs/base/browser/ui/inputbox/inputBox'; @@ -60,9 +58,9 @@ export interface IQuickInputStyles { export interface IQuickInputWidgetStyles { quickInputBackground?: Color; quickInputForeground?: Color; + quickInputTitleBackground?: Color; contrastBorder?: Color; widgetShadow?: Color; - titleColor: string | undefined; } const $ = dom.$; @@ -70,10 +68,7 @@ const $ = dom.$; type Writeable = { -readonly [P in keyof T]: T[P] }; const backButton = { - iconPath: { - dark: URI.parse(registerAndGetAmdImageURL('vs/base/parts/quickinput/browser/media/arrow-left-dark.svg')), - light: URI.parse(registerAndGetAmdImageURL('vs/base/parts/quickinput/browser/media/arrow-left-light.svg')) - }, + iconClass: 'codicon-arrow-left', tooltip: localize('quickInput.back', "Back"), handle: -1 // TODO }; @@ -125,6 +120,7 @@ type Visibilities = { list?: boolean; ok?: boolean; customButton?: boolean; + progressBar?: boolean; }; class QuickInput extends Disposable implements IQuickInput { @@ -142,6 +138,7 @@ class QuickInput extends Disposable implements IQuickInput { private buttonsUpdated = false; private readonly onDidTriggerButtonEmitter = this._register(new Emitter()); private readonly onDidHideEmitter = this._register(new Emitter()); + private readonly onDisposeEmitter = this._register(new Emitter()); protected readonly visibleDisposables = this._register(new DisposableStore()); @@ -235,7 +232,7 @@ class QuickInput extends Disposable implements IQuickInput { this.update(); } - onDidTriggerButton = this.onDidTriggerButtonEmitter.event; + readonly onDidTriggerButton = this.onDidTriggerButtonEmitter.event; show(): void { if (this.visible) { @@ -266,7 +263,7 @@ class QuickInput extends Disposable implements IQuickInput { this.onDidHideEmitter.fire(); } - onDidHide = this.onDidHideEmitter.event; + readonly onDidHide = this.onDidHideEmitter.event; protected update() { if (!this.visible) { @@ -298,9 +295,8 @@ class QuickInput extends Disposable implements IQuickInput { this.ui.leftActionBar.clear(); const leftButtons = this.buttons.filter(button => button === backButton); this.ui.leftActionBar.push(leftButtons.map((button, index) => { - const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, () => { + const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, async () => { this.onDidTriggerButtonEmitter.fire(button); - return Promise.resolve(null); }); action.tooltip = button.tooltip || ''; return action; @@ -308,9 +304,8 @@ class QuickInput extends Disposable implements IQuickInput { this.ui.rightActionBar.clear(); const rightButtons = this.buttons.filter(button => button !== backButton); this.ui.rightActionBar.push(rightButtons.map((button, index) => { - const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, () => { + const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, async () => { this.onDidTriggerButtonEmitter.fire(button); - return Promise.resolve(null); }); action.tooltip = button.tooltip || ''; return action; @@ -362,29 +357,36 @@ class QuickInput extends Disposable implements IQuickInput { } } - public dispose(): void { + readonly onDispose = this.onDisposeEmitter.event; + + dispose(): void { this.hide(); + this.onDisposeEmitter.fire(); + super.dispose(); } } class QuickPick extends QuickInput implements IQuickPick { - private static readonly INPUT_BOX_ARIA_LABEL = localize('quickInputBox.ariaLabel', "Type to narrow down results."); + private static readonly DEFAULT_ARIA_LABEL = localize('quickInputBox.ariaLabel', "Type to narrow down results."); private _value = ''; + private _ariaLabel = QuickPick.DEFAULT_ARIA_LABEL; private _placeholder: string | undefined; private readonly onDidChangeValueEmitter = this._register(new Emitter()); - private readonly onDidAcceptEmitter = this._register(new Emitter()); + private readonly onDidAcceptEmitter = this._register(new Emitter()); private readonly onDidCustomEmitter = this._register(new Emitter()); private _items: Array = []; private itemsUpdated = false; private _canSelectMany = false; + private _canAcceptInBackground = false; private _matchOnDescription = false; private _matchOnDetail = false; private _matchOnLabel = true; private _sortByLabel = true; private _autoFocusOnList = true; + private _itemActivation = this.ui.isScreenReaderOptimized() ? ItemActivation.NONE /* https://github.com/microsoft/vscode/issues/57501 */ : ItemActivation.FIRST; private _activeItems: T[] = []; private activeItemsUpdated = false; private activeItemsToConfirm: T[] | null = []; @@ -401,9 +403,17 @@ class QuickPick extends QuickInput implements IQuickPi private _customButton = false; private _customButtonLabel: string | undefined; private _customButtonHover: string | undefined; + private _quickNavigate: IQuickNavigateConfiguration | undefined; + private _hideInput: boolean | undefined; - quickNavigate: IQuickNavigateConfiguration | undefined; + get quickNavigate() { + return this._quickNavigate; + } + set quickNavigate(quickNavigate: IQuickNavigateConfiguration | undefined) { + this._quickNavigate = quickNavigate; + this.update(); + } get value() { return this._value; @@ -414,6 +424,17 @@ class QuickPick extends QuickInput implements IQuickPi this.update(); } + filterValue = (value: string) => value; + + set ariaLabel(ariaLabel: string) { + this._ariaLabel = ariaLabel || QuickPick.DEFAULT_ARIA_LABEL; + this.update(); + } + + get ariaLabel() { + return this._ariaLabel; + } + get placeholder() { return this._placeholder; } @@ -448,6 +469,14 @@ class QuickPick extends QuickInput implements IQuickPi this.update(); } + get canAcceptInBackground() { + return this._canAcceptInBackground; + } + + set canAcceptInBackground(canAcceptInBackground: boolean) { + this._canAcceptInBackground = canAcceptInBackground; + } + get matchOnDescription() { return this._matchOnDescription; } @@ -484,7 +513,6 @@ class QuickPick extends QuickInput implements IQuickPi this.update(); } - get autoFocusOnList() { return this._autoFocusOnList; } @@ -494,6 +522,14 @@ class QuickPick extends QuickInput implements IQuickPi this.update(); } + get itemActivation() { + return this._itemActivation; + } + + set itemActivation(itemActivation: ItemActivation) { + this._itemActivation = itemActivation; + } + get activeItems() { return this._activeItems; } @@ -517,6 +553,13 @@ class QuickPick extends QuickInput implements IQuickPi } get keyMods() { + if (this._quickNavigate) { + // Disable keyMods when quick navigate is enabled + // because in this model the interaction is purely + // keyboard driven and Ctrl/Alt are typically + // pressed and hold during this interaction. + return NO_KEY_MODS; + } return this.ui.keyMods; } @@ -571,22 +614,31 @@ class QuickPick extends QuickInput implements IQuickPi this.update(); } - public inputHasFocus(): boolean { + inputHasFocus(): boolean { return this.visible ? this.ui.inputBox.hasFocus() : false; } - public focusOnInput() { + focusOnInput() { this.ui.inputBox.setFocus(); } + get hideInput() { + return !!this._hideInput; + } + + set hideInput(hideInput: boolean) { + this._hideInput = hideInput; + this.update(); + } + onDidChangeSelection = this.onDidChangeSelectionEmitter.event; onDidTriggerItemButton = this.onDidTriggerItemButtonEmitter.event; private trySelectFirst() { if (this.autoFocusOnList) { - if (!this.ui.isScreenReaderOptimized() && !this.canSelectMany) { - this.ui.list.focus('First'); + if (!this.canSelectMany) { + this.ui.list.focus(QuickInputListFocus.First); } } } @@ -599,8 +651,10 @@ class QuickPick extends QuickInput implements IQuickPi return; } this._value = value; - this.ui.list.filter(this.ui.inputBox.value); - this.trySelectFirst(); + const didFilter = this.ui.list.filter(this.filterValue(this.ui.inputBox.value)); + if (didFilter) { + this.trySelectFirst(); + } this.onDidChangeValueEmitter.fire(value); })); this.visibleDisposables.add(this.ui.inputBox.onMouseDown(event => { @@ -611,7 +665,7 @@ class QuickPick extends QuickInput implements IQuickPi this.visibleDisposables.add(this.ui.inputBox.onKeyDown(event => { switch (event.keyCode) { case KeyCode.DownArrow: - this.ui.list.focus('Next'); + this.ui.list.focus(QuickInputListFocus.Next); if (this.canSelectMany) { this.ui.list.domFocus(); } @@ -619,9 +673,9 @@ class QuickPick extends QuickInput implements IQuickPi break; case KeyCode.UpArrow: if (this.ui.list.getFocusedElements().length) { - this.ui.list.focus('Previous'); + this.ui.list.focus(QuickInputListFocus.Previous); } else { - this.ui.list.focus('Last'); + this.ui.list.focus(QuickInputListFocus.Last); } if (this.canSelectMany) { this.ui.list.domFocus(); @@ -629,26 +683,34 @@ class QuickPick extends QuickInput implements IQuickPi event.preventDefault(); break; case KeyCode.PageDown: - if (this.ui.list.getFocusedElements().length) { - this.ui.list.focus('NextPage'); - } else { - this.ui.list.focus('First'); - } + this.ui.list.focus(QuickInputListFocus.NextPage); if (this.canSelectMany) { this.ui.list.domFocus(); } event.preventDefault(); break; case KeyCode.PageUp: - if (this.ui.list.getFocusedElements().length) { - this.ui.list.focus('PreviousPage'); - } else { - this.ui.list.focus('Last'); - } + this.ui.list.focus(QuickInputListFocus.PreviousPage); if (this.canSelectMany) { this.ui.list.domFocus(); } event.preventDefault(); + break; + case KeyCode.RightArrow: + if (!this._canAcceptInBackground) { + return; // needs to be enabled + } + + if (!this.ui.inputBox.isSelectionAtEnd()) { + return; // ensure input box selection at end + } + + if (this.activeItems[0]) { + this._selectedItems = [this.activeItems[0]]; + this.onDidChangeSelectionEmitter.fire(this.selectedItems); + this.onDidAcceptEmitter.fire({ inBackground: true }); + } + break; } })); @@ -657,10 +719,10 @@ class QuickPick extends QuickInput implements IQuickPi this._selectedItems = [this.activeItems[0]]; this.onDidChangeSelectionEmitter.fire(this.selectedItems); } - this.onDidAcceptEmitter.fire(undefined); + this.onDidAcceptEmitter.fire({ inBackground: false }); })); this.visibleDisposables.add(this.ui.onDidCustom(() => { - this.onDidCustomEmitter.fire(undefined); + this.onDidCustomEmitter.fire(); })); this.visibleDisposables.add(this.ui.list.onDidChangeFocus(focusedItems => { if (this.activeItemsUpdated) { @@ -672,7 +734,7 @@ class QuickPick extends QuickInput implements IQuickPi this._activeItems = focusedItems as T[]; this.onDidChangeActiveEmitter.fire(focusedItems as T[]); })); - this.visibleDisposables.add(this.ui.list.onDidChangeSelection(selectedItems => { + this.visibleDisposables.add(this.ui.list.onDidChangeSelection(({ items: selectedItems, event }) => { if (this.canSelectMany) { if (selectedItems.length) { this.ui.list.setSelectedElements([]); @@ -685,7 +747,7 @@ class QuickPick extends QuickInput implements IQuickPi this._selectedItems = selectedItems as T[]; this.onDidChangeSelectionEmitter.fire(selectedItems as T[]); if (selectedItems.length) { - this.onDidAcceptEmitter.fire(undefined); + this.onDidAcceptEmitter.fire({ inBackground: event instanceof MouseEvent && event.button === 1 /* mouse middle click */ }); } })); this.visibleDisposables.add(this.ui.list.onChangedCheckedElements(checkedItems => { @@ -707,7 +769,7 @@ class QuickPick extends QuickInput implements IQuickPi private registerQuickNavigation() { return dom.addDisposableListener(this.ui.container, dom.EventType.KEY_UP, e => { - if (this.canSelectMany || !this.quickNavigate) { + if (this.canSelectMany || !this._quickNavigate) { return; } @@ -715,7 +777,7 @@ class QuickPick extends QuickInput implements IQuickPi const keyCode = keyboardEvent.keyCode; // Select element when keys are pressed that signal it - const quickNavKeys = this.quickNavigate.keybindings; + const quickNavKeys = this._quickNavigate.keybindings; const wasTriggerKeyPressed = quickNavKeys.some(k => { const [firstPart, chordPart] = k.getParts(); if (chordPart) { @@ -724,7 +786,7 @@ class QuickPick extends QuickInput implements IQuickPi if (firstPart.shiftKey && keyCode === KeyCode.Shift) { if (keyboardEvent.ctrlKey || keyboardEvent.altKey || keyboardEvent.metaKey) { - return false; // this is an optimistic check for the shift key being used to navigate back in quick open + return false; // this is an optimistic check for the shift key being used to navigate back in quick input } return true; @@ -745,10 +807,16 @@ class QuickPick extends QuickInput implements IQuickPi return false; }); - if (wasTriggerKeyPressed && this.activeItems[0]) { - this._selectedItems = [this.activeItems[0]]; - this.onDidChangeSelectionEmitter.fire(this.selectedItems); - this.onDidAcceptEmitter.fire(undefined); + if (wasTriggerKeyPressed) { + if (this.activeItems[0]) { + this._selectedItems = [this.activeItems[0]]; + this.onDidChangeSelectionEmitter.fire(this.selectedItems); + this.onDidAcceptEmitter.fire({ inBackground: false }); + } + // Unset quick navigate after press. It is only valid once + // and should not result in any behaviour change afterwards + // if the picker remains open because there was no active item + this._quickNavigate = undefined; } }); } @@ -757,8 +825,22 @@ class QuickPick extends QuickInput implements IQuickPi if (!this.visible) { return; } - const ok = this.ok === 'default' ? this.canSelectMany : this.ok; - this.ui.setVisibilities(this.canSelectMany ? { title: !!this.title || !!this.step, description: !!this.description, checkAll: true, inputBox: true, visibleCount: true, count: true, ok, list: true, message: !!this.validationMessage, customButton: this.customButton } : { title: !!this.title || !!this.step, description: !!this.description, inputBox: true, visibleCount: true, list: true, message: !!this.validationMessage, customButton: this.customButton, ok }); + const hideInput = !!this._hideInput && this._items.length > 0; // do not allow to hide input without items + dom.toggleClass(this.ui.container, 'hidden-input', hideInput); + const visibilities: Visibilities = { + title: !!this.title || !!this.step, + description: !!this.description, + checkAll: this.canSelectMany, + inputBox: !hideInput, + progressBar: !hideInput, + visibleCount: true, + count: this.canSelectMany, + ok: this.ok === 'default' ? this.canSelectMany : this.ok, + list: true, + message: !!this.validationMessage, + customButton: this.customButton + }; + this.ui.setVisibilities(visibilities); super.update(); if (this.ui.inputBox.value !== this.value) { this.ui.inputBox.value = this.value; @@ -770,14 +852,36 @@ class QuickPick extends QuickInput implements IQuickPi if (this.ui.inputBox.placeholder !== (this.placeholder || '')) { this.ui.inputBox.placeholder = (this.placeholder || ''); } + if (this.ui.inputBox.ariaLabel !== this.ariaLabel) { + this.ui.inputBox.ariaLabel = this.ariaLabel; + } + this.ui.list.matchOnDescription = this.matchOnDescription; + this.ui.list.matchOnDetail = this.matchOnDetail; + this.ui.list.matchOnLabel = this.matchOnLabel; + this.ui.list.sortByLabel = this.sortByLabel; if (this.itemsUpdated) { this.itemsUpdated = false; this.ui.list.setElements(this.items); - this.ui.list.filter(this.ui.inputBox.value); + this.ui.list.filter(this.filterValue(this.ui.inputBox.value)); this.ui.checkAll.checked = this.ui.list.getAllVisibleChecked(); this.ui.visibleCount.setCount(this.ui.list.getVisibleCount()); this.ui.count.setCount(this.ui.list.getCheckedCount()); - this.trySelectFirst(); + switch (this._itemActivation) { + case ItemActivation.NONE: + this._itemActivation = ItemActivation.FIRST; // only valid once, then unset + break; + case ItemActivation.SECOND: + this.ui.list.focus(QuickInputListFocus.Second); + this._itemActivation = ItemActivation.FIRST; // only valid once, then unset + break; + case ItemActivation.LAST: + this.ui.list.focus(QuickInputListFocus.Last); + this._itemActivation = ItemActivation.FIRST; // only valid once, then unset + break; + default: + this.trySelectFirst(); + break; + } } if (this.ui.container.classList.contains('show-checkboxes') !== !!this.canSelectMany) { if (this.canSelectMany) { @@ -815,12 +919,12 @@ class QuickPick extends QuickInput implements IQuickPi } this.ui.customButton.label = this.customLabel || ''; this.ui.customButton.element.title = this.customHover || ''; - this.ui.list.matchOnDescription = this.matchOnDescription; - this.ui.list.matchOnDetail = this.matchOnDetail; - this.ui.list.matchOnLabel = this.matchOnLabel; - this.ui.list.sortByLabel = this.sortByLabel; this.ui.setComboboxAccessibility(true); - this.ui.inputBox.setAttribute('aria-label', QuickPick.INPUT_BOX_ARIA_LABEL); + if (!visibilities.inputBox) { + // we need to move focus into the tree to detect keybindings + // properly when the input box is not visible (quick nav) + this.ui.list.domFocus(); + } } } @@ -907,7 +1011,7 @@ class InputBox extends QuickInput implements IInputBox { this._value = value; this.onDidValueChangeEmitter.fire(value); })); - this.visibleDisposables.add(this.ui.onDidAccept(() => this.onDidAcceptEmitter.fire(undefined))); + this.visibleDisposables.add(this.ui.onDidAccept(() => this.onDidAcceptEmitter.fire())); this.valueSelectionUpdated = true; } super.show(); @@ -944,11 +1048,11 @@ class InputBox extends QuickInput implements IInputBox { } export class QuickInputController extends Disposable { - private static readonly MAX_WIDTH = 600; // Max total width of quick open widget + private static readonly MAX_WIDTH = 600; // Max total width of quick input widget private idPrefix: string; private ui: QuickInputUI | undefined; - private dimension?: dom.Dimension; + private dimension?: dom.IDimension; private titleBarOffset?: number; private comboboxAccessibility = false; private enabled = true; @@ -961,10 +1065,12 @@ export class QuickInputController extends Disposable { private parentElement: HTMLElement; private styles: IQuickInputStyles; + private onShowEmitter = new Emitter(); + readonly onShow = this.onShowEmitter.event; + private onHideEmitter = new Emitter(); - public onShow = this.onShowEmitter.event; - public onHide = this.onHideEmitter.event; + readonly onHide = this.onHideEmitter.event; constructor(private options: IQuickInputOptions) { super(); @@ -975,30 +1081,13 @@ export class QuickInputController extends Disposable { } private registerKeyModsListeners() { - this._register(dom.addDisposableListener(this.parentElement, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { - const event = new StandardKeyboardEvent(e); - switch (event.keyCode) { - case KeyCode.Ctrl: - case KeyCode.Meta: - this.keyMods.ctrlCmd = true; - break; - case KeyCode.Alt: - this.keyMods.alt = true; - break; - } - })); - this._register(dom.addDisposableListener(this.parentElement, dom.EventType.KEY_UP, (e: KeyboardEvent) => { - const event = new StandardKeyboardEvent(e); - switch (event.keyCode) { - case KeyCode.Ctrl: - case KeyCode.Meta: - this.keyMods.ctrlCmd = false; - break; - case KeyCode.Alt: - this.keyMods.alt = false; - break; - } - })); + const listener = (e: KeyboardEvent | MouseEvent) => { + this.keyMods.ctrlCmd = e.ctrlKey || e.metaKey; + this.keyMods.alt = e.altKey; + }; + this._register(dom.addDisposableListener(window, dom.EventType.KEY_DOWN, listener, true)); + this._register(dom.addDisposableListener(window, dom.EventType.KEY_UP, listener, true)); + this._register(dom.addDisposableListener(window, dom.EventType.MOUSE_DOWN, listener, true)); } private getUI() { @@ -1251,6 +1340,9 @@ export class QuickInputController extends Disposable { ]; input.canSelectMany = !!options.canPickMany; input.placeholder = options.placeHolder; + if (options.placeHolder) { + input.ariaLabel = options.placeHolder; + } input.ignoreFocusOut = !!options.ignoreFocusLost; input.matchOnDescription = !!options.matchOnDescription; input.matchOnDetail = !!options.matchOnDetail; @@ -1378,7 +1470,7 @@ export class QuickInputController extends Disposable { ui.list.sortByLabel = true; ui.ignoreFocusOut = false; this.setComboboxAccessibility(false); - ui.inputBox.removeAttribute('aria-label'); + ui.inputBox.ariaLabel = ''; const backKeybindingLabel = this.options.backKeybindingLabel(); backButton.tooltip = backKeybindingLabel ? localize('quickInput.backWithKeybinding', "Back ({0})", backKeybindingLabel) : localize('quickInput.back', "Back"); @@ -1399,6 +1491,7 @@ export class QuickInputController extends Disposable { ui.okContainer.style.display = visibilities.ok ? '' : 'none'; ui.customButtonContainer.style.display = visibilities.customButton ? '' : 'none'; ui.message.style.display = visibilities.message ? '' : 'none'; + ui.progressBar.getContainer().style.display = visibilities.progressBar ? '' : 'none'; ui.list.display(!!visibilities.list); ui.container.classList[visibilities.checkAll ? 'add' : 'remove']('show-checkboxes'); this.updateLayout(); // TODO @@ -1438,7 +1531,7 @@ export class QuickInputController extends Disposable { } } - public hide(focusLost?: boolean) { + hide(focusLost?: boolean) { const controller = this.controller; if (controller) { this.controller = null; @@ -1465,29 +1558,33 @@ export class QuickInputController extends Disposable { navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration) { if (this.isDisplayed() && this.getUI().list.isDisplayed()) { - this.getUI().list.focus(next ? 'Next' : 'Previous'); + this.getUI().list.focus(next ? QuickInputListFocus.Next : QuickInputListFocus.Previous); if (quickNavigate && this.controller instanceof QuickPick) { this.controller.quickNavigate = quickNavigate; } } } - accept() { + async accept(keyMods: IKeyMods = { alt: false, ctrlCmd: false }) { + // When accepting the item programmatically, it is important that + // we update `keyMods` either from the provided set or unset it + // because the accept did not happen from mouse or keyboard + // interaction on the list itself + this.keyMods.alt = keyMods.alt; + this.keyMods.ctrlCmd = keyMods.ctrlCmd; + this.onDidAcceptEmitter.fire(); - return Promise.resolve(undefined); } - back() { + async back() { this.onDidTriggerButtonEmitter.fire(this.backButton); - return Promise.resolve(undefined); } - cancel() { + async cancel() { this.hide(); - return Promise.resolve(undefined); } - layout(dimension: dom.Dimension, titleBarOffset: number): void { + layout(dimension: dom.IDimension, titleBarOffset: number): void { this.dimension = dimension; this.titleBarOffset = titleBarOffset; this.updateLayout(); @@ -1507,7 +1604,7 @@ export class QuickInputController extends Disposable { } } - public applyStyles(styles: IQuickInputStyles) { + applyStyles(styles: IQuickInputStyles) { this.styles = styles; this.updateStyles(); } @@ -1515,13 +1612,13 @@ export class QuickInputController extends Disposable { private updateStyles() { if (this.ui) { const { - titleColor, + quickInputTitleBackground, quickInputBackground, quickInputForeground, contrastBorder, widgetShadow, } = this.styles.widget; - this.ui.titleBar.style.backgroundColor = titleColor || ''; + this.ui.titleBar.style.backgroundColor = quickInputTitleBackground ? quickInputTitleBackground.toString() : ''; this.ui.container.style.backgroundColor = quickInputBackground ? quickInputBackground.toString() : ''; this.ui.container.style.color = quickInputForeground ? quickInputForeground.toString() : ''; this.ui.container.style.border = contrastBorder ? `1px solid ${contrastBorder}` : ''; diff --git a/src/vs/base/parts/quickinput/browser/quickInputBox.ts b/src/vs/base/parts/quickinput/browser/quickInputBox.ts index 31c4f7cb160..2e796764a6e 100644 --- a/src/vs/base/parts/quickinput/browser/quickInputBox.ts +++ b/src/vs/base/parts/quickinput/browser/quickInputBox.ts @@ -54,7 +54,11 @@ export class QuickInputBox extends Disposable { this.inputBox.select(range); } - setPlaceholder(placeholder: string) { + isSelectionAtEnd(): boolean { + return this.inputBox.isSelectionAtEnd(); + } + + setPlaceholder(placeholder: string): void { this.inputBox.setPlaceHolder(placeholder); } @@ -66,6 +70,14 @@ export class QuickInputBox extends Disposable { this.inputBox.setPlaceHolder(placeholder); } + get ariaLabel() { + return this.inputBox.getAriaLabel(); + } + + set ariaLabel(ariaLabel: string) { + this.inputBox.setAriaLabel(ariaLabel); + } + get password() { return this.inputBox.inputElement.type === 'password'; } @@ -82,11 +94,11 @@ export class QuickInputBox extends Disposable { return this.inputBox.hasFocus(); } - setAttribute(name: string, value: string) { + setAttribute(name: string, value: string): void { this.inputBox.inputElement.setAttribute(name, value); } - removeAttribute(name: string) { + removeAttribute(name: string): void { this.inputBox.inputElement.removeAttribute(name); } @@ -110,7 +122,7 @@ export class QuickInputBox extends Disposable { this.inputBox.layout(); } - style(styles: IInputBoxStyles) { + style(styles: IInputBoxStyles): void { this.inputBox.style(styles); } } diff --git a/src/vs/base/parts/quickinput/browser/quickInputList.ts b/src/vs/base/parts/quickinput/browser/quickInputList.ts index e824a69d127..bf83c206372 100644 --- a/src/vs/base/parts/quickinput/browser/quickInputList.ts +++ b/src/vs/base/parts/quickinput/browser/quickInputList.ts @@ -25,7 +25,8 @@ import { Action } from 'vs/base/common/actions'; import { getIconClass } from 'vs/base/parts/quickinput/browser/quickInputUtils'; import { withNullAsUndefined } from 'vs/base/common/types'; import { IQuickInputOptions } from 'vs/base/parts/quickinput/browser/quickInput'; -import { IListOptions, List, IListStyles } from 'vs/base/browser/ui/list/listWidget'; +import { IListOptions, List, IListStyles, IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; const $ = dom.$; @@ -33,8 +34,12 @@ interface IListElement { readonly index: number; readonly item: IQuickPickItem; readonly saneLabel: string; + readonly saneAriaLabel: string; readonly saneDescription?: string; readonly saneDetail?: string; + readonly labelHighlights?: IMatch[]; + readonly descriptionHighlights?: IMatch[]; + readonly detailHighlights?: IMatch[]; readonly checked: boolean; readonly separator?: IQuickPickSeparator; readonly fireButtonTriggered: (event: IQuickPickItemButtonEvent) => void; @@ -44,6 +49,7 @@ class ListElement implements IListElement { index!: number; item!: IQuickPickItem; saneLabel!: string; + saneAriaLabel!: string; saneDescription?: string; saneDetail?: string; hidden = false; @@ -74,6 +80,7 @@ interface IListElementTemplateData { entry: HTMLDivElement; checkbox: HTMLInputElement; label: IconLabel; + keybinding: KeybindingLabel; detail: HighlightedLabel; separator: HTMLDivElement; actionBar: ActionBar; @@ -99,6 +106,11 @@ class ListElementRenderer implements IListRenderer { + if (!data.checkbox.offsetParent) { // If checkbox not visible: + e.preventDefault(); // Prevent toggle of checkbox when it is immediately shown afterwards. #91740 + } + })); data.checkbox = dom.append(label, $('input.quick-input-list-checkbox')); data.checkbox.type = 'checkbox'; data.toDisposeTemplate.push(dom.addStandardDisposableListener(data.checkbox, dom.EventType.CHANGE, e => { @@ -113,6 +125,10 @@ class ListElementRenderer implements IListRenderer s && parseCodicons(s).text) - .filter(s => !!s) - .join(', ')); - // Separator if (element.separator && element.separator.label) { data.separator.textContent = element.separator.label; @@ -171,7 +186,11 @@ class ListElementRenderer implements IListRenderer { - const action = new Action(`id-${index}`, '', button.iconClass || (button.iconPath ? getIconClass(button.iconPath) : undefined), true, () => { + let cssClasses = button.iconClass || (button.iconPath ? getIconClass(button.iconPath) : undefined); + if (button.alwaysVisible) { + cssClasses = cssClasses ? `${cssClasses} always-visible` : 'always-visible'; + } + const action = new Action(`id-${index}`, '', cssClasses, true, () => { element.fireButtonTriggered({ button, item: element.item @@ -208,6 +227,16 @@ class ListElementDelegate implements IListVirtualDelegate { } } +export enum QuickInputListFocus { + First = 1, + Second, + Last, + Next, + Previous, + NextPage, + PreviousPage +} + export class QuickInputList { readonly id: string; @@ -244,12 +273,20 @@ export class QuickInputList { this.id = id; this.container = dom.append(this.parent, $('.quick-input-list')); const delegate = new ListElementDelegate(); + const accessibilityProvider = new QuickInputAccessibilityProvider(); this.list = options.createList('QuickInput', this.container, delegate, [new ListElementRenderer()], { identityProvider: { getId: element => element.saneLabel }, openController: { shouldOpen: () => false }, // Workaround #58124 setRowLineHeight: false, multipleSelectionSupport: false, horizontalScrolling: false, + accessibilityProvider, + ariaProvider: { + getRole: () => 'option', + getSetSize: (_: ListElement, _index: number, listLength: number) => listLength, + getPosInSet: (_: ListElement, index: number) => index + }, + ariaRole: 'listbox' } as IListOptions); this.list.getHTMLElement().id = id; this.disposables.push(this.list); @@ -265,14 +302,12 @@ export class QuickInputList { } break; case KeyCode.UpArrow: - case KeyCode.PageUp: const focus1 = this.list.getFocus(); if (focus1.length === 1 && focus1[0] === 0) { this._onLeave.fire(); } break; case KeyCode.DownArrow: - case KeyCode.PageDown: const focus2 = this.list.getFocus(); if (focus2.length === 1 && focus2[0] === this.list.length - 1) { this._onLeave.fire(); @@ -291,16 +326,31 @@ export class QuickInputList { this._onLeave.fire(); } })); + this.disposables.push(this.list.onMouseMiddleClick(e => { + this._onLeave.fire(); + })); + this.disposables.push(this.list.onContextMenu(e => { + if (typeof e.index === 'number') { + e.browserEvent.preventDefault(); + + // we want to treat a context menu event as + // a gesture to open the item at the index + // since we do not have any context menu + // this enables for example macOS to Ctrl- + // click on an item to open it. + this.list.setSelection([e.index]); + } + })); } @memoize get onDidChangeFocus() { - return Event.map(this.list.onFocusChange, e => e.elements.map(e => e.item)); + return Event.map(this.list.onDidChangeFocus, e => e.elements.map(e => e.item)); } @memoize get onDidChangeSelection() { - return Event.map(this.list.onSelectionChange, e => e.elements.map(e => e.item)); + return Event.map(this.list.onDidChangeSelection, e => ({ items: e.elements.map(e => e.item), event: e.browserEvent })); } getAllVisibleChecked() { @@ -364,12 +414,24 @@ export class QuickInputList { this.elements = inputElements.reduce((result, item, index) => { if (item.type !== 'separator') { const previous = index && inputElements[index - 1]; + const saneLabel = item.label && item.label.replace(/\r?\n/g, ' '); + const saneDescription = item.description && item.description.replace(/\r?\n/g, ' '); + const saneDetail = item.detail && item.detail.replace(/\r?\n/g, ' '); + const saneAriaLabel = item.ariaLabel || [saneLabel, saneDescription, saneDetail] + .map(s => s && parseCodicons(s).text) + .filter(s => !!s) + .join(', '); + result.push(new ListElement({ index, item, - saneLabel: item.label && item.label.replace(/\r?\n/g, ' '), - saneDescription: item.description && item.description.replace(/\r?\n/g, ' '), - saneDetail: item.detail && item.detail.replace(/\r?\n/g, ' '), + saneLabel, + saneAriaLabel, + saneDescription, + saneDetail, + labelHighlights: item.highlights?.label, + descriptionHighlights: item.highlights?.description, + detailHighlights: item.highlights?.detail, checked: false, separator: previous && previous.type === 'separator' ? previous : undefined, fireButtonTriggered @@ -388,6 +450,10 @@ export class QuickInputList { this._onChangedVisibleCount.fire(this.elements.length); } + getElementsCount(): number { + return this.inputElements.length; + } + getFocusedElements() { return this.list.getFocusedElements() .map(e => e.item); @@ -398,7 +464,10 @@ export class QuickInputList { .filter(item => this.elementsToIndexes.has(item)) .map(item => this.elementsToIndexes.get(item)!)); if (items.length > 0) { - this.list.reveal(this.list.getFocus()[0]); + const focused = this.list.getFocus()[0]; + if (typeof focused === 'number') { + this.list.reveal(focused); + } } } @@ -439,22 +508,54 @@ export class QuickInputList { } set enabled(value: boolean) { - this.list.getHTMLElement().style.pointerEvents = value ? null : 'none'; + this.list.getHTMLElement().style.pointerEvents = value ? '' : 'none'; } - focus(what: 'First' | 'Last' | 'Next' | 'Previous' | 'NextPage' | 'PreviousPage'): void { + focus(what: QuickInputListFocus): void { if (!this.list.length) { return; } - if ((what === 'Next' || what === 'NextPage') && this.list.getFocus()[0] === this.list.length - 1) { - what = 'First'; + if (what === QuickInputListFocus.Next && this.list.getFocus()[0] === this.list.length - 1) { + what = QuickInputListFocus.First; } - if ((what === 'Previous' || what === 'PreviousPage') && this.list.getFocus()[0] === 0) { - what = 'Last'; + + if (what === QuickInputListFocus.Previous && this.list.getFocus()[0] === 0) { + what = QuickInputListFocus.Last; + } + + if (what === QuickInputListFocus.Second && this.list.length < 2) { + what = QuickInputListFocus.First; + } + + switch (what) { + case QuickInputListFocus.First: + this.list.focusFirst(); + break; + case QuickInputListFocus.Second: + this.list.focusNth(1); + break; + case QuickInputListFocus.Last: + this.list.focusLast(); + break; + case QuickInputListFocus.Next: + this.list.focusNext(); + break; + case QuickInputListFocus.Previous: + this.list.focusPrevious(); + break; + case QuickInputListFocus.NextPage: + this.list.focusNextPage(); + break; + case QuickInputListFocus.PreviousPage: + this.list.focusPreviousPage(); + break; + } + + const focused = this.list.getFocus()[0]; + if (typeof focused === 'number') { + this.list.reveal(focused); } - this.list['focus' + what as 'focusFirst' | 'focusLast' | 'focusNext' | 'focusPrevious' | 'focusNextPage' | 'focusPreviousPage'](); - this.list.reveal(this.list.getFocus()[0]); } clearFocus() { @@ -470,9 +571,10 @@ export class QuickInputList { this.list.layout(); } - filter(query: string) { + filter(query: string): boolean { if (!(this.sortByLabel || this.matchOnLabel || this.matchOnDescription || this.matchOnDetail)) { - return; + this.list.layout(); + return false; } query = query.trim(); @@ -530,6 +632,8 @@ export class QuickInputList { this._onChangedAllVisibleChecked.fire(this.getAllVisibleChecked()); this._onChangedVisibleCount.fire(shownElements.length); + + return true; } toggleCheckbox() { @@ -590,3 +694,9 @@ function compareEntries(elementA: ListElement, elementB: ListElement, lookFor: s return compareAnything(elementA.saneLabel, elementB.saneLabel, lookFor); } + +class QuickInputAccessibilityProvider implements IAccessibilityProvider { + getAriaLabel(element: ListElement): string | null { + return element.saneAriaLabel; + } +} diff --git a/src/vs/base/parts/quickinput/common/quickInput.ts b/src/vs/base/parts/quickinput/common/quickInput.ts index 10c8255a444..f09e508bed7 100644 --- a/src/vs/base/parts/quickinput/common/quickInput.ts +++ b/src/vs/base/parts/quickinput/common/quickInput.ts @@ -6,14 +6,34 @@ import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { IMatch } from 'vs/base/common/filters'; +import { IItemAccessor } from 'vs/base/common/fuzzyScorer'; +import { Schemas } from 'vs/base/common/network'; + +export interface IQuickPickItemHighlights { + label?: IMatch[]; + description?: IMatch[]; + detail?: IMatch[]; +} export interface IQuickPickItem { type?: 'item'; id?: string; label: string; + ariaLabel?: string; description?: string; detail?: string; + /** + * Allows to show a keybinding next to the item to indicate + * how the item can be triggered outside of the picker using + * keyboard shortcut. + */ + keybinding?: ResolvedKeybinding; iconClasses?: string[]; + italic?: boolean; + strikethrough?: boolean; + highlights?: IQuickPickItemHighlights; buttons?: IQuickInputButton[]; picked?: boolean; alwaysShow?: boolean; @@ -29,6 +49,8 @@ export interface IKeyMods { readonly alt: boolean; } +export const NO_KEY_MODS: IKeyMods = { ctrlCmd: false, alt: false }; + export interface IQuickNavigateConfiguration { keybindings: ResolvedKeybinding[]; } @@ -125,7 +147,10 @@ export interface IInputOptions { validateInput?: (input: string) => Promise; } -export interface IQuickInput { +export interface IQuickInput extends IDisposable { + + readonly onDidHide: Event; + readonly onDispose: Event; title: string | undefined; @@ -146,21 +171,48 @@ export interface IQuickInput { show(): void; hide(): void; +} - onDidHide: Event; +export interface IQuickPickAcceptEvent { - dispose(): void; + /** + * Signals if the picker item is to be accepted + * in the background while keeping the picker open. + */ + inBackground: boolean; +} + +export enum ItemActivation { + NONE, + FIRST, + SECOND, + LAST } export interface IQuickPick extends IQuickInput { value: string; + /** + * A method that allows to massage the value used + * for filtering, e.g, to remove certain parts. + */ + filterValue: (value: string) => string; + + ariaLabel: string; + placeholder: string | undefined; readonly onDidChangeValue: Event; - readonly onDidAccept: Event; + readonly onDidAccept: Event; + + /** + * If enabled, will fire the `onDidAccept` event when + * pressing the arrow-right key with the idea of accepting + * the selected item without closing the picker. + */ + canAcceptInBackground: boolean; ok: boolean | 'default'; @@ -198,6 +250,11 @@ export interface IQuickPick extends IQuickInput { readonly onDidChangeActive: Event; + /** + * Allows to control which entry should be activated by default. + */ + itemActivation: ItemActivation; + selectedItems: ReadonlyArray; readonly onDidChangeSelection: Event; @@ -211,6 +268,13 @@ export interface IQuickPick extends IQuickInput { inputHasFocus(): boolean; focusOnInput(): void; + + /** + * Hides the input box from the picker UI. This is typically used + * in combination with quick-navigation where no search UI should + * be presented. + */ + hideInput: boolean; } export interface IInputBox extends IQuickInput { @@ -242,6 +306,11 @@ export interface IQuickInputButton { /** iconPath or iconClass required */ iconClass?: string; tooltip?: string; + /** + * Wether to always show the button. By default buttons + * are only visible when hovering over them with the mouse + */ + alwaysVisible?: boolean; } export interface IQuickPickItemButtonEvent { @@ -254,3 +323,41 @@ export interface IQuickPickItemButtonContext extends I } export type QuickPickInput = T | IQuickPickSeparator; + + +//region Fuzzy Scorer Support + +export type IQuickPickItemWithResource = IQuickPickItem & { resource?: URI }; + +export class QuickPickItemScorerAccessor implements IItemAccessor { + + constructor(private options?: { skipDescription?: boolean, skipPath?: boolean }) { } + + getItemLabel(entry: IQuickPickItemWithResource): string { + return entry.label; + } + + getItemDescription(entry: IQuickPickItemWithResource): string | undefined { + if (this.options?.skipDescription) { + return undefined; + } + + return entry.description; + } + + getItemPath(entry: IQuickPickItemWithResource): string | undefined { + if (this.options?.skipPath) { + return undefined; + } + + if (entry.resource?.scheme === Schemas.file) { + return entry.resource.fsPath; + } + + return entry.resource?.path; + } +} + +export const quickPickItemScorerAccessor = new QuickPickItemScorerAccessor(); + +//#endregion diff --git a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts deleted file mode 100644 index b846129db46..00000000000 --- a/src/vs/base/parts/quickopen/browser/quickOpenModel.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 nls from 'vs/nls'; -import * as types from 'vs/base/common/types'; -import { URI } from 'vs/base/common/uri'; -import { ITree, IActionProvider } from 'vs/base/parts/tree/browser/tree'; -import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; -import { IQuickNavigateConfiguration, IModel, IDataSource, IFilter, IAccessiblityProvider, IRenderer, IRunner, Mode, IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen'; -import { IAction, IActionRunner } from 'vs/base/common/actions'; -import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; -import * as DOM from 'vs/base/browser/dom'; -import { IQuickOpenStyles } from 'vs/base/parts/quickopen/browser/quickOpenWidget'; -import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; -import { OS } from 'vs/base/common/platform'; -import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; -import { IItemAccessor } from 'vs/base/parts/quickopen/common/quickOpenScorer'; -import { coalesce } from 'vs/base/common/arrays'; -import { IMatch } from 'vs/base/common/filters'; - -export interface IContext { - event: any; - quickNavigateConfiguration: IQuickNavigateConfiguration; -} - -export interface IHighlight extends IMatch { - start: number; - end: number; -} - -let IDS = 0; - -export class QuickOpenItemAccessorClass implements IItemAccessor { - - getItemLabel(entry: QuickOpenEntry): string | null { - return types.withUndefinedAsNull(entry.getLabel()); - } - - getItemDescription(entry: QuickOpenEntry): string | null { - return types.withUndefinedAsNull(entry.getDescription()); - } - - getItemPath(entry: QuickOpenEntry): string | undefined { - const resource = entry.getResource(); - - return resource ? resource.fsPath : undefined; - } -} - -export const QuickOpenItemAccessor = new QuickOpenItemAccessorClass(); - -export class QuickOpenEntry { - private id: string; - private labelHighlights?: IHighlight[]; - private descriptionHighlights?: IHighlight[]; - private detailHighlights?: IHighlight[]; - private hidden: boolean | undefined; - - constructor(highlights: IHighlight[] = []) { - this.id = (IDS++).toString(); - this.labelHighlights = highlights; - this.descriptionHighlights = []; - } - - /** - * A unique identifier for the entry - */ - getId(): string { - return this.id; - } - - /** - * The label of the entry to identify it from others in the list - */ - getLabel(): string | undefined { - return undefined; - } - - /** - * The options for the label to use for this entry - */ - getLabelOptions(): IIconLabelValueOptions | undefined { - return undefined; - } - - /** - * The label of the entry to use when a screen reader wants to read about the entry - */ - getAriaLabel(): string { - return coalesce([this.getLabel(), this.getDescription(), this.getDetail()]) - .join(', '); - } - - /** - * Detail information about the entry that is optional and can be shown below the label - */ - getDetail(): string | undefined { - return undefined; - } - - /** - * The icon of the entry to identify it from others in the list - */ - getIcon(): string | undefined { - return undefined; - } - - /** - * A secondary description that is optional and can be shown right to the label - */ - getDescription(): string | undefined { - return undefined; - } - - /** - * A tooltip to show when hovering over the entry. - */ - getTooltip(): string | undefined { - return undefined; - } - - /** - * A tooltip to show when hovering over the description portion of the entry. - */ - getDescriptionTooltip(): string | undefined { - return undefined; - } - - /** - * An optional keybinding to show for an entry. - */ - getKeybinding(): ResolvedKeybinding | undefined { - return undefined; - } - - /** - * A resource for this entry. Resource URIs can be used to compare different kinds of entries and group - * them together. - */ - getResource(): URI | undefined { - return undefined; - } - - /** - * Allows to reuse the same model while filtering. Hidden entries will not show up in the viewer. - */ - isHidden(): boolean { - return !!this.hidden; - } - - /** - * Allows to reuse the same model while filtering. Hidden entries will not show up in the viewer. - */ - setHidden(hidden: boolean): void { - this.hidden = hidden; - } - - /** - * Allows to set highlight ranges that should show up for the entry label and optionally description if set. - */ - setHighlights(labelHighlights?: IHighlight[], descriptionHighlights?: IHighlight[], detailHighlights?: IHighlight[]): void { - this.labelHighlights = labelHighlights; - this.descriptionHighlights = descriptionHighlights; - this.detailHighlights = detailHighlights; - } - - /** - * Allows to return highlight ranges that should show up for the entry label and description. - */ - getHighlights(): [IHighlight[] | undefined /* Label */, IHighlight[] | undefined /* Description */, IHighlight[] | undefined /* Detail */] { - return [this.labelHighlights, this.descriptionHighlights, this.detailHighlights]; - } - - /** - * Called when the entry is selected for opening. Returns a boolean value indicating if an action was performed or not. - * The mode parameter gives an indication if the element is previewed (using arrow keys) or opened. - * - * The context parameter provides additional context information how the run was triggered. - */ - run(mode: Mode, context: IEntryRunContext): boolean { - return false; - } - - /** - * Determines if this quick open entry should merge with the editor history in quick open. If set to true - * and the resource of this entry is the same as the resource for an editor history, it will not show up - * because it is considered to be a duplicate of an editor history. - */ - mergeWithEditorHistory(): boolean { - return false; - } -} - -export class QuickOpenEntryGroup extends QuickOpenEntry { - private entry?: QuickOpenEntry; - private groupLabel?: string; - private withBorder?: boolean; - - constructor(entry?: QuickOpenEntry, groupLabel?: string, withBorder?: boolean) { - super(); - - this.entry = entry; - this.groupLabel = groupLabel; - this.withBorder = withBorder; - } - - /** - * The label of the group or null if none. - */ - getGroupLabel(): string | undefined { - return this.groupLabel; - } - - setGroupLabel(groupLabel: string | undefined): void { - this.groupLabel = groupLabel; - } - - /** - * Whether to show a border on top of the group entry or not. - */ - showBorder(): boolean { - return !!this.withBorder; - } - - setShowBorder(showBorder: boolean): void { - this.withBorder = showBorder; - } - - getLabel(): string | undefined { - return this.entry ? this.entry.getLabel() : super.getLabel(); - } - - getLabelOptions(): IIconLabelValueOptions | undefined { - return this.entry ? this.entry.getLabelOptions() : super.getLabelOptions(); - } - - getAriaLabel(): string { - return this.entry ? this.entry.getAriaLabel() : super.getAriaLabel(); - } - - getDetail(): string | undefined { - return this.entry ? this.entry.getDetail() : super.getDetail(); - } - - getResource(): URI | undefined { - return this.entry ? this.entry.getResource() : super.getResource(); - } - - getIcon(): string | undefined { - return this.entry ? this.entry.getIcon() : super.getIcon(); - } - - getDescription(): string | undefined { - return this.entry ? this.entry.getDescription() : super.getDescription(); - } - - getEntry(): QuickOpenEntry | undefined { - return this.entry; - } - - getHighlights(): [IHighlight[] | undefined, IHighlight[] | undefined, IHighlight[] | undefined] { - return this.entry ? this.entry.getHighlights() : super.getHighlights(); - } - - isHidden(): boolean { - return this.entry ? this.entry.isHidden() : super.isHidden(); - } - - setHighlights(labelHighlights?: IHighlight[], descriptionHighlights?: IHighlight[], detailHighlights?: IHighlight[]): void { - this.entry ? this.entry.setHighlights(labelHighlights, descriptionHighlights, detailHighlights) : super.setHighlights(labelHighlights, descriptionHighlights, detailHighlights); - } - - setHidden(hidden: boolean): void { - this.entry ? this.entry.setHidden(hidden) : super.setHidden(hidden); - } - - run(mode: Mode, context: IEntryRunContext): boolean { - return this.entry ? this.entry.run(mode, context) : super.run(mode, context); - } -} - -class NoActionProvider implements IActionProvider { - - hasActions(tree: ITree, element: any): boolean { - return false; - } - - getActions(tree: ITree, element: any): IAction[] | null { - return null; - } -} - -export interface IQuickOpenEntryTemplateData { - container: HTMLElement; - entry: HTMLElement; - icon: HTMLSpanElement; - label: IconLabel; - detail: HighlightedLabel; - keybinding: KeybindingLabel; - actionBar: ActionBar; -} - -export interface IQuickOpenEntryGroupTemplateData extends IQuickOpenEntryTemplateData { - group?: HTMLDivElement; -} - -const templateEntry = 'quickOpenEntry'; -const templateEntryGroup = 'quickOpenEntryGroup'; - -class Renderer implements IRenderer { - - private actionProvider: IActionProvider; - private actionRunner?: IActionRunner; - - constructor(actionProvider: IActionProvider = new NoActionProvider(), actionRunner?: IActionRunner) { - this.actionProvider = actionProvider; - this.actionRunner = actionRunner; - } - - getHeight(entry: QuickOpenEntry): number { - if (entry.getDetail()) { - return 44; - } - - return 22; - } - - getTemplateId(entry: QuickOpenEntry): string { - if (entry instanceof QuickOpenEntryGroup) { - return templateEntryGroup; - } - - return templateEntry; - } - - renderTemplate(templateId: string, container: HTMLElement, styles: IQuickOpenStyles): IQuickOpenEntryGroupTemplateData { - const entryContainer = document.createElement('div'); - DOM.addClass(entryContainer, 'sub-content'); - container.appendChild(entryContainer); - - // Entry - const row1 = DOM.$('.quick-open-row'); - const row2 = DOM.$('.quick-open-row'); - const entry = DOM.$('.quick-open-entry', undefined, row1, row2); - entryContainer.appendChild(entry); - - // Icon - const icon = document.createElement('span'); - row1.appendChild(icon); - - // Label - const label = new IconLabel(row1, { supportHighlights: true, supportDescriptionHighlights: true, supportCodicons: true }); - - // Keybinding - const keybindingContainer = document.createElement('span'); - row1.appendChild(keybindingContainer); - DOM.addClass(keybindingContainer, 'quick-open-entry-keybinding'); - const keybinding = new KeybindingLabel(keybindingContainer, OS); - - // Detail - const detailContainer = document.createElement('div'); - row2.appendChild(detailContainer); - DOM.addClass(detailContainer, 'quick-open-entry-meta'); - const detail = new HighlightedLabel(detailContainer, true); - - // Entry Group - let group: HTMLDivElement | undefined; - if (templateId === templateEntryGroup) { - group = document.createElement('div'); - DOM.addClass(group, 'results-group'); - container.appendChild(group); - } - - // Actions - DOM.addClass(container, 'actions'); - - const actionBarContainer = document.createElement('div'); - DOM.addClass(actionBarContainer, 'primary-action-bar'); - container.appendChild(actionBarContainer); - - const actionBar = new ActionBar(actionBarContainer, { - actionRunner: this.actionRunner - }); - - return { - container, - entry, - icon, - label, - detail, - keybinding, - group, - actionBar - }; - } - - renderElement(entry: QuickOpenEntry, templateId: string, data: IQuickOpenEntryGroupTemplateData, styles: IQuickOpenStyles): void { - - // Action Bar - if (this.actionProvider.hasActions(null, entry)) { - DOM.addClass(data.container, 'has-actions'); - } else { - DOM.removeClass(data.container, 'has-actions'); - } - - data.actionBar.context = entry; // make sure the context is the current element - - const actions = this.actionProvider.getActions(null, entry); - if (data.actionBar.isEmpty() && actions && actions.length > 0) { - data.actionBar.push(actions, { icon: true, label: false }); - } else if (!data.actionBar.isEmpty() && (!actions || actions.length === 0)) { - data.actionBar.clear(); - } - - // Entry group class - if (entry instanceof QuickOpenEntryGroup && entry.getGroupLabel()) { - DOM.addClass(data.container, 'has-group-label'); - } else { - DOM.removeClass(data.container, 'has-group-label'); - } - - // Entry group - if (entry instanceof QuickOpenEntryGroup) { - const group = entry; - const groupData = data; - - // Border - if (group.showBorder()) { - DOM.addClass(groupData.container, 'results-group-separator'); - if (styles.pickerGroupBorder) { - groupData.container.style.borderTopColor = styles.pickerGroupBorder.toString(); - } - } else { - DOM.removeClass(groupData.container, 'results-group-separator'); - groupData.container.style.borderTopColor = ''; - } - - // Group Label - const groupLabel = group.getGroupLabel() || ''; - if (groupData.group) { - groupData.group.textContent = groupLabel; - if (styles.pickerGroupForeground) { - groupData.group.style.color = styles.pickerGroupForeground.toString(); - } - } - } - - // Normal Entry - if (entry instanceof QuickOpenEntry) { - const [labelHighlights, descriptionHighlights, detailHighlights] = entry.getHighlights(); - - // Icon - const iconClass = entry.getIcon() ? ('quick-open-entry-icon ' + entry.getIcon()) : ''; - data.icon.className = iconClass; - - // Label - const options: IIconLabelValueOptions = entry.getLabelOptions() || Object.create(null); - options.matches = labelHighlights || []; - options.title = entry.getTooltip(); - options.descriptionTitle = entry.getDescriptionTooltip() || entry.getDescription(); // tooltip over description because it could overflow - options.descriptionMatches = descriptionHighlights || []; - data.label.setLabel(entry.getLabel() || '', entry.getDescription(), options); - - // Meta - data.detail.set(entry.getDetail(), detailHighlights); - - // Keybinding - data.keybinding.set(entry.getKeybinding()!); - } - } - - disposeTemplate(templateId: string, templateData: IQuickOpenEntryGroupTemplateData): void { - templateData.actionBar.dispose(); - templateData.actionBar = null!; - templateData.container = null!; - templateData.entry = null!; - templateData.keybinding = null!; - templateData.detail = null!; - templateData.group = null!; - templateData.icon = null!; - templateData.label.dispose(); - templateData.label = null!; - } -} - -export class QuickOpenModel implements - IModel, - IDataSource, - IFilter, - IRunner, - IAccessiblityProvider -{ - private _entries: QuickOpenEntry[]; - private _dataSource: IDataSource; - private _renderer: IRenderer; - private _filter: IFilter; - private _runner: IRunner; - private _accessibilityProvider: IAccessiblityProvider; - - constructor(entries: QuickOpenEntry[] = [], actionProvider: IActionProvider = new NoActionProvider()) { - this._entries = entries; - this._dataSource = this; - this._renderer = new Renderer(actionProvider); - this._filter = this; - this._runner = this; - this._accessibilityProvider = this; - } - - get entries() { return this._entries; } - get dataSource() { return this._dataSource; } - get renderer() { return this._renderer; } - get filter() { return this._filter; } - get runner() { return this._runner; } - get accessibilityProvider() { return this._accessibilityProvider; } - - set entries(entries: QuickOpenEntry[]) { - this._entries = entries; - } - - /** - * Adds entries that should show up in the quick open viewer. - */ - addEntries(entries: QuickOpenEntry[]): void { - if (types.isArray(entries)) { - this._entries = this._entries.concat(entries); - } - } - - /** - * Set the entries that should show up in the quick open viewer. - */ - setEntries(entries: QuickOpenEntry[]): void { - if (types.isArray(entries)) { - this._entries = entries; - } - } - - /** - * Get the entries that should show up in the quick open viewer. - * - * @visibleOnly optional parameter to only return visible entries - */ - getEntries(visibleOnly?: boolean): QuickOpenEntry[] { - if (visibleOnly) { - return this._entries.filter((e) => !e.isHidden()); - } - - return this._entries; - } - - getId(entry: QuickOpenEntry): string { - return entry.getId(); - } - - getLabel(entry: QuickOpenEntry): string | null { - return types.withUndefinedAsNull(entry.getLabel()); - } - - getAriaLabel(entry: QuickOpenEntry): string { - const ariaLabel = entry.getAriaLabel(); - if (ariaLabel) { - return nls.localize('quickOpenAriaLabelEntry', "{0}, picker", entry.getAriaLabel()); - } - - return nls.localize('quickOpenAriaLabel', "picker"); - } - - isVisible(entry: QuickOpenEntry): boolean { - return !entry.isHidden(); - } - - run(entry: QuickOpenEntry, mode: Mode, context: IEntryRunContext): boolean { - return entry.run(mode, context); - } -} diff --git a/src/vs/base/parts/quickopen/browser/quickOpenViewer.ts b/src/vs/base/parts/quickopen/browser/quickOpenViewer.ts deleted file mode 100644 index a87b30dbadc..00000000000 --- a/src/vs/base/parts/quickopen/browser/quickOpenViewer.ts +++ /dev/null @@ -1,142 +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 { isFunction } from 'vs/base/common/types'; -import { ITree, IRenderer, IFilter, IDataSource, IAccessibilityProvider } from 'vs/base/parts/tree/browser/tree'; -import { IModel } from 'vs/base/parts/quickopen/common/quickOpen'; -import { IQuickOpenStyles } from 'vs/base/parts/quickopen/browser/quickOpenWidget'; - -export interface IModelProvider { - getModel(): IModel; -} - -export class DataSource implements IDataSource { - - private modelProvider: IModelProvider; - - constructor(model: IModel); - constructor(modelProvider: IModelProvider); - constructor(arg: any) { - this.modelProvider = isFunction(arg.getModel) ? arg : { getModel: () => arg }; - } - - getId(tree: ITree, element: any): string { - if (!element) { - return null!; - } - - const model = this.modelProvider.getModel(); - return model === element ? '__root__' : model.dataSource.getId(element); - } - - hasChildren(tree: ITree, element: any): boolean { - const model = this.modelProvider.getModel(); - return !!(model && model === element && model.entries.length > 0); - } - - getChildren(tree: ITree, element: any): Promise { - const model = this.modelProvider.getModel(); - return Promise.resolve(model === element ? model.entries : []); - } - - getParent(tree: ITree, element: any): Promise { - return Promise.resolve(null); - } -} - -export class AccessibilityProvider implements IAccessibilityProvider { - constructor(private modelProvider: IModelProvider) { } - - getAriaLabel(tree: ITree, element: any): string | null { - const model = this.modelProvider.getModel(); - - return model.accessibilityProvider ? model.accessibilityProvider.getAriaLabel(element) : null; - } - - getPosInSet(tree: ITree, element: any): string { - const model = this.modelProvider.getModel(); - let i = 0; - if (model.filter) { - for (const entry of model.entries) { - if (model.filter.isVisible(entry)) { - i++; - } - if (entry === element) { - break; - } - } - } else { - i = model.entries.indexOf(element) + 1; - } - return String(i); - } - - getSetSize(): string { - const model = this.modelProvider.getModel(); - let n = 0; - if (model.filter) { - for (const entry of model.entries) { - if (model.filter.isVisible(entry)) { - n++; - } - } - } else { - n = model.entries.length; - } - return String(n); - } -} - -export class Filter implements IFilter { - - constructor(private modelProvider: IModelProvider) { } - - isVisible(tree: ITree, element: any): boolean { - const model = this.modelProvider.getModel(); - - if (!model.filter) { - return true; - } - - return model.filter.isVisible(element); - } -} - -export class Renderer implements IRenderer { - private styles: IQuickOpenStyles; - - constructor(private modelProvider: IModelProvider, styles: IQuickOpenStyles) { - this.styles = styles; - } - - updateStyles(styles: IQuickOpenStyles): void { - this.styles = styles; - } - - getHeight(tree: ITree, element: any): number { - const model = this.modelProvider.getModel(); - return model.renderer.getHeight(element); - } - - getTemplateId(tree: ITree, element: any): string { - const model = this.modelProvider.getModel(); - return model.renderer.getTemplateId(element); - } - - renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any { - const model = this.modelProvider.getModel(); - return model.renderer.renderTemplate(templateId, container, this.styles); - } - - renderElement(tree: ITree, element: any, templateId: string, templateData: any): void { - const model = this.modelProvider.getModel(); - model.renderer.renderElement(element, templateId, templateData, this.styles); - } - - disposeTemplate(tree: ITree, templateId: string, templateData: any): void { - const model = this.modelProvider.getModel(); - model.renderer.disposeTemplate(templateId, templateData); - } -} diff --git a/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts b/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts deleted file mode 100644 index 463cbc5a2ba..00000000000 --- a/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts +++ /dev/null @@ -1,1041 +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 'vs/css!./quickopen'; -import * as nls from 'vs/nls'; -import * as platform from 'vs/base/common/platform'; -import * as types from 'vs/base/common/types'; -import { IQuickNavigateConfiguration, IAutoFocus, IEntryRunContext, IModel, Mode, IKeyMods } from 'vs/base/parts/quickopen/common/quickOpen'; -import { Filter, Renderer, DataSource, IModelProvider, AccessibilityProvider } from 'vs/base/parts/quickopen/browser/quickOpenViewer'; -import { ITree, ContextMenuEvent, IActionProvider, ITreeStyles, ITreeOptions, ITreeConfiguration } from 'vs/base/parts/tree/browser/tree'; -import { InputBox, MessageType, IInputBoxStyles, IRange } from 'vs/base/browser/ui/inputbox/inputBox'; -import Severity from 'vs/base/common/severity'; -import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; -import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { DefaultController, ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults'; -import * as DOM from 'vs/base/browser/dom'; -import { KeyCode } from 'vs/base/common/keyCodes'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { ScrollbarVisibility } from 'vs/base/common/scrollable'; -import { Color } from 'vs/base/common/color'; -import { mixin } from 'vs/base/common/objects'; -import { StandardMouseEvent, IMouseEvent } from 'vs/base/browser/mouseEvent'; -import { IThemable } from 'vs/base/common/styler'; - -export interface IQuickOpenCallbacks { - onOk: () => void; - onCancel: () => void; - onType: (value: string) => void; - onShow?: () => void; - onHide?: (reason: HideReason) => void; - onFocusLost?: () => boolean /* veto close */; -} - -export interface IQuickOpenOptions extends IQuickOpenStyles { - minItemsToShow?: number; - maxItemsToShow?: number; - inputPlaceHolder?: string; - inputAriaLabel?: string; - actionProvider?: IActionProvider; - keyboardSupport?: boolean; - treeCreator?: (container: HTMLElement, configuration: ITreeConfiguration, options?: ITreeOptions) => ITree; -} - -export interface IQuickOpenStyles extends IInputBoxStyles, ITreeStyles { - background?: Color; - foreground?: Color; - borderColor?: Color; - pickerGroupForeground?: Color; - pickerGroupBorder?: Color; - widgetShadow?: Color; - progressBarBackground?: Color; -} - -export interface IShowOptions { - quickNavigateConfiguration?: IQuickNavigateConfiguration; - autoFocus?: IAutoFocus; - inputSelection?: IRange; - value?: string; -} - -export class QuickOpenController extends DefaultController { - - onContextMenu(tree: ITree, element: any, event: ContextMenuEvent): boolean { - if (platform.isMacintosh) { - return this.onLeftClick(tree, element, event); // https://github.com/Microsoft/vscode/issues/1011 - } - - return super.onContextMenu(tree, element, event); - } - - onMouseMiddleClick(tree: ITree, element: any, event: IMouseEvent): boolean { - return this.onLeftClick(tree, element, event); - } -} - -export const enum HideReason { - ELEMENT_SELECTED, - FOCUS_LOST, - CANCELED -} - -const defaultStyles = { - background: Color.fromHex('#1E1E1E'), - foreground: Color.fromHex('#CCCCCC'), - pickerGroupForeground: Color.fromHex('#0097FB'), - pickerGroupBorder: Color.fromHex('#3F3F46'), - widgetShadow: Color.fromHex('#000000'), - progressBarBackground: Color.fromHex('#0E70C0') -}; - -const DEFAULT_INPUT_ARIA_LABEL = nls.localize('quickOpenAriaLabel', "Quick picker. Type to narrow down results."); - -export class QuickOpenWidget extends Disposable implements IModelProvider, IThemable { - - private static readonly MAX_WIDTH = 600; // Max total width of quick open widget - private static readonly MAX_ITEMS_HEIGHT = 20 * 22; // Max height of item list below input field - - private isDisposed: boolean; - private options: IQuickOpenOptions; - // @ts-ignore (legacy widget - to be replaced with quick input) - private element: HTMLElement; - // @ts-ignore (legacy widget - to be replaced with quick input) - private tree: ITree; - // @ts-ignore (legacy widget - to be replaced with quick input) - private inputBox: InputBox; - // @ts-ignore (legacy widget - to be replaced with quick input) - private inputContainer: HTMLElement; - // @ts-ignore (legacy widget - to be replaced with quick input) - private helpText: HTMLElement; - // @ts-ignore (legacy widget - to be replaced with quick input) - private resultCount: HTMLElement; - // @ts-ignore (legacy widget - to be replaced with quick input) - private treeContainer: HTMLElement; - // @ts-ignore (legacy widget - to be replaced with quick input) - private progressBar: ProgressBar; - // @ts-ignore (legacy widget - to be replaced with quick input) - private visible: boolean; - // @ts-ignore (legacy widget - to be replaced with quick input) - private isLoosingFocus: boolean; - private callbacks: IQuickOpenCallbacks; - private quickNavigateConfiguration: IQuickNavigateConfiguration | undefined; - private container: HTMLElement; - // @ts-ignore (legacy widget - to be replaced with quick input) - private treeElement: HTMLElement; - // @ts-ignore (legacy widget - to be replaced with quick input) - private inputElement: HTMLElement; - // @ts-ignore (legacy widget - to be replaced with quick input) - private layoutDimensions: DOM.Dimension; - private model: IModel | null; - private inputChangingTimeoutHandle: any; - // @ts-ignore (legacy widget - to be replaced with quick input) - private styles: IQuickOpenStyles; - // @ts-ignore (legacy widget - to be replaced with quick input) - private renderer: Renderer; - - constructor(container: HTMLElement, callbacks: IQuickOpenCallbacks, options: IQuickOpenOptions) { - super(); - - this.isDisposed = false; - this.container = container; - this.callbacks = callbacks; - this.options = options; - this.styles = options || Object.create(null); - mixin(this.styles, defaultStyles, false); - this.model = null; - } - - getElement(): HTMLElement { - return this.element; - } - - getModel(): IModel { - return this.model!; - } - - setCallbacks(callbacks: IQuickOpenCallbacks): void { - this.callbacks = callbacks; - } - - create(): HTMLElement { - - // Container - this.element = document.createElement('div'); - DOM.addClass(this.element, 'monaco-quick-open-widget'); - this.container.appendChild(this.element); - - this._register(DOM.addDisposableListener(this.element, DOM.EventType.CONTEXT_MENU, e => DOM.EventHelper.stop(e, true))); // Do this to fix an issue on Mac where the menu goes into the way - this._register(DOM.addDisposableListener(this.element, DOM.EventType.FOCUS, e => this.gainingFocus(), true)); - this._register(DOM.addDisposableListener(this.element, DOM.EventType.BLUR, e => this.loosingFocus(e), true)); - this._register(DOM.addDisposableListener(this.element, DOM.EventType.KEY_DOWN, e => { - const keyboardEvent: StandardKeyboardEvent = new StandardKeyboardEvent(e); - if (keyboardEvent.keyCode === KeyCode.Escape) { - DOM.EventHelper.stop(e, true); - - this.hide(HideReason.CANCELED); - } else if (keyboardEvent.keyCode === KeyCode.Tab && !keyboardEvent.altKey && !keyboardEvent.ctrlKey && !keyboardEvent.metaKey) { - const stops = (e.currentTarget as HTMLElement).querySelectorAll('input, .monaco-tree, .monaco-tree-row.focused .action-label.icon') as NodeListOf; - if (keyboardEvent.shiftKey && keyboardEvent.target === stops[0]) { - DOM.EventHelper.stop(e, true); - stops[stops.length - 1].focus(); - } else if (!keyboardEvent.shiftKey && keyboardEvent.target === stops[stops.length - 1]) { - DOM.EventHelper.stop(e, true); - stops[0].focus(); - } - } - })); - - // Progress Bar - this.progressBar = this._register(new ProgressBar(this.element, { progressBarBackground: this.styles.progressBarBackground })); - this.progressBar.hide(); - - // Input Field - this.inputContainer = document.createElement('div'); - DOM.addClass(this.inputContainer, 'quick-open-input'); - this.element.appendChild(this.inputContainer); - - this.inputBox = this._register(new InputBox(this.inputContainer, undefined, { - placeholder: this.options.inputPlaceHolder || '', - ariaLabel: DEFAULT_INPUT_ARIA_LABEL, - inputBackground: this.styles.inputBackground, - inputForeground: this.styles.inputForeground, - inputBorder: this.styles.inputBorder, - inputValidationInfoBackground: this.styles.inputValidationInfoBackground, - inputValidationInfoForeground: this.styles.inputValidationInfoForeground, - inputValidationInfoBorder: this.styles.inputValidationInfoBorder, - inputValidationWarningBackground: this.styles.inputValidationWarningBackground, - inputValidationWarningForeground: this.styles.inputValidationWarningForeground, - inputValidationWarningBorder: this.styles.inputValidationWarningBorder, - inputValidationErrorBackground: this.styles.inputValidationErrorBackground, - inputValidationErrorForeground: this.styles.inputValidationErrorForeground, - inputValidationErrorBorder: this.styles.inputValidationErrorBorder - })); - - this.inputElement = this.inputBox.inputElement; - this.inputElement.setAttribute('role', 'combobox'); - this.inputElement.setAttribute('aria-haspopup', 'false'); - this.inputElement.setAttribute('aria-autocomplete', 'list'); - - this._register(DOM.addDisposableListener(this.inputBox.inputElement, DOM.EventType.INPUT, (e: Event) => this.onType())); - this._register(DOM.addDisposableListener(this.inputBox.inputElement, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => { - const keyboardEvent: StandardKeyboardEvent = new StandardKeyboardEvent(e); - const shouldOpenInBackground = this.shouldOpenInBackground(keyboardEvent); - - // Do not handle Tab: It is used to navigate between elements without mouse - if (keyboardEvent.keyCode === KeyCode.Tab) { - return; - } - - // Pass tree navigation keys to the tree but leave focus in input field - else if (keyboardEvent.keyCode === KeyCode.DownArrow || keyboardEvent.keyCode === KeyCode.UpArrow || keyboardEvent.keyCode === KeyCode.PageDown || keyboardEvent.keyCode === KeyCode.PageUp) { - DOM.EventHelper.stop(e, true); - - this.navigateInTree(keyboardEvent.keyCode, keyboardEvent.shiftKey); - - // Position cursor at the end of input to allow right arrow (open in background) - // to function immediately unless the user has made a selection - if (this.inputBox.inputElement.selectionStart === this.inputBox.inputElement.selectionEnd) { - this.inputBox.inputElement.selectionStart = this.inputBox.value.length; - } - } - - // Select element on Enter or on Arrow-Right if we are at the end of the input - else if (keyboardEvent.keyCode === KeyCode.Enter || shouldOpenInBackground) { - DOM.EventHelper.stop(e, true); - - const focus = this.tree.getFocus(); - if (focus) { - this.elementSelected(focus, e, shouldOpenInBackground ? Mode.OPEN_IN_BACKGROUND : Mode.OPEN); - } - } - })); - - // Result count for screen readers - this.resultCount = document.createElement('div'); - DOM.addClass(this.resultCount, 'quick-open-result-count'); - this.resultCount.setAttribute('aria-live', 'polite'); - this.resultCount.setAttribute('aria-atomic', 'true'); - this.element.appendChild(this.resultCount); - - // Tree - this.treeContainer = document.createElement('div'); - DOM.addClass(this.treeContainer, 'quick-open-tree'); - this.element.appendChild(this.treeContainer); - - const createTree = this.options.treeCreator || ((container, config, opts) => new Tree(container, config, opts)); - - this.tree = this._register(createTree(this.treeContainer, { - dataSource: new DataSource(this), - controller: new QuickOpenController({ clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: this.options.keyboardSupport }), - renderer: (this.renderer = new Renderer(this, this.styles)), - filter: new Filter(this), - accessibilityProvider: new AccessibilityProvider(this) - }, { - twistiePixels: 11, - indentPixels: 0, - alwaysFocused: true, - verticalScrollMode: ScrollbarVisibility.Visible, - horizontalScrollMode: ScrollbarVisibility.Hidden, - ariaLabel: nls.localize('treeAriaLabel', "Quick Picker"), - keyboardSupport: this.options.keyboardSupport, - preventRootFocus: false - })); - - this.treeElement = this.tree.getHTMLElement(); - - // Handle Focus and Selection event - this._register(this.tree.onDidChangeFocus(event => { - this.elementFocused(event.focus, event); - })); - - this._register(this.tree.onDidChangeSelection(event => { - if (event.selection && event.selection.length > 0) { - const mouseEvent: StandardMouseEvent = event.payload && event.payload.originalEvent instanceof StandardMouseEvent ? event.payload.originalEvent : undefined; - const shouldOpenInBackground = mouseEvent ? this.shouldOpenInBackground(mouseEvent) : false; - - this.elementSelected(event.selection[0], event, shouldOpenInBackground ? Mode.OPEN_IN_BACKGROUND : Mode.OPEN); - } - })); - - this._register(DOM.addDisposableListener(this.treeContainer, DOM.EventType.KEY_DOWN, e => { - const keyboardEvent: StandardKeyboardEvent = new StandardKeyboardEvent(e); - - // Only handle when in quick navigation mode - if (!this.quickNavigateConfiguration) { - return; - } - - // Support keyboard navigation in quick navigation mode - if (keyboardEvent.keyCode === KeyCode.DownArrow || keyboardEvent.keyCode === KeyCode.UpArrow || keyboardEvent.keyCode === KeyCode.PageDown || keyboardEvent.keyCode === KeyCode.PageUp) { - DOM.EventHelper.stop(e, true); - - this.navigateInTree(keyboardEvent.keyCode); - } - - // Support to open item with Enter still even in quick nav mode - else if (keyboardEvent.keyCode === KeyCode.Enter) { - DOM.EventHelper.stop(e, true); - - const focus = this.tree.getFocus(); - if (focus) { - this.elementSelected(focus, e); - } - } - })); - - this._register(DOM.addDisposableListener(this.treeContainer, DOM.EventType.KEY_UP, e => { - const keyboardEvent: StandardKeyboardEvent = new StandardKeyboardEvent(e); - const keyCode = keyboardEvent.keyCode; - - // Only handle when in quick navigation mode - if (!this.quickNavigateConfiguration) { - return; - } - - // Select element when keys are pressed that signal it - const quickNavKeys = this.quickNavigateConfiguration.keybindings; - const wasTriggerKeyPressed = quickNavKeys.some(k => { - const [firstPart, chordPart] = k.getParts(); - if (chordPart) { - return false; - } - - if (firstPart.shiftKey && keyCode === KeyCode.Shift) { - if (keyboardEvent.ctrlKey || keyboardEvent.altKey || keyboardEvent.metaKey) { - return false; // this is an optimistic check for the shift key being used to navigate back in quick open - } - - return true; - } - - if (firstPart.altKey && keyCode === KeyCode.Alt) { - return true; - } - - if (firstPart.ctrlKey && keyCode === KeyCode.Ctrl) { - return true; - } - - if (firstPart.metaKey && keyCode === KeyCode.Meta) { - return true; - } - - return false; - }); - - if (wasTriggerKeyPressed) { - const focus = this.tree.getFocus(); - if (focus) { - this.elementSelected(focus, e); - } - } - })); - - // Support layout - if (this.layoutDimensions) { - this.layout(this.layoutDimensions); - } - - this.applyStyles(); - - // Allows focus to switch to next/previous entry after tab into an actionbar item - this._register(DOM.addDisposableListener(this.treeContainer, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => { - const keyboardEvent: StandardKeyboardEvent = new StandardKeyboardEvent(e); - // Only handle when not in quick navigation mode - if (this.quickNavigateConfiguration) { - return; - } - if (keyboardEvent.keyCode === KeyCode.DownArrow || keyboardEvent.keyCode === KeyCode.UpArrow || keyboardEvent.keyCode === KeyCode.PageDown || keyboardEvent.keyCode === KeyCode.PageUp) { - DOM.EventHelper.stop(e, true); - this.navigateInTree(keyboardEvent.keyCode, keyboardEvent.shiftKey); - this.treeElement.focus(); - } - })); - - return this.element; - } - - style(styles: IQuickOpenStyles): void { - this.styles = styles; - - this.applyStyles(); - } - - protected applyStyles(): void { - if (this.element) { - const foreground = this.styles.foreground ? this.styles.foreground.toString() : ''; - const background = this.styles.background ? this.styles.background.toString() : ''; - const borderColor = this.styles.borderColor ? this.styles.borderColor.toString() : ''; - const widgetShadow = this.styles.widgetShadow ? this.styles.widgetShadow.toString() : ''; - - this.element.style.color = foreground; - this.element.style.backgroundColor = background; - this.element.style.borderColor = borderColor; - this.element.style.borderWidth = borderColor ? '1px' : ''; - this.element.style.borderStyle = borderColor ? 'solid' : ''; - this.element.style.boxShadow = widgetShadow ? `0 5px 8px ${widgetShadow}` : ''; - } - - if (this.progressBar) { - this.progressBar.style({ - progressBarBackground: this.styles.progressBarBackground - }); - } - - if (this.inputBox) { - this.inputBox.style({ - inputBackground: this.styles.inputBackground, - inputForeground: this.styles.inputForeground, - inputBorder: this.styles.inputBorder, - inputValidationInfoBackground: this.styles.inputValidationInfoBackground, - inputValidationInfoForeground: this.styles.inputValidationInfoForeground, - inputValidationInfoBorder: this.styles.inputValidationInfoBorder, - inputValidationWarningBackground: this.styles.inputValidationWarningBackground, - inputValidationWarningForeground: this.styles.inputValidationWarningForeground, - inputValidationWarningBorder: this.styles.inputValidationWarningBorder, - inputValidationErrorBackground: this.styles.inputValidationErrorBackground, - inputValidationErrorForeground: this.styles.inputValidationErrorForeground, - inputValidationErrorBorder: this.styles.inputValidationErrorBorder - }); - } - - if (this.tree && !this.options.treeCreator) { - this.tree.style(this.styles); - } - - if (this.renderer) { - this.renderer.updateStyles(this.styles); - } - } - - private shouldOpenInBackground(e: StandardKeyboardEvent | StandardMouseEvent): boolean { - - // Keyboard - if (e instanceof StandardKeyboardEvent) { - if (e.keyCode !== KeyCode.RightArrow) { - return false; // only for right arrow - } - - if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) { - return false; // no modifiers allowed - } - - // validate the cursor is at the end of the input and there is no selection, - // and if not prevent opening in the background such as the selection can be changed - const element = this.inputBox.inputElement; - return element.selectionEnd === this.inputBox.value.length && element.selectionStart === element.selectionEnd; - } - - // Mouse - return e.middleButton; - } - - private onType(): void { - const value = this.inputBox.value; - - // Adjust help text as needed if present - if (this.helpText) { - if (value) { - DOM.hide(this.helpText); - } else { - DOM.show(this.helpText); - } - } - - // Send to callbacks - this.callbacks.onType(value); - } - - navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration): void { - if (this.isVisible()) { - - // Transition into quick navigate mode if not yet done - if (!this.quickNavigateConfiguration && quickNavigate) { - this.quickNavigateConfiguration = quickNavigate; - this.tree.domFocus(); - } - - // Navigate - this.navigateInTree(next ? KeyCode.DownArrow : KeyCode.UpArrow); - } - } - - private navigateInTree(keyCode: KeyCode, isShift?: boolean): void { - const model: IModel = this.tree.getInput(); - const entries = model ? model.entries : []; - const oldFocus = this.tree.getFocus(); - - // Normal Navigation - switch (keyCode) { - case KeyCode.DownArrow: - this.tree.focusNext(); - break; - - case KeyCode.UpArrow: - this.tree.focusPrevious(); - break; - - case KeyCode.PageDown: - this.tree.focusNextPage(); - break; - - case KeyCode.PageUp: - this.tree.focusPreviousPage(); - break; - - case KeyCode.Tab: - if (isShift) { - this.tree.focusPrevious(); - } else { - this.tree.focusNext(); - } - break; - } - - let newFocus = this.tree.getFocus(); - - // Support cycle-through navigation if focus did not change - if (entries.length > 1 && oldFocus === newFocus) { - - // Up from no entry or first entry goes down to last - if (keyCode === KeyCode.UpArrow || (keyCode === KeyCode.Tab && isShift)) { - this.tree.focusLast(); - } - - // Down from last entry goes to up to first - else if (keyCode === KeyCode.DownArrow || keyCode === KeyCode.Tab && !isShift) { - this.tree.focusFirst(); - } - } - - // Reveal - newFocus = this.tree.getFocus(); - if (newFocus) { - this.tree.reveal(newFocus); - } - } - - private elementFocused(value: any, event?: any): void { - if (!value || !this.isVisible()) { - return; - } - - // ARIA - const arivaActiveDescendant = this.treeElement.getAttribute('aria-activedescendant'); - if (arivaActiveDescendant) { - this.inputElement.setAttribute('aria-activedescendant', arivaActiveDescendant); - } else { - this.inputElement.removeAttribute('aria-activedescendant'); - } - - const context: IEntryRunContext = { event: event, keymods: this.extractKeyMods(event), quickNavigateConfiguration: this.quickNavigateConfiguration }; - this.model!.runner.run(value, Mode.PREVIEW, context); - } - - private elementSelected(value: any, event?: any, preferredMode?: Mode): void { - let hide = true; - - // Trigger open of element on selection - if (this.isVisible()) { - let mode = preferredMode || Mode.OPEN; - - const context: IEntryRunContext = { event, keymods: this.extractKeyMods(event), quickNavigateConfiguration: this.quickNavigateConfiguration }; - - hide = this.model!.runner.run(value, mode, context); - } - - // Hide if command was run successfully - if (hide) { - this.hide(HideReason.ELEMENT_SELECTED); - } - } - - private extractKeyMods(event: any): IKeyMods { - return { - ctrlCmd: event && (event.ctrlKey || event.metaKey || (event.payload && event.payload.originalEvent && (event.payload.originalEvent.ctrlKey || event.payload.originalEvent.metaKey))), - alt: event && (event.altKey || (event.payload && event.payload.originalEvent && event.payload.originalEvent.altKey)) - }; - } - - show(prefix: string, options?: IShowOptions): void; - show(input: IModel, options?: IShowOptions): void; - show(param: any, options?: IShowOptions): void { - this.visible = true; - this.isLoosingFocus = false; - this.quickNavigateConfiguration = options ? options.quickNavigateConfiguration : undefined; - - // Adjust UI for quick navigate mode - if (this.quickNavigateConfiguration) { - DOM.hide(this.inputContainer); - DOM.show(this.element); - this.tree.domFocus(); - } - - // Otherwise use normal UI - else { - DOM.show(this.inputContainer); - DOM.show(this.element); - this.inputBox.focus(); - } - - // Adjust Help text for IE - if (this.helpText) { - if (this.quickNavigateConfiguration || types.isString(param)) { - DOM.hide(this.helpText); - } else { - DOM.show(this.helpText); - } - } - - // Show based on param - if (types.isString(param)) { - this.doShowWithPrefix(param); - } else { - if (options && options.value) { - this.restoreLastInput(options.value); - } - this.doShowWithInput(param, options && options.autoFocus ? options.autoFocus : {}); - } - - // Respect selectAll option - if (options && options.inputSelection && !this.quickNavigateConfiguration) { - this.inputBox.select(options.inputSelection); - } - - if (this.callbacks.onShow) { - this.callbacks.onShow(); - } - } - - private restoreLastInput(lastInput: string) { - this.inputBox.value = lastInput; - this.inputBox.select(); - this.callbacks.onType(lastInput); - } - - private doShowWithPrefix(prefix: string): void { - this.inputBox.value = prefix; - this.callbacks.onType(prefix); - } - - private doShowWithInput(input: IModel, autoFocus: IAutoFocus): void { - this.setInput(input, autoFocus); - } - - private setInputAndLayout(input: IModel, autoFocus?: IAutoFocus): void { - this.treeContainer.style.height = `${this.getHeight(input)}px`; - - this.tree.setInput(null).then(() => { - this.model = input; - - // ARIA - this.inputElement.setAttribute('aria-haspopup', String(input && input.entries && input.entries.length > 0)); - - return this.tree.setInput(input); - }).then(() => { - - // Indicate entries to tree - this.tree.layout(); - - const entries = input ? input.entries.filter(e => this.isElementVisible(input, e)) : []; - this.updateResultCount(entries.length); - - // Handle auto focus - if (entries.length) { - this.autoFocus(input, entries, autoFocus); - } - }); - } - - private isElementVisible(input: IModel, e: T): boolean { - if (!input.filter) { - return true; - } - - return input.filter.isVisible(e); - } - - private autoFocus(input: IModel, entries: any[], autoFocus: IAutoFocus = {}): void { - - // First check for auto focus of prefix matches - if (autoFocus.autoFocusPrefixMatch) { - let caseSensitiveMatch: any; - let caseInsensitiveMatch: any; - const prefix = autoFocus.autoFocusPrefixMatch; - const lowerCasePrefix = prefix.toLowerCase(); - for (const entry of entries) { - const label = input.dataSource.getLabel(entry) || ''; - - if (!caseSensitiveMatch && label.indexOf(prefix) === 0) { - caseSensitiveMatch = entry; - } else if (!caseInsensitiveMatch && label.toLowerCase().indexOf(lowerCasePrefix) === 0) { - caseInsensitiveMatch = entry; - } - - if (caseSensitiveMatch && caseInsensitiveMatch) { - break; - } - } - - const entryToFocus = caseSensitiveMatch || caseInsensitiveMatch; - if (entryToFocus) { - this.tree.setFocus(entryToFocus); - this.tree.reveal(entryToFocus, 0.5); - - return; - } - } - - // Second check for auto focus of first entry - if (autoFocus.autoFocusFirstEntry) { - this.tree.focusFirst(); - this.tree.reveal(this.tree.getFocus()); - } - - // Third check for specific index option - else if (typeof autoFocus.autoFocusIndex === 'number') { - if (entries.length > autoFocus.autoFocusIndex) { - this.tree.focusNth(autoFocus.autoFocusIndex); - this.tree.reveal(this.tree.getFocus()); - } - } - - // Check for auto focus of second entry - else if (autoFocus.autoFocusSecondEntry) { - if (entries.length > 1) { - this.tree.focusNth(1); - } - } - - // Finally check for auto focus of last entry - else if (autoFocus.autoFocusLastEntry) { - if (entries.length > 1) { - this.tree.focusLast(); - this.tree.reveal(this.tree.getFocus()); - } - } - } - - refresh(input?: IModel, autoFocus?: IAutoFocus): void { - if (!this.isVisible()) { - return; - } - - if (!input) { - input = this.tree.getInput(); - } - - if (!input) { - return; - } - - // Apply height & Refresh - this.treeContainer.style.height = `${this.getHeight(input)}px`; - this.tree.refresh().then(() => { - - // Indicate entries to tree - this.tree.layout(); - - const entries = input ? input.entries!.filter(e => this.isElementVisible(input!, e)) : []; - this.updateResultCount(entries.length); - - // Handle auto focus - if (autoFocus) { - if (entries.length) { - this.autoFocus(input!, entries, autoFocus); - } - } - }); - } - - private getHeight(input: IModel): number { - const renderer = input.renderer; - - if (!input) { - const itemHeight = renderer.getHeight(null); - - return this.options.minItemsToShow ? this.options.minItemsToShow * itemHeight : 0; - } - - let height = 0; - - let preferredItemsHeight: number | undefined; - if (this.layoutDimensions && this.layoutDimensions.height) { - preferredItemsHeight = (this.layoutDimensions.height - 50 /* subtract height of input field (30px) and some spacing (drop shadow) to fit */) * 0.4 /* max 40% of screen */; - } - - if (!preferredItemsHeight || preferredItemsHeight > QuickOpenWidget.MAX_ITEMS_HEIGHT) { - preferredItemsHeight = QuickOpenWidget.MAX_ITEMS_HEIGHT; - } - - const entries = input.entries.filter(e => this.isElementVisible(input, e)); - const maxEntries = this.options.maxItemsToShow || entries.length; - for (let i = 0; i < maxEntries && i < entries.length; i++) { - const entryHeight = renderer.getHeight(entries[i]); - if (height + entryHeight <= preferredItemsHeight) { - height += entryHeight; - } else { - break; - } - } - - return height; - } - - updateResultCount(count: number) { - this.resultCount.textContent = nls.localize({ key: 'quickInput.visibleCount', comment: ['This tells the user how many items are shown in a list of items to select from. The items can be anything. Currently not visible, but read by screen readers.'] }, "{0} Results", count); - } - - hide(reason?: HideReason): void { - if (!this.isVisible()) { - return; - } - - this.visible = false; - DOM.hide(this.element); - this.element.blur(); - - // Clear input field and clear tree - this.inputBox.value = ''; - this.tree.setInput(null); - - // ARIA - this.inputElement.setAttribute('aria-haspopup', 'false'); - - // Reset Tree Height - this.treeContainer.style.height = `${this.options.minItemsToShow ? this.options.minItemsToShow * 22 : 0}px`; - - // Clear any running Progress - this.progressBar.stop().hide(); - - // Clear Focus - if (this.tree.isDOMFocused()) { - this.tree.domBlur(); - } else if (this.inputBox.hasFocus()) { - this.inputBox.blur(); - } - - // Callbacks - if (reason === HideReason.ELEMENT_SELECTED) { - this.callbacks.onOk(); - } else { - this.callbacks.onCancel(); - } - - if (this.callbacks.onHide) { - this.callbacks.onHide(reason!); - } - } - - getQuickNavigateConfiguration(): IQuickNavigateConfiguration { - return this.quickNavigateConfiguration!; - } - - setPlaceHolder(placeHolder: string): void { - if (this.inputBox) { - this.inputBox.setPlaceHolder(placeHolder); - } - } - - setValue(value: string, selectionOrStableHint?: [number, number] | null): void { - if (this.inputBox) { - this.inputBox.value = value; - if (selectionOrStableHint === null) { - // null means stable-selection - } else if (Array.isArray(selectionOrStableHint)) { - const [start, end] = selectionOrStableHint; - this.inputBox.select({ start, end }); - } else { - this.inputBox.select(); - } - } - } - - setPassword(isPassword: boolean): void { - if (this.inputBox) { - this.inputBox.inputElement.type = isPassword ? 'password' : 'text'; - } - } - - setInput(input: IModel, autoFocus?: IAutoFocus, ariaLabel?: string): void { - if (!this.isVisible()) { - return; - } - - // If the input changes, indicate this to the tree - if (!!this.getInput()) { - this.onInputChanging(); - } - - // Adapt tree height to entries and apply input - this.setInputAndLayout(input, autoFocus); - - // Apply ARIA - if (this.inputBox) { - this.inputBox.setAriaLabel(ariaLabel || DEFAULT_INPUT_ARIA_LABEL); - } - } - - private onInputChanging(): void { - if (this.inputChangingTimeoutHandle) { - clearTimeout(this.inputChangingTimeoutHandle); - this.inputChangingTimeoutHandle = null; - } - - // when the input is changing in quick open, we indicate this as CSS class to the widget - // for a certain timeout. this helps reducing some hectic UI updates when input changes quickly - DOM.addClass(this.element, 'content-changing'); - this.inputChangingTimeoutHandle = setTimeout(() => { - DOM.removeClass(this.element, 'content-changing'); - }, 500); - } - - getInput(): IModel { - return this.tree.getInput(); - } - - showInputDecoration(decoration: Severity): void { - if (this.inputBox) { - this.inputBox.showMessage({ type: decoration === Severity.Info ? MessageType.INFO : decoration === Severity.Warning ? MessageType.WARNING : MessageType.ERROR, content: '' }); - } - } - - clearInputDecoration(): void { - if (this.inputBox) { - this.inputBox.hideMessage(); - } - } - - focus(): void { - if (this.isVisible() && this.inputBox) { - this.inputBox.focus(); - } - } - - accept(): void { - if (this.isVisible()) { - const focus = this.tree.getFocus(); - if (focus) { - this.elementSelected(focus); - } - } - } - - getProgressBar(): ProgressBar { - return this.progressBar; - } - - getInputBox(): InputBox { - return this.inputBox; - } - - setExtraClass(clazz: string | null): void { - const previousClass = this.element.getAttribute('quick-open-extra-class'); - if (previousClass) { - DOM.removeClasses(this.element, previousClass); - } - - if (clazz) { - DOM.addClasses(this.element, clazz); - this.element.setAttribute('quick-open-extra-class', clazz); - } else if (previousClass) { - this.element.removeAttribute('quick-open-extra-class'); - } - } - - isVisible(): boolean { - return this.visible; - } - - layout(dimension: DOM.Dimension): void { - this.layoutDimensions = dimension; - - // Apply to quick open width (height is dynamic by number of items to show) - const quickOpenWidth = Math.min(this.layoutDimensions.width * 0.62 /* golden cut */, QuickOpenWidget.MAX_WIDTH); - if (this.element) { - - // quick open - this.element.style.width = `${quickOpenWidth}px`; - this.element.style.marginLeft = `-${quickOpenWidth / 2}px`; - - // input field - this.inputContainer.style.width = `${quickOpenWidth - 12}px`; - } - } - - private gainingFocus(): void { - this.isLoosingFocus = false; - } - - private loosingFocus(e: FocusEvent): void { - if (!this.isVisible()) { - return; - } - - const relatedTarget = e.relatedTarget as HTMLElement; - if (!this.quickNavigateConfiguration && DOM.isAncestor(relatedTarget, this.element)) { - return; // user clicked somewhere into quick open widget, do not close thereby - } - - this.isLoosingFocus = true; - setTimeout(() => { - if (!this.isLoosingFocus || this.isDisposed) { - return; - } - - const veto = this.callbacks.onFocusLost && this.callbacks.onFocusLost(); - if (!veto) { - this.hide(HideReason.FOCUS_LOST); - } - }, 0); - } - - dispose(): void { - super.dispose(); - - this.isDisposed = true; - } -} diff --git a/src/vs/base/parts/quickopen/browser/quickopen.css b/src/vs/base/parts/quickopen/browser/quickopen.css deleted file mode 100644 index 4ecafadcfd3..00000000000 --- a/src/vs/base/parts/quickopen/browser/quickopen.css +++ /dev/null @@ -1,169 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-quick-open-widget { - position: absolute; - width: 600px; - z-index: 2000; - padding-bottom: 6px; - left: 50%; - margin-left: -300px; -} - -.monaco-quick-open-widget .monaco-progress-container { - position: absolute; - left: 0; - top: 38px; - z-index: 1; - height: 2px; -} - -.monaco-quick-open-widget .monaco-progress-container .progress-bit { - height: 2px; -} - -.monaco-quick-open-widget .quick-open-input { - width: 588px; - border: none; - margin: 6px; -} - -.monaco-quick-open-widget .quick-open-input .monaco-inputbox { - width: 100%; - height: 25px; -} - -.monaco-quick-open-widget .quick-open-result-count { - position: absolute; - left: -10000px; -} - -.monaco-quick-open-widget .quick-open-tree { - line-height: 22px; -} - -.monaco-quick-open-widget .quick-open-tree .monaco-tree-row > .content > .sub-content { - overflow: hidden; -} - -.monaco-quick-open-widget.content-changing .quick-open-tree .monaco-scrollable-element .slider { - display: none; /* scrollbar slider causes some hectic updates when input changes quickly, so hide it while quick open changes */ -} - -.monaco-quick-open-widget .quick-open-tree .quick-open-entry { - overflow: hidden; - text-overflow: ellipsis; - display: flex; - flex-direction: column; - height: 100%; -} - -.monaco-quick-open-widget .quick-open-tree .quick-open-entry > .quick-open-row { - display: flex; - align-items: center; -} - -.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon { - overflow: hidden; - width: 16px; - height: 16px; - margin-right: 4px; - display: flex; - align-items: center; - vertical-align: middle; - flex-shrink: 0; -} - -.monaco-quick-open-widget .quick-open-tree .monaco-icon-label, -.monaco-quick-open-widget .quick-open-tree .monaco-icon-label .monaco-icon-label-container > .monaco-icon-name-container { - flex: 1; /* make sure the icon label grows within the row */ -} - -.monaco-quick-open-widget .quick-open-tree .quick-open-entry .monaco-highlighted-label span { - opacity: 1; -} - -.monaco-quick-open-widget .quick-open-tree .quick-open-entry .monaco-highlighted-label .codicon { - vertical-align: sub; /* vertically align codicon */ -} - -.monaco-quick-open-widget .quick-open-tree .quick-open-entry-meta { - opacity: 0.7; - line-height: normal; -} - -.monaco-quick-open-widget .quick-open-tree .content.has-group-label .quick-open-entry-keybinding { - margin-right: 8px; -} - -.monaco-quick-open-widget .quick-open-tree .quick-open-entry-keybinding .monaco-keybinding-key { - vertical-align: text-bottom; -} - -.monaco-quick-open-widget .quick-open-tree .results-group { - margin-right: 18px; -} - -.monaco-quick-open-widget .quick-open-tree .monaco-tree-row.focused > .content.has-actions > .results-group, -.monaco-quick-open-widget .quick-open-tree .monaco-tree-row:hover:not(.highlighted) > .content.has-actions > .results-group, -.monaco-quick-open-widget .quick-open-tree .focused .monaco-tree-row.focused > .content.has-actions > .results-group { - margin-right: 0px; -} - -.monaco-quick-open-widget .quick-open-tree .results-group-separator { - border-top-width: 1px; - border-top-style: solid; - box-sizing: border-box; - margin-left: -11px; - padding-left: 11px; -} - -/* Actions in Quick Open Items */ - -.monaco-tree .monaco-tree-row > .content.actions { - position: relative; - display: flex; -} - -.monaco-tree .monaco-tree-row > .content.actions > .sub-content { - flex: 1; -} - -.monaco-tree .monaco-tree-row > .content.actions .action-item { - margin: 0; -} - -.monaco-tree .monaco-tree-row > .content.actions > .primary-action-bar { - line-height: 22px; -} - -.monaco-tree .monaco-tree-row > .content.actions > .primary-action-bar { - display: none; - padding: 0 0.8em 0 0.4em; -} - -.monaco-tree .monaco-tree-row.focused > .content.has-actions > .primary-action-bar { - width: 0; /* in order to support a11y with keyboard, we use width: 0 to hide the actions, which still allows to "Tab" into the actions */ - display: block; -} - -.monaco-tree .monaco-tree-row:hover:not(.highlighted) > .content.has-actions > .primary-action-bar, -.monaco-tree.focused .monaco-tree-row.focused > .content.has-actions > .primary-action-bar, -.monaco-tree .monaco-tree-row > .content.has-actions.more > .primary-action-bar { - width: inherit; - display: block; -} - -.monaco-tree .monaco-tree-row > .content.actions > .primary-action-bar .action-label { - margin-right: 0.4em; - margin-top: 4px; - background-repeat: no-repeat; - width: 16px; - height: 16px; -} - -.monaco-quick-open-widget .quick-open-tree .monaco-highlighted-label .highlight { - font-weight: bold; -} diff --git a/src/vs/base/parts/quickopen/common/quickOpen.ts b/src/vs/base/parts/quickopen/common/quickOpen.ts deleted file mode 100644 index 582ddf56ee6..00000000000 --- a/src/vs/base/parts/quickopen/common/quickOpen.ts +++ /dev/null @@ -1,95 +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 { ResolvedKeybinding } from 'vs/base/common/keyCodes'; - -export interface IQuickNavigateConfiguration { - keybindings: ResolvedKeybinding[]; -} - -export interface IAutoFocus { - - /** - * The index of the element to focus in the result list. - */ - autoFocusIndex?: number; - - /** - * If set to true, will automatically select the first entry from the result list. - */ - autoFocusFirstEntry?: boolean; - - /** - * If set to true, will automatically select the second entry from the result list. - */ - autoFocusSecondEntry?: boolean; - - /** - * If set to true, will automatically select the last entry from the result list. - */ - autoFocusLastEntry?: boolean; - - /** - * If set to true, will automatically select any entry whose label starts with the search - * value. Since some entries to the top might match the query but not on the prefix, this - * allows to select the most accurate match (matching the prefix) while still showing other - * elements. - */ - autoFocusPrefixMatch?: string; -} - -export const enum Mode { - PREVIEW, - OPEN, - OPEN_IN_BACKGROUND -} - -export interface IEntryRunContext { - event: any; - keymods: IKeyMods; - quickNavigateConfiguration: IQuickNavigateConfiguration | undefined; -} - -export interface IKeyMods { - ctrlCmd: boolean; - alt: boolean; -} - -export interface IDataSource { - getId(entry: T): string; - getLabel(entry: T): string | null; -} - -/** - * See vs/base/parts/tree/browser/tree.ts - IRenderer - */ -export interface IRenderer { - getHeight(entry: T): number; - getTemplateId(entry: T): string; - renderTemplate(templateId: string, container: any /* HTMLElement */, styles: any): any; - renderElement(entry: T, templateId: string, templateData: any, styles: any): void; - disposeTemplate(templateId: string, templateData: any): void; -} - -export interface IFilter { - isVisible(entry: T): boolean; -} - -export interface IAccessiblityProvider { - getAriaLabel(entry: T): string; -} - -export interface IRunner { - run(entry: T, mode: Mode, context: IEntryRunContext): boolean; -} - -export interface IModel { - entries: T[]; - dataSource: IDataSource; - renderer: IRenderer; - runner: IRunner; - filter?: IFilter; - accessibilityProvider?: IAccessiblityProvider; -} diff --git a/src/vs/base/parts/quickopen/test/browser/quickopen.test.ts b/src/vs/base/parts/quickopen/test/browser/quickopen.test.ts deleted file mode 100644 index f857c0b64a7..00000000000 --- a/src/vs/base/parts/quickopen/test/browser/quickopen.test.ts +++ /dev/null @@ -1,49 +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 { QuickOpenModel, QuickOpenEntry, QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { DataSource } from 'vs/base/parts/quickopen/browser/quickOpenViewer'; - -suite('QuickOpen', () => { - test('QuickOpenModel', () => { - const model = new QuickOpenModel(); - - const entry1 = new QuickOpenEntry(); - const entry2 = new QuickOpenEntry(); - const entry3 = new QuickOpenEntryGroup(); - - assert.notEqual(entry1.getId(), entry2.getId()); - assert.notEqual(entry2.getId(), entry3.getId()); - - model.addEntries([entry1, entry2, entry3]); - assert.equal(3, model.getEntries().length); - - model.setEntries([entry1, entry2]); - assert.equal(2, model.getEntries().length); - - entry1.setHidden(true); - assert.equal(1, model.getEntries(true).length); - assert.equal(entry2, model.getEntries(true)[0]); - }); - - test('QuickOpenDataSource', async () => { - const model = new QuickOpenModel(); - - const entry1 = new QuickOpenEntry(); - const entry2 = new QuickOpenEntry(); - const entry3 = new QuickOpenEntryGroup(); - - model.addEntries([entry1, entry2, entry3]); - - const ds = new DataSource(model); - assert.equal(entry1.getId(), ds.getId(null!, entry1)); - assert.equal(true, ds.hasChildren(null!, model)); - assert.equal(false, ds.hasChildren(null!, entry1)); - - const children = await ds.getChildren(null!, model); - assert.equal(3, children.length); - }); -}); diff --git a/src/vs/base/parts/storage/common/storage.ts b/src/vs/base/parts/storage/common/storage.ts index 2c2c909a9d1..b6602b6a626 100644 --- a/src/vs/base/parts/storage/common/storage.ts +++ b/src/vs/base/parts/storage/common/storage.ts @@ -18,17 +18,17 @@ export enum StorageHint { } export interface IStorageOptions { - hint?: StorageHint; + readonly hint?: StorageHint; } export interface IUpdateRequest { - insert?: Map; - delete?: Set; + readonly insert?: Map; + readonly delete?: Set; } export interface IStorageItemsChangeEvent { - changed?: Map; - deleted?: Set; + readonly changed?: Map; + readonly deleted?: Set; } export interface IStorageDatabase { @@ -74,26 +74,24 @@ export class Storage extends Disposable implements IStorage { private static readonly DEFAULT_FLUSH_DELAY = 100; - private readonly _onDidChangeStorage: Emitter = this._register(new Emitter()); - readonly onDidChangeStorage: Event = this._onDidChangeStorage.event; + private readonly _onDidChangeStorage = this._register(new Emitter()); + readonly onDidChangeStorage = this._onDidChangeStorage.event; private state = StorageState.None; - private cache: Map = new Map(); + private cache = new Map(); - private flushDelayer: ThrottledDelayer; + private readonly flushDelayer = this._register(new ThrottledDelayer(Storage.DEFAULT_FLUSH_DELAY)); - private pendingDeletes: Set = new Set(); - private pendingInserts: Map = new Map(); + private pendingDeletes = new Set(); + private pendingInserts = new Map(); constructor( - protected database: IStorageDatabase, - private options: IStorageOptions = Object.create(null) + protected readonly database: IStorageDatabase, + private readonly options: IStorageOptions = Object.create(null) ) { super(); - this.flushDelayer = this._register(new ThrottledDelayer(Storage.DEFAULT_FLUSH_DELAY)); - this.registerListeners(); } @@ -146,7 +144,7 @@ export class Storage extends Disposable implements IStorage { async init(): Promise { if (this.state !== StorageState.None) { - return Promise.resolve(); // either closed or already initialized + return; // either closed or already initialized } this.state = StorageState.Initialized; @@ -155,7 +153,7 @@ export class Storage extends Disposable implements IStorage { // return early if we know the storage file does not exist. this is a performance // optimization to not load all items of the underlying storage if we know that // there can be no items because the storage does not exist. - return Promise.resolve(); + return; } this.cache = await this.database.getItems(); @@ -296,13 +294,13 @@ export class InMemoryStorageDatabase implements IStorageDatabase { readonly onDidChangeItemsExternal = Event.None; - private items = new Map(); + private readonly items = new Map(); - getItems(): Promise> { - return Promise.resolve(this.items); + async getItems(): Promise> { + return this.items; } - updateItems(request: IUpdateRequest): Promise { + async updateItems(request: IUpdateRequest): Promise { if (request.insert) { request.insert.forEach((value, key) => this.items.set(key, value)); } @@ -310,11 +308,7 @@ export class InMemoryStorageDatabase implements IStorageDatabase { if (request.delete) { request.delete.forEach(key => this.items.delete(key)); } - - return Promise.resolve(); } - close(): Promise { - return Promise.resolve(); - } + async close(): Promise { } } diff --git a/src/vs/base/parts/storage/node/storage.ts b/src/vs/base/parts/storage/node/storage.ts index e73aafe6ad4..58a809a1c36 100644 --- a/src/vs/base/parts/storage/node/storage.ts +++ b/src/vs/base/parts/storage/node/storage.ts @@ -9,20 +9,18 @@ import { timeout } from 'vs/base/common/async'; import { mapToString, setToString } from 'vs/base/common/map'; import { basename } from 'vs/base/common/path'; import { copy, renameIgnoreError, unlink } from 'vs/base/node/pfs'; -import { fill } from 'vs/base/common/arrays'; import { IStorageDatabase, IStorageItemsChangeEvent, IUpdateRequest } from 'vs/base/parts/storage/common/storage'; interface IDatabaseConnection { - db: Database; - - isInMemory: boolean; + readonly db: Database; + readonly isInMemory: boolean; isErroneous?: boolean; lastError?: string; } export interface ISQLiteStorageDatabaseOptions { - logging?: ISQLiteStorageDatabaseLoggingOptions; + readonly logging?: ISQLiteStorageDatabaseLoggingOptions; } export interface ISQLiteStorageDatabaseLoggingOptions { @@ -39,21 +37,13 @@ export class SQLiteStorageDatabase implements IStorageDatabase { private static readonly BUSY_OPEN_TIMEOUT = 2000; // timeout in ms to retry when opening DB fails with SQLITE_BUSY private static readonly MAX_HOST_PARAMETERS = 256; // maximum number of parameters within a statement - private path: string; - private name: string; + private readonly name = basename(this.path); - private logger: SQLiteStorageDatabaseLogger; + private readonly logger = new SQLiteStorageDatabaseLogger(this.options.logging); - private whenConnected: Promise; + private readonly whenConnected = this.connect(this.path); - constructor(path: string, options: ISQLiteStorageDatabaseOptions = Object.create(null)) { - this.path = path; - this.name = basename(path); - - this.logger = new SQLiteStorageDatabaseLogger(options.logging); - - this.whenConnected = this.connect(path); - } + constructor(private readonly path: string, private readonly options: ISQLiteStorageDatabaseOptions = Object.create(null)) { } async getItems(): Promise> { const connection = await this.whenConnected; @@ -106,7 +96,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { }); keysValuesChunks.forEach(keysValuesChunk => { - this.prepare(connection, `INSERT INTO ItemTable VALUES ${fill(keysValuesChunk.length / 2, '(?,?)').join(',')}`, stmt => stmt.run(keysValuesChunk), () => { + this.prepare(connection, `INSERT INTO ItemTable VALUES ${new Array(keysValuesChunk.length / 2).fill('(?,?)').join(',')}`, stmt => stmt.run(keysValuesChunk), () => { const keys: string[] = []; let length = 0; toInsert.forEach((value, key) => { @@ -141,7 +131,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { }); keysChunks.forEach(keysChunk => { - this.prepare(connection, `DELETE FROM ItemTable WHERE key IN (${fill(keysChunk.length, '?').join(',')})`, stmt => stmt.run(keysChunk), () => { + this.prepare(connection, `DELETE FROM ItemTable WHERE key IN (${new Array(keysChunk.length).fill('?').join(',')})`, stmt => stmt.run(keysChunk), () => { const keys: string[] = []; toDelete.forEach(key => { keys.push(key); @@ -166,7 +156,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { return new Promise((resolve, reject) => { connection.db.close(closeError => { if (closeError) { - this.handleSQLiteError(connection, closeError, `[storage ${this.name}] close(): ${closeError}`); + this.handleSQLiteError(connection, `[storage ${this.name}] close(): ${closeError}`); } // Return early if this storage was created only in-memory @@ -296,7 +286,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { } } - private handleSQLiteError(connection: IDatabaseConnection, error: Error & { code?: string }, msg: string): void { + private handleSQLiteError(connection: IDatabaseConnection, msg: string): void { connection.isErroneous = true; connection.lastError = msg; @@ -328,7 +318,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { }; // Errors - connection.db.on('error', error => this.handleSQLiteError(connection, error, `[storage ${this.name}] Error (event): ${error}`)); + connection.db.on('error', error => this.handleSQLiteError(connection, `[storage ${this.name}] Error (event): ${error}`)); // Tracing if (this.logger.isTracing) { @@ -342,7 +332,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { return new Promise((resolve, reject) => { connection.db.exec(sql, error => { if (error) { - this.handleSQLiteError(connection, error, `[storage ${this.name}] exec(): ${error}`); + this.handleSQLiteError(connection, `[storage ${this.name}] exec(): ${error}`); return reject(error); } @@ -356,7 +346,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { return new Promise((resolve, reject) => { connection.db.get(sql, (error, row) => { if (error) { - this.handleSQLiteError(connection, error, `[storage ${this.name}] get(): ${error}`); + this.handleSQLiteError(connection, `[storage ${this.name}] get(): ${error}`); return reject(error); } @@ -370,7 +360,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { return new Promise((resolve, reject) => { connection.db.all(sql, (error, rows) => { if (error) { - this.handleSQLiteError(connection, error, `[storage ${this.name}] all(): ${error}`); + this.handleSQLiteError(connection, `[storage ${this.name}] all(): ${error}`); return reject(error); } @@ -389,7 +379,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { connection.db.run('END TRANSACTION', error => { if (error) { - this.handleSQLiteError(connection, error, `[storage ${this.name}] transaction(): ${error}`); + this.handleSQLiteError(connection, `[storage ${this.name}] transaction(): ${error}`); return reject(error); } @@ -404,7 +394,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { const stmt = connection.db.prepare(sql); const statementErrorListener = (error: Error) => { - this.handleSQLiteError(connection, error, `[storage ${this.name}] prepare(): ${error} (${sql}). Details: ${errorDetails()}`); + this.handleSQLiteError(connection, `[storage ${this.name}] prepare(): ${error} (${sql}). Details: ${errorDetails()}`); }; stmt.on('error', statementErrorListener); diff --git a/src/vs/base/parts/tree/browser/tree.css b/src/vs/base/parts/tree/browser/tree.css deleted file mode 100644 index 3e709334db6..00000000000 --- a/src/vs/base/parts/tree/browser/tree.css +++ /dev/null @@ -1,61 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -.monaco-tree { - height: 100%; - width: 100%; - white-space: nowrap; - user-select: none; - -webkit-user-select: none; - -ms-user-select: none; - position: relative; -} - -.monaco-tree > .monaco-scrollable-element { - height: 100%; -} - -.monaco-tree > .monaco-scrollable-element > .monaco-tree-wrapper { - height: 100%; - width: 100%; - position: relative; -} - -.monaco-tree .monaco-tree-rows { - position: absolute; - width: 100%; - height: 100%; -} - -.monaco-tree .monaco-tree-rows > .monaco-tree-row { - box-sizing: border-box; - cursor: pointer; - overflow: hidden; - width: 100%; - touch-action: none; -} - -.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content { - position: relative; - height: 100%; -} - -.monaco-tree-drag-image { - display: inline-block; - padding: 1px 7px; - border-radius: 10px; - font-size: 12px; - position: absolute; -} - -/* for OS X ballistic scrolling */ -.monaco-tree .monaco-tree-rows > .monaco-tree-row.scrolling { - display: none; -} - -/* Highlighted */ - -.monaco-tree.highlighted .monaco-tree-rows > .monaco-tree-row:not(.highlighted) { - opacity: 0.3; -} diff --git a/src/vs/base/parts/tree/browser/tree.ts b/src/vs/base/parts/tree/browser/tree.ts deleted file mode 100644 index 82b4015e6dd..00000000000 --- a/src/vs/base/parts/tree/browser/tree.ts +++ /dev/null @@ -1,582 +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 Touch from 'vs/base/browser/touch'; -import * as Mouse from 'vs/base/browser/mouseEvent'; -import * as Keyboard from 'vs/base/browser/keyboardEvent'; -import { INavigator } from 'vs/base/common/iterator'; -import { ScrollbarVisibility } from 'vs/base/common/scrollable'; -import { Event } from 'vs/base/common/event'; -import { IAction } from 'vs/base/common/actions'; -import { Color } from 'vs/base/common/color'; -import { IItemCollapseEvent, IItemExpandEvent } from 'vs/base/parts/tree/browser/treeModel'; -import { IDragAndDropData } from 'vs/base/browser/dnd'; - -export interface ITree { - - onDidFocus: Event; - onDidBlur: Event; - onDidChangeFocus: Event; - onDidChangeSelection: Event; - onDidChangeHighlight: Event; - onDidExpandItem: Event; - onDidCollapseItem: Event; - onDidDispose: Event; - onDidScroll: Event; - - /** - * Returns the tree's DOM element. - */ - getHTMLElement(): HTMLElement; - - /** - * Lays out the tree. - * Provide a specific height to save an (expensive) height computation. - */ - layout(height?: number): void; - - /** - * Notifies the tree that is has become visible. - */ - onVisible(): void; - - /** - * Notifies the tree that is has become hidden. - */ - onHidden(): void; - - /** - * Sets the input of the tree. - */ - setInput(element: any): Promise; - - /** - * Returns the tree's input. - */ - getInput(): any; - - /** - * Sets DOM focus on the tree. - */ - domFocus(): void; - - /** - * Returns whether the tree has DOM focus. - */ - isDOMFocused(): boolean; - - /** - * Removes DOM focus from the tree. - */ - domBlur(): void; - - /** - * Refreshes an element. - * Provide no arguments and it will refresh the input element. - */ - refresh(element?: any, recursive?: boolean): Promise; - - /** - * Expands an element. - * The returned promise returns a boolean for whether the element was expanded or not. - */ - expand(element: any): Promise; - - /** - * Expands several elements. - * The returned promise returns a boolean array for whether the elements were expanded or not. - */ - expandAll(elements?: any[]): Promise; - - /** - * Collapses an element. - * The returned promise returns a boolean for whether the element was collapsed or not. - */ - collapse(element: any, recursive?: boolean): Promise; - - /** - * Collapses several elements. - * Provide no arguments and it will recursively collapse all elements in the tree - * The returned promise returns a boolean for whether the elements were collapsed or not. - */ - collapseAll(elements?: any[], recursive?: boolean): Promise; - - /** - * Toggles an element's expansion state. - */ - toggleExpansion(element: any, recursive?: boolean): Promise; - - /** - * Returns whether an element is expanded or not. - */ - isExpanded(element: any): boolean; - - /** - * Reveals an element in the tree. The relativeTop is a value between 0 and 1. The closer to 0 the more the - * element will scroll up to the top. - */ - reveal(element: any, relativeTop?: number): Promise; - - /** - * Returns the currently highlighted element. - */ - getHighlight(includeHidden?: boolean): any; - - /** - * Clears the highlight. - */ - clearHighlight(eventPayload?: any): void; - - /** - * Replaces the current selection with the given elements. - */ - setSelection(elements: any[], eventPayload?: any): void; - - /** - * Returns the currently selected elements. - */ - getSelection(includeHidden?: boolean): any[]; - - /** - * Clears the selection. - */ - clearSelection(eventPayload?: any): void; - - /** - * Sets the focused element. - */ - setFocus(element?: any, eventPayload?: any): void; - - /** - * Returns focused element. - */ - getFocus(includeHidden?: boolean): any; - - /** - * Focuses the next `count`-nth element, in visible order. - */ - focusNext(count?: number, eventPayload?: any): void; - - /** - * Focuses the previous `count`-nth element, in visible order. - */ - focusPrevious(count?: number, eventPayload?: any): void; - - /** - * Focuses the currently focused element's parent. - */ - focusParent(eventPayload?: any): void; - - /** - * Focuses the first child of the currently focused element. - */ - focusFirstChild(eventPayload?: any): void; - - /** - * Focuses the second element, in visible order. Will focus the first - * child from the provided element's parent if any. - */ - focusFirst(eventPayload?: any, from?: any): void; - - /** - * Focuses the nth element, in visible order. - */ - focusNth(index: number, eventPayload?: any): void; - - /** - * Focuses the last element, in visible order. Will focus the last - * child from the provided element's parent if any. - */ - focusLast(eventPayload?: any, from?: any): void; - - /** - * Focuses the element at the end of the next page, in visible order. - */ - focusNextPage(eventPayload?: any): void; - - /** - * Focuses the element at the beginning of the previous page, in visible order. - */ - focusPreviousPage(eventPayload?: any): void; - - /** - * Clears the focus. - */ - clearFocus(eventPayload?: any): void; - - /** - * Returns a navigator which allows to discover the visible and - * expanded elements in the tree. - */ - getNavigator(fromElement?: any, subTreeOnly?: boolean): INavigator; - - /** - * Apply styles to the tree. - */ - style(styles: ITreeStyles): void; - - /** - * Disposes the tree - */ - dispose(): void; -} - -export interface IDataSource { - - /** - * Returns the unique identifier of the given element. - * No more than one element may use a given identifier. - * - * You should not attempt to "move" an element to a different - * parent by keeping its ID. The idea here is to have tree location - * related IDs (e.g. full file path, in the Explorer example). - */ - getId(tree: ITree, element: any): string; - - /** - * Returns a boolean value indicating whether the element has children. - */ - hasChildren(tree: ITree, element: any): boolean; - - /** - * Returns the element's children as an array in a promise. - */ - getChildren(tree: ITree, element: any): Promise; - - /** - * Returns the element's parent in a promise. - */ - getParent(tree: ITree, element: any): Promise; - - /** - * Returns whether an element should be expanded when first added to the tree. - */ - shouldAutoexpand?(tree: ITree, element: any): boolean; -} - -export interface IRenderer { - - /** - * Returns the element's height in the tree, in pixels. - */ - getHeight(tree: ITree, element: any): number; - - /** - * Returns a template ID for a given element. This will be used as an identifier - * for the next 3 methods. - */ - getTemplateId(tree: ITree, element: any): string; - - /** - * Renders the template in a DOM element. This method should render all the DOM - * structure for an hypothetical element leaving its contents blank. It should - * return an object bag which will be passed along to `renderElement` and used - * to fill in those blanks. - * - * You should do all DOM creating and object allocation in this method. It - * will be called only a few times. - */ - renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any; - - /** - * Renders an element, given an object bag returned by `renderTemplate`. - * This method should do as little as possible and ideally it should only fill - * in the blanks left by `renderTemplate`. - * - * Try to make this method do as little possible, since it will be called very - * often. - */ - renderElement(tree: ITree, element: any, templateId: string, templateData: any): void; - - /** - * Disposes a template that was once rendered. - */ - disposeTemplate(tree: ITree, templateId: string, templateData: any): void; -} - -export interface IAccessibilityProvider { - - /** - * Given an element in the tree, return the ARIA label that should be associated with the - * item. This helps screen readers to provide a meaningful label for the currently focused - * tree element. - * - * Returning null will not disable ARIA for the element. Instead it is up to the screen reader - * to compute a meaningful label based on the contents of the element in the DOM - * - * See also: https://www.w3.org/TR/wai-aria/states_and_properties#aria-label - */ - getAriaLabel(tree: ITree, element: any): string | null; - - /** - * Given an element in the tree return its aria-posinset. Should be between 1 and aria-setsize - * https://www.w3.org/TR/wai-aria/states_and_properties#aria-posinset - */ - getPosInSet?(tree: ITree, element: any): string; - - /** - * Return the aria-setsize of the tree. - * https://www.w3.org/TR/wai-aria/states_and_properties#aria-setsize - */ - getSetSize?(): string; -} - -export /* abstract */ class ContextMenuEvent { - - private _posx: number; - private _posy: number; - private _target: HTMLElement; - - constructor(posx: number, posy: number, target: HTMLElement) { - this._posx = posx; - this._posy = posy; - this._target = target; - } - - public preventDefault(): void { - // no-op - } - - public stopPropagation(): void { - // no-op - } - - public get posx(): number { - return this._posx; - } - - public get posy(): number { - return this._posy; - } - - public get target(): HTMLElement { - return this._target; - } -} - -export class MouseContextMenuEvent extends ContextMenuEvent { - - private originalEvent: Mouse.IMouseEvent; - - constructor(originalEvent: Mouse.IMouseEvent) { - super(originalEvent.posx, originalEvent.posy, originalEvent.target); - this.originalEvent = originalEvent; - } - - public preventDefault(): void { - this.originalEvent.preventDefault(); - } - - public stopPropagation(): void { - this.originalEvent.stopPropagation(); - } -} - -export class KeyboardContextMenuEvent extends ContextMenuEvent { - - private originalEvent: Keyboard.IKeyboardEvent; - - constructor(posx: number, posy: number, originalEvent: Keyboard.IKeyboardEvent) { - super(posx, posy, originalEvent.target); - this.originalEvent = originalEvent; - } - - public preventDefault(): void { - this.originalEvent.preventDefault(); - } - - public stopPropagation(): void { - this.originalEvent.stopPropagation(); - } -} - -export interface IController { - - /** - * Called when an element is clicked. - */ - onClick(tree: ITree, element: any, event: Mouse.IMouseEvent): boolean; - - /** - * Called when an element is requested for a context menu. - */ - onContextMenu(tree: ITree, element: any, event: ContextMenuEvent): boolean; - - /** - * Called when an element is tapped. - */ - onTap(tree: ITree, element: any, event: Touch.GestureEvent): boolean; - - /** - * Called when a key is pressed down while selecting elements. - */ - onKeyDown(tree: ITree, event: Keyboard.IKeyboardEvent): boolean; - - /** - * Called when a key is released while selecting elements. - */ - onKeyUp(tree: ITree, event: Keyboard.IKeyboardEvent): boolean; - - /** - * Called when a mouse middle button is pressed down on an element. - */ - onMouseMiddleClick?(tree: ITree, element: any, event: Mouse.IMouseEvent): boolean; - - /** - * Called when a mouse button is pressed down on an element. - */ - onMouseDown?(tree: ITree, element: any, event: Mouse.IMouseEvent): boolean; - - /** - * Called when a mouse button goes up on an element. - */ - onMouseUp?(tree: ITree, element: any, event: Mouse.IMouseEvent): boolean; -} - -export const enum DragOverEffect { - COPY, - MOVE -} - -export const enum DragOverBubble { - BUBBLE_DOWN, - BUBBLE_UP -} - -export interface IDragOverReaction { - accept: boolean; - effect?: DragOverEffect; - bubble?: DragOverBubble; - autoExpand?: boolean; -} - -export interface IDragAndDrop { - - /** - * Returns a uri if the given element should be allowed to drag. - * Returns null, otherwise. - */ - getDragURI(tree: ITree, element: any): string | null; - - /** - * Returns a label to display when dragging the element. - */ - getDragLabel?(tree: ITree, elements: any[]): string; - - /** - * Sent when the drag operation is starting. - */ - onDragStart(tree: ITree, data: IDragAndDropData, originalEvent: Mouse.DragMouseEvent): void; - - /** - * Returns a DragOverReaction indicating whether sources can be - * dropped into target or some parent of the target. - */ - onDragOver(tree: ITree, data: IDragAndDropData, targetElement: any, originalEvent: Mouse.DragMouseEvent): IDragOverReaction | null; - - /** - * Handles the action of dropping sources into target. - */ - drop(tree: ITree, data: IDragAndDropData, targetElement: any, originalEvent: Mouse.DragMouseEvent): void; -} - -export interface IFilter { - - /** - * Returns whether the given element should be visible. - */ - isVisible(tree: ITree, element: any): boolean; -} - -export interface ISorter { - - /** - * Compare two elements in the viewer to define the sorting order. - */ - compare(tree: ITree, element: any, otherElement: any): number; -} - -// Events - -export interface ISelectionEvent { - selection: any[]; - payload?: any; -} - -export interface IFocusEvent { - focus: any; - payload?: any; -} - -export interface IHighlightEvent { - highlight: any; - payload?: any; -} - -// Options - -export interface ITreeConfiguration { - dataSource: IDataSource; - renderer?: IRenderer; - controller?: IController; - dnd?: IDragAndDrop; - filter?: IFilter; - sorter?: ISorter; - accessibilityProvider?: IAccessibilityProvider; - styler?: ITreeStyler; -} - -export interface ITreeOptions extends ITreeStyles { - twistiePixels?: number; - showTwistie?: boolean; - indentPixels?: number; - verticalScrollMode?: ScrollbarVisibility; - horizontalScrollMode?: ScrollbarVisibility; - alwaysFocused?: boolean; - autoExpandSingleChildren?: boolean; - useShadows?: boolean; - paddingOnRow?: boolean; - ariaLabel?: string; - keyboardSupport?: boolean; - preventRootFocus?: boolean; - showLoading?: boolean; -} - -export interface ITreeStyler { - style(styles: ITreeStyles): void; -} - -export interface ITreeStyles { - listFocusBackground?: Color; - listFocusForeground?: Color; - listActiveSelectionBackground?: Color; - listActiveSelectionForeground?: Color; - listFocusAndSelectionBackground?: Color; - listFocusAndSelectionForeground?: Color; - listInactiveSelectionBackground?: Color; - listInactiveSelectionForeground?: Color; - listHoverBackground?: Color; - listHoverForeground?: Color; - listDropBackground?: Color; - listFocusOutline?: Color; -} - -export interface ITreeContext extends ITreeConfiguration { - tree: ITree; - options: ITreeOptions; -} - -export interface IActionProvider { - - /** - * Returns whether or not the element has actions. These show up in place right to the element in the tree. - */ - hasActions(tree: ITree | null, element: any): boolean; - - /** - * Returns an array with the actions of the element that should show up in place right to the element in the tree. - */ - getActions(tree: ITree | null, element: any): ReadonlyArray | null; -} diff --git a/src/vs/base/parts/tree/browser/treeDefaults.ts b/src/vs/base/parts/tree/browser/treeDefaults.ts deleted file mode 100644 index f91ca2bcf84..00000000000 --- a/src/vs/base/parts/tree/browser/treeDefaults.ts +++ /dev/null @@ -1,574 +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 nls from 'vs/nls'; -import { Action } from 'vs/base/common/actions'; -import * as platform from 'vs/base/common/platform'; -import * as touch from 'vs/base/browser/touch'; -import * as errors from 'vs/base/common/errors'; -import * as dom from 'vs/base/browser/dom'; -import * as mouse from 'vs/base/browser/mouseEvent'; -import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import * as _ from 'vs/base/parts/tree/browser/tree'; -import { IDragAndDropData } from 'vs/base/browser/dnd'; -import { KeyCode, KeyMod, Keybinding, SimpleKeybinding, createKeybinding } from 'vs/base/common/keyCodes'; - -export interface IKeyBindingCallback { - (tree: _.ITree, event: IKeyboardEvent): void; -} - -export interface ICancelableEvent { - preventDefault(): void; - stopPropagation(): void; -} - -export const enum ClickBehavior { - - /** - * Handle the click when the mouse button is pressed but not released yet. - */ - ON_MOUSE_DOWN, - - /** - * Handle the click when the mouse button is released. - */ - ON_MOUSE_UP -} - -export const enum OpenMode { - SINGLE_CLICK, - DOUBLE_CLICK -} - -export interface IControllerOptions { - clickBehavior?: ClickBehavior; - openMode?: OpenMode; - keyboardSupport?: boolean; -} - -interface IKeybindingDispatcherItem { - keybinding: Keybinding | null; - callback: IKeyBindingCallback; -} - -export class KeybindingDispatcher { - - private _arr: IKeybindingDispatcherItem[]; - - constructor() { - this._arr = []; - } - - public has(keybinding: KeyCode): boolean { - let target = createKeybinding(keybinding, platform.OS); - if (target !== null) { - for (const a of this._arr) { - if (target.equals(a.keybinding)) { - return true; - } - } - } - return false; - } - - public set(keybinding: number, callback: IKeyBindingCallback) { - this._arr.push({ - keybinding: createKeybinding(keybinding, platform.OS), - callback: callback - }); - } - - public dispatch(keybinding: SimpleKeybinding): IKeyBindingCallback | null { - // Loop from the last to the first to handle overwrites - for (let i = this._arr.length - 1; i >= 0; i--) { - let item = this._arr[i]; - if (keybinding.toChord().equals(item.keybinding)) { - return item.callback; - } - } - return null; - } -} - -export class DefaultController implements _.IController { - - protected downKeyBindingDispatcher: KeybindingDispatcher; - protected upKeyBindingDispatcher: KeybindingDispatcher; - - private options: IControllerOptions; - - constructor(options: IControllerOptions = { clickBehavior: ClickBehavior.ON_MOUSE_DOWN, keyboardSupport: true, openMode: OpenMode.SINGLE_CLICK }) { - this.options = options; - - this.downKeyBindingDispatcher = new KeybindingDispatcher(); - this.upKeyBindingDispatcher = new KeybindingDispatcher(); - - if (typeof options.keyboardSupport !== 'boolean' || options.keyboardSupport) { - this.downKeyBindingDispatcher.set(KeyCode.UpArrow, (t, e) => this.onUp(t, e)); - this.downKeyBindingDispatcher.set(KeyCode.DownArrow, (t, e) => this.onDown(t, e)); - this.downKeyBindingDispatcher.set(KeyCode.LeftArrow, (t, e) => this.onLeft(t, e)); - this.downKeyBindingDispatcher.set(KeyCode.RightArrow, (t, e) => this.onRight(t, e)); - if (platform.isMacintosh) { - this.downKeyBindingDispatcher.set(KeyMod.CtrlCmd | KeyCode.UpArrow, (t, e) => this.onLeft(t, e)); - this.downKeyBindingDispatcher.set(KeyMod.WinCtrl | KeyCode.KEY_N, (t, e) => this.onDown(t, e)); - this.downKeyBindingDispatcher.set(KeyMod.WinCtrl | KeyCode.KEY_P, (t, e) => this.onUp(t, e)); - } - this.downKeyBindingDispatcher.set(KeyCode.PageUp, (t, e) => this.onPageUp(t, e)); - this.downKeyBindingDispatcher.set(KeyCode.PageDown, (t, e) => this.onPageDown(t, e)); - this.downKeyBindingDispatcher.set(KeyCode.Home, (t, e) => this.onHome(t, e)); - this.downKeyBindingDispatcher.set(KeyCode.End, (t, e) => this.onEnd(t, e)); - - this.downKeyBindingDispatcher.set(KeyCode.Space, (t, e) => this.onSpace(t, e)); - this.downKeyBindingDispatcher.set(KeyCode.Escape, (t, e) => this.onEscape(t, e)); - - this.upKeyBindingDispatcher.set(KeyCode.Enter, this.onEnter.bind(this)); - this.upKeyBindingDispatcher.set(KeyMod.CtrlCmd | KeyCode.Enter, this.onEnter.bind(this)); - } - } - - public onMouseDown(tree: _.ITree, element: any, event: mouse.IMouseEvent, origin: string = 'mouse'): boolean { - if (this.options.clickBehavior === ClickBehavior.ON_MOUSE_DOWN && (event.leftButton || event.middleButton)) { - if (event.target) { - if (event.target.tagName && event.target.tagName.toLowerCase() === 'input') { - return false; // Ignore event if target is a form input field (avoids browser specific issues) - } - - if (dom.findParentWithClass(event.target, 'scrollbar', 'monaco-tree')) { - return false; - } - - if (dom.findParentWithClass(event.target, 'monaco-action-bar', 'row')) { // TODO@Joao not very nice way of checking for the action bar (implicit knowledge) - return false; // Ignore event if target is over an action bar of the row - } - } - - // Propagate to onLeftClick now - return this.onLeftClick(tree, element, event, origin); - } - - return false; - } - - public onClick(tree: _.ITree, element: any, event: mouse.IMouseEvent): boolean { - const isMac = platform.isMacintosh; - - // A Ctrl click on the Mac is a context menu event - if (isMac && event.ctrlKey) { - event.preventDefault(); - event.stopPropagation(); - return false; - } - - if (event.target && event.target.tagName && event.target.tagName.toLowerCase() === 'input') { - return false; // Ignore event if target is a form input field (avoids browser specific issues) - } - - if (this.options.clickBehavior === ClickBehavior.ON_MOUSE_DOWN && (event.leftButton || event.middleButton)) { - return false; // Already handled by onMouseDown - } - - return this.onLeftClick(tree, element, event); - } - - protected onLeftClick(tree: _.ITree, element: any, eventish: ICancelableEvent, origin: string = 'mouse'): boolean { - const event = eventish; - const payload = { origin: origin, originalEvent: eventish, didClickOnTwistie: this.isClickOnTwistie(event) }; - - if (tree.getInput() === element) { - tree.clearFocus(payload); - tree.clearSelection(payload); - } else { - const isSingleMouseDown = eventish && event.browserEvent && event.browserEvent.type === 'mousedown' && event.browserEvent.detail === 1; - if (!isSingleMouseDown) { - eventish.preventDefault(); // we cannot preventDefault onMouseDown with single click because this would break DND otherwise - } - eventish.stopPropagation(); - - tree.domFocus(); - tree.setSelection([element], payload); - tree.setFocus(element, payload); - - if (this.shouldToggleExpansion(element, event, origin)) { - if (tree.isExpanded(element)) { - tree.collapse(element).then(undefined, errors.onUnexpectedError); - } else { - tree.expand(element).then(undefined, errors.onUnexpectedError); - } - } - } - - return true; - } - - protected shouldToggleExpansion(element: any, event: mouse.IMouseEvent, origin: string): boolean { - const isDoubleClick = (origin === 'mouse' && event.detail === 2); - return this.openOnSingleClick || isDoubleClick || this.isClickOnTwistie(event); - } - - protected setOpenMode(openMode: OpenMode) { - this.options.openMode = openMode; - } - - protected get openOnSingleClick(): boolean { - return this.options.openMode === OpenMode.SINGLE_CLICK; - } - - protected isClickOnTwistie(event: mouse.IMouseEvent): boolean { - let element = event.target as HTMLElement; - - if (!dom.hasClass(element, 'content')) { - return false; - } - - const twistieStyle = window.getComputedStyle(element, ':before'); - - if (twistieStyle.backgroundImage === 'none' || twistieStyle.display === 'none') { - return false; - } - - const twistieWidth = parseInt(twistieStyle.width!) + parseInt(twistieStyle.paddingRight!); - return event.browserEvent.offsetX <= twistieWidth; - } - - public onContextMenu(tree: _.ITree, element: any, event: _.ContextMenuEvent): boolean { - if (event.target && event.target.tagName && event.target.tagName.toLowerCase() === 'input') { - return false; // allow context menu on input fields - } - - // Prevent native context menu from showing up - if (event) { - event.preventDefault(); - event.stopPropagation(); - } - - return false; - } - - public onTap(tree: _.ITree, element: any, event: touch.GestureEvent): boolean { - const target = event.initialTarget; - - if (target && target.tagName && target.tagName.toLowerCase() === 'input') { - return false; // Ignore event if target is a form input field (avoids browser specific issues) - } - - return this.onLeftClick(tree, element, event, 'touch'); - } - - public onKeyDown(tree: _.ITree, event: IKeyboardEvent): boolean { - return this.onKey(this.downKeyBindingDispatcher, tree, event); - } - - public onKeyUp(tree: _.ITree, event: IKeyboardEvent): boolean { - return this.onKey(this.upKeyBindingDispatcher, tree, event); - } - - private onKey(bindings: KeybindingDispatcher, tree: _.ITree, event: IKeyboardEvent): boolean { - const handler: any = bindings.dispatch(event.toKeybinding()); - if (handler) { - // TODO: TS 3.1 upgrade. Why are we checking against void? - if (handler(tree, event)) { - event.preventDefault(); - event.stopPropagation(); - return true; - } - } - return false; - } - - protected onUp(tree: _.ITree, event: IKeyboardEvent): boolean { - const payload = { origin: 'keyboard', originalEvent: event }; - - if (tree.getHighlight()) { - tree.clearHighlight(payload); - } else { - tree.focusPrevious(1, payload); - tree.reveal(tree.getFocus()).then(undefined, errors.onUnexpectedError); - } - return true; - } - - protected onPageUp(tree: _.ITree, event: IKeyboardEvent): boolean { - const payload = { origin: 'keyboard', originalEvent: event }; - - if (tree.getHighlight()) { - tree.clearHighlight(payload); - } else { - tree.focusPreviousPage(payload); - tree.reveal(tree.getFocus()).then(undefined, errors.onUnexpectedError); - } - return true; - } - - protected onDown(tree: _.ITree, event: IKeyboardEvent): boolean { - const payload = { origin: 'keyboard', originalEvent: event }; - - if (tree.getHighlight()) { - tree.clearHighlight(payload); - } else { - tree.focusNext(1, payload); - tree.reveal(tree.getFocus()).then(undefined, errors.onUnexpectedError); - } - return true; - } - - protected onPageDown(tree: _.ITree, event: IKeyboardEvent): boolean { - const payload = { origin: 'keyboard', originalEvent: event }; - - if (tree.getHighlight()) { - tree.clearHighlight(payload); - } else { - tree.focusNextPage(payload); - tree.reveal(tree.getFocus()).then(undefined, errors.onUnexpectedError); - } - return true; - } - - protected onHome(tree: _.ITree, event: IKeyboardEvent): boolean { - const payload = { origin: 'keyboard', originalEvent: event }; - - if (tree.getHighlight()) { - tree.clearHighlight(payload); - } else { - tree.focusFirst(payload); - tree.reveal(tree.getFocus()).then(undefined, errors.onUnexpectedError); - } - return true; - } - - protected onEnd(tree: _.ITree, event: IKeyboardEvent): boolean { - const payload = { origin: 'keyboard', originalEvent: event }; - - if (tree.getHighlight()) { - tree.clearHighlight(payload); - } else { - tree.focusLast(payload); - tree.reveal(tree.getFocus()).then(undefined, errors.onUnexpectedError); - } - return true; - } - - protected onLeft(tree: _.ITree, event: IKeyboardEvent): boolean { - const payload = { origin: 'keyboard', originalEvent: event }; - - if (tree.getHighlight()) { - tree.clearHighlight(payload); - } else { - const focus = tree.getFocus(); - tree.collapse(focus).then(didCollapse => { - if (focus && !didCollapse) { - tree.focusParent(payload); - return tree.reveal(tree.getFocus()); - } - return undefined; - }).then(undefined, errors.onUnexpectedError); - } - return true; - } - - protected onRight(tree: _.ITree, event: IKeyboardEvent): boolean { - const payload = { origin: 'keyboard', originalEvent: event }; - - if (tree.getHighlight()) { - tree.clearHighlight(payload); - } else { - const focus = tree.getFocus(); - tree.expand(focus).then(didExpand => { - if (focus && !didExpand) { - tree.focusFirstChild(payload); - return tree.reveal(tree.getFocus()); - } - return undefined; - }).then(undefined, errors.onUnexpectedError); - } - return true; - } - - protected onEnter(tree: _.ITree, event: IKeyboardEvent): boolean { - const payload = { origin: 'keyboard', originalEvent: event }; - - if (tree.getHighlight()) { - return false; - } - const focus = tree.getFocus(); - if (focus) { - tree.setSelection([focus], payload); - } - return true; - } - - protected onSpace(tree: _.ITree, event: IKeyboardEvent): boolean { - if (tree.getHighlight()) { - return false; - } - const focus = tree.getFocus(); - if (focus) { - tree.toggleExpansion(focus); - } - return true; - } - - protected onEscape(tree: _.ITree, event: IKeyboardEvent): boolean { - const payload = { origin: 'keyboard', originalEvent: event }; - - if (tree.getHighlight()) { - tree.clearHighlight(payload); - return true; - } - - if (tree.getSelection().length) { - tree.clearSelection(payload); - return true; - } - - if (tree.getFocus()) { - tree.clearFocus(payload); - return true; - } - - return false; - } -} - -export class DefaultDragAndDrop implements _.IDragAndDrop { - - public getDragURI(tree: _.ITree, element: any): string | null { - return null; - } - - public onDragStart(tree: _.ITree, data: IDragAndDropData, originalEvent: mouse.DragMouseEvent): void { - return; - } - - public onDragOver(tree: _.ITree, data: IDragAndDropData, targetElement: any, originalEvent: mouse.DragMouseEvent): _.IDragOverReaction | null { - return null; - } - - public drop(tree: _.ITree, data: IDragAndDropData, targetElement: any, originalEvent: mouse.DragMouseEvent): void { - return; - } -} - -export class DefaultFilter implements _.IFilter { - - public isVisible(tree: _.ITree, element: any): boolean { - return true; - } -} - -export class DefaultSorter implements _.ISorter { - - public compare(tree: _.ITree, element: any, otherElement: any): number { - return 0; - } -} - -export class DefaultAccessibilityProvider implements _.IAccessibilityProvider { - - getAriaLabel(tree: _.ITree, element: any): string | null { - return null; - } -} - -export class DefaultTreestyler implements _.ITreeStyler { - - constructor(private styleElement: HTMLStyleElement, private selectorSuffix?: string) { } - - style(styles: _.ITreeStyles): void { - const suffix = this.selectorSuffix ? `.${this.selectorSuffix}` : ''; - const content: string[] = []; - - if (styles.listFocusBackground) { - content.push(`.monaco-tree${suffix}.focused .monaco-tree-rows > .monaco-tree-row.focused:not(.highlighted) { background-color: ${styles.listFocusBackground}; }`); - } - - if (styles.listFocusForeground) { - content.push(`.monaco-tree${suffix}.focused .monaco-tree-rows > .monaco-tree-row.focused:not(.highlighted) { color: ${styles.listFocusForeground}; }`); - } - - if (styles.listActiveSelectionBackground) { - content.push(`.monaco-tree${suffix}.focused .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { background-color: ${styles.listActiveSelectionBackground}; }`); - } - - if (styles.listActiveSelectionForeground) { - content.push(`.monaco-tree${suffix}.focused .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { color: ${styles.listActiveSelectionForeground}; }`); - } - - if (styles.listFocusAndSelectionBackground) { - content.push(` - .monaco-tree-drag-image, - .monaco-tree${suffix}.focused .monaco-tree-rows > .monaco-tree-row.focused.selected:not(.highlighted) { background-color: ${styles.listFocusAndSelectionBackground}; } - `); - } - - if (styles.listFocusAndSelectionForeground) { - content.push(` - .monaco-tree-drag-image, - .monaco-tree${suffix}.focused .monaco-tree-rows > .monaco-tree-row.focused.selected:not(.highlighted) { color: ${styles.listFocusAndSelectionForeground}; } - `); - } - - if (styles.listInactiveSelectionBackground) { - content.push(`.monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { background-color: ${styles.listInactiveSelectionBackground}; }`); - } - - if (styles.listInactiveSelectionForeground) { - content.push(`.monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { color: ${styles.listInactiveSelectionForeground}; }`); - } - - if (styles.listHoverBackground) { - content.push(`.monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row:hover:not(.highlighted):not(.selected):not(.focused) { background-color: ${styles.listHoverBackground}; }`); - } - - if (styles.listHoverForeground) { - content.push(`.monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row:hover:not(.highlighted):not(.selected):not(.focused) { color: ${styles.listHoverForeground}; }`); - } - - if (styles.listDropBackground) { - content.push(` - .monaco-tree${suffix} .monaco-tree-wrapper.drop-target, - .monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row.drop-target { background-color: ${styles.listDropBackground} !important; color: inherit !important; } - `); - } - - if (styles.listFocusOutline) { - content.push(` - .monaco-tree-drag-image { border: 1px solid ${styles.listFocusOutline}; background: #000; } - .monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row { border: 1px solid transparent; } - .monaco-tree${suffix}.focused .monaco-tree-rows > .monaco-tree-row.focused:not(.highlighted) { border: 1px dotted ${styles.listFocusOutline}; } - .monaco-tree${suffix}.focused .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { border: 1px solid ${styles.listFocusOutline}; } - .monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { border: 1px solid ${styles.listFocusOutline}; } - .monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row:hover:not(.highlighted):not(.selected):not(.focused) { border: 1px dashed ${styles.listFocusOutline}; } - .monaco-tree${suffix} .monaco-tree-wrapper.drop-target, - .monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row.drop-target { border: 1px dashed ${styles.listFocusOutline}; } - `); - } - - const newStyles = content.join('\n'); - if (newStyles !== this.styleElement.innerHTML) { - this.styleElement.innerHTML = newStyles; - } - } -} - -export class CollapseAllAction extends Action { - - constructor(private viewer: _.ITree, enabled: boolean) { - super('vs.tree.collapse', nls.localize('collapse all', "Collapse All"), 'monaco-tree-action collapse-all', enabled); - } - - public run(context?: any): Promise { - if (this.viewer.getHighlight()) { - return Promise.resolve(); // Global action disabled if user is in edit mode from another action - } - - this.viewer.collapseAll(); - this.viewer.clearSelection(); - this.viewer.clearFocus(); - this.viewer.domFocus(); - this.viewer.focusFirst(); - - return Promise.resolve(); - } -} diff --git a/src/vs/base/parts/tree/browser/treeDnd.ts b/src/vs/base/parts/tree/browser/treeDnd.ts deleted file mode 100644 index 810e6ce98ee..00000000000 --- a/src/vs/base/parts/tree/browser/treeDnd.ts +++ /dev/null @@ -1,73 +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 _ from 'vs/base/parts/tree/browser/tree'; -import { IDragAndDropData } from 'vs/base/browser/dnd'; - -export class ElementsDragAndDropData implements IDragAndDropData { - - private elements: any[]; - - constructor(elements: any[]) { - this.elements = elements; - } - - public update(dataTransfer: DataTransfer): void { - // no-op - } - - public getData(): any { - return this.elements; - } -} - -export class ExternalElementsDragAndDropData implements IDragAndDropData { - - private elements: any[]; - - constructor(elements: any[]) { - this.elements = elements; - } - - public update(dataTransfer: DataTransfer): void { - // no-op - } - - public getData(): any { - return this.elements; - } -} - -export class DesktopDragAndDropData implements IDragAndDropData { - - private types: any[]; - private files: any[]; - - constructor() { - this.types = []; - this.files = []; - } - - public update(dataTransfer: DataTransfer): void { - if (dataTransfer.types) { - this.types = []; - Array.prototype.push.apply(this.types, dataTransfer.types as any); - } - - if (dataTransfer.files) { - this.files = []; - Array.prototype.push.apply(this.files, dataTransfer.files as any); - - this.files = this.files.filter(f => f.size || f.type); - } - } - - public getData(): any { - return { - types: this.types, - files: this.files - }; - } -} \ No newline at end of file diff --git a/src/vs/base/parts/tree/browser/treeImpl.ts b/src/vs/base/parts/tree/browser/treeImpl.ts deleted file mode 100644 index 8b8f7b28078..00000000000 --- a/src/vs/base/parts/tree/browser/treeImpl.ts +++ /dev/null @@ -1,275 +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 'vs/css!./tree'; -import * as TreeDefaults from 'vs/base/parts/tree/browser/treeDefaults'; -import * as Model from 'vs/base/parts/tree/browser/treeModel'; -import * as View from './treeView'; -import * as _ from 'vs/base/parts/tree/browser/tree'; -import { INavigator, MappedNavigator } from 'vs/base/common/iterator'; -import { Event, Emitter, Relay } from 'vs/base/common/event'; -import { Color } from 'vs/base/common/color'; -import { mixin } from 'vs/base/common/objects'; - -export class TreeContext implements _.ITreeContext { - - public tree: _.ITree; - public configuration: _.ITreeConfiguration; - public options: _.ITreeOptions; - - public dataSource: _.IDataSource; - public renderer?: _.IRenderer; - public controller: _.IController; - public dnd: _.IDragAndDrop; - public filter: _.IFilter; - public sorter?: _.ISorter; - public accessibilityProvider: _.IAccessibilityProvider; - public styler?: _.ITreeStyler; - - constructor(tree: _.ITree, configuration: _.ITreeConfiguration, options: _.ITreeOptions = {}) { - this.tree = tree; - this.configuration = configuration; - this.options = options; - - if (!configuration.dataSource) { - throw new Error('You must provide a Data Source to the tree.'); - } - - this.dataSource = configuration.dataSource; - this.renderer = configuration.renderer; - this.controller = configuration.controller || new TreeDefaults.DefaultController({ clickBehavior: TreeDefaults.ClickBehavior.ON_MOUSE_UP, keyboardSupport: typeof options.keyboardSupport !== 'boolean' || options.keyboardSupport }); - this.dnd = configuration.dnd || new TreeDefaults.DefaultDragAndDrop(); - this.filter = configuration.filter || new TreeDefaults.DefaultFilter(); - this.sorter = configuration.sorter; - this.accessibilityProvider = configuration.accessibilityProvider || new TreeDefaults.DefaultAccessibilityProvider(); - this.styler = configuration.styler; - } -} - -const defaultStyles: _.ITreeStyles = { - listFocusBackground: Color.fromHex('#073655'), - listActiveSelectionBackground: Color.fromHex('#0E639C'), - listActiveSelectionForeground: Color.fromHex('#FFFFFF'), - listFocusAndSelectionBackground: Color.fromHex('#094771'), - listFocusAndSelectionForeground: Color.fromHex('#FFFFFF'), - listInactiveSelectionBackground: Color.fromHex('#3F3F46'), - listHoverBackground: Color.fromHex('#2A2D2E'), - listDropBackground: Color.fromHex('#383B3D') -}; - -export class Tree implements _.ITree { - - private container: HTMLElement; - - private context: _.ITreeContext; - private model: Model.TreeModel; - private view: View.TreeView; - - private _onDidChangeFocus = new Relay<_.IFocusEvent>(); - readonly onDidChangeFocus: Event<_.IFocusEvent> = this._onDidChangeFocus.event; - private _onDidChangeSelection = new Relay<_.ISelectionEvent>(); - readonly onDidChangeSelection: Event<_.ISelectionEvent> = this._onDidChangeSelection.event; - private _onHighlightChange = new Relay<_.IHighlightEvent>(); - readonly onDidChangeHighlight: Event<_.IHighlightEvent> = this._onHighlightChange.event; - private _onDidExpandItem = new Relay(); - readonly onDidExpandItem: Event = this._onDidExpandItem.event; - private _onDidCollapseItem = new Relay(); - readonly onDidCollapseItem: Event = this._onDidCollapseItem.event; - private readonly _onDispose = new Emitter(); - readonly onDidDispose: Event = this._onDispose.event; - - constructor(container: HTMLElement, configuration: _.ITreeConfiguration, options: _.ITreeOptions = {}) { - this.container = container; - mixin(options, defaultStyles, false); - - options.twistiePixels = typeof options.twistiePixels === 'number' ? options.twistiePixels : 32; - options.showTwistie = options.showTwistie === false ? false : true; - options.indentPixels = typeof options.indentPixels === 'number' ? options.indentPixels : 12; - options.alwaysFocused = options.alwaysFocused === true ? true : false; - options.useShadows = options.useShadows === false ? false : true; - options.paddingOnRow = options.paddingOnRow === false ? false : true; - options.showLoading = options.showLoading === false ? false : true; - - this.context = new TreeContext(this, configuration, options); - this.model = new Model.TreeModel(this.context); - this.view = new View.TreeView(this.context, this.container); - - this.view.setModel(this.model); - - this._onDidChangeFocus.input = this.model.onDidFocus; - this._onDidChangeSelection.input = this.model.onDidSelect; - this._onHighlightChange.input = this.model.onDidHighlight; - this._onDidExpandItem.input = this.model.onDidExpandItem; - this._onDidCollapseItem.input = this.model.onDidCollapseItem; - } - - public style(styles: _.ITreeStyles): void { - this.view.applyStyles(styles); - } - - get onDidFocus(): Event { - return this.view.onDOMFocus; - } - - get onDidBlur(): Event { - return this.view.onDOMBlur; - } - - get onDidScroll(): Event { - return this.view.onDidScroll; - } - - public getHTMLElement(): HTMLElement { - return this.view.getHTMLElement(); - } - - public layout(height?: number, width?: number): void { - this.view.layout(height, width); - } - - public domFocus(): void { - this.view.focus(); - } - - public isDOMFocused(): boolean { - return this.view.isFocused(); - } - - public domBlur(): void { - this.view.blur(); - } - - public onVisible(): void { - this.view.onVisible(); - } - - public onHidden(): void { - this.view.onHidden(); - } - - public setInput(element: any): Promise { - return this.model.setInput(element); - } - - public getInput(): any { - return this.model.getInput(); - } - - public refresh(element: any = null, recursive = true): Promise { - return this.model.refresh(element, recursive); - } - - public expand(element: any): Promise { - return this.model.expand(element); - } - - public expandAll(elements: any[]): Promise { - return this.model.expandAll(elements); - } - - public collapse(element: any, recursive: boolean = false): Promise { - return this.model.collapse(element, recursive); - } - - public collapseAll(elements: any[] | null = null, recursive: boolean = false): Promise { - return this.model.collapseAll(elements, recursive); - } - - public toggleExpansion(element: any, recursive: boolean = false): Promise { - return this.model.toggleExpansion(element, recursive); - } - - public isExpanded(element: any): boolean { - return this.model.isExpanded(element); - } - - public reveal(element: any, relativeTop: number | null = null): Promise { - return this.model.reveal(element, relativeTop); - } - - public getHighlight(): any { - return this.model.getHighlight(); - } - - public clearHighlight(eventPayload?: any): void { - this.model.setHighlight(null, eventPayload); - } - - public setSelection(elements: any[], eventPayload?: any): void { - this.model.setSelection(elements, eventPayload); - } - - public getSelection(): any[] { - return this.model.getSelection(); - } - - public clearSelection(eventPayload?: any): void { - this.model.setSelection([], eventPayload); - } - - public setFocus(element?: any, eventPayload?: any): void { - this.model.setFocus(element, eventPayload); - } - - public getFocus(): any { - return this.model.getFocus(); - } - - public focusNext(count?: number, eventPayload?: any): void { - this.model.focusNext(count, eventPayload); - } - - public focusPrevious(count?: number, eventPayload?: any): void { - this.model.focusPrevious(count, eventPayload); - } - - public focusParent(eventPayload?: any): void { - this.model.focusParent(eventPayload); - } - - public focusFirstChild(eventPayload?: any): void { - this.model.focusFirstChild(eventPayload); - } - - public focusFirst(eventPayload?: any, from?: any): void { - this.model.focusFirst(eventPayload, from); - } - - public focusNth(index: number, eventPayload?: any): void { - this.model.focusNth(index, eventPayload); - } - - public focusLast(eventPayload?: any, from?: any): void { - this.model.focusLast(eventPayload, from); - } - - public focusNextPage(eventPayload?: any): void { - this.view.focusNextPage(eventPayload); - } - - public focusPreviousPage(eventPayload?: any): void { - this.view.focusPreviousPage(eventPayload); - } - - public clearFocus(eventPayload?: any): void { - this.model.setFocus(null, eventPayload); - } - - getNavigator(fromElement?: any, subTreeOnly?: boolean): INavigator { - return new MappedNavigator(this.model.getNavigator(fromElement, subTreeOnly), i => i && i.getElement()); - } - - public dispose(): void { - this._onDispose.fire(); - this.model.dispose(); - this.view.dispose(); - this._onDidChangeFocus.dispose(); - this._onDidChangeSelection.dispose(); - this._onHighlightChange.dispose(); - this._onDidExpandItem.dispose(); - this._onDidCollapseItem.dispose(); - this._onDispose.dispose(); - } -} diff --git a/src/vs/base/parts/tree/browser/treeModel.ts b/src/vs/base/parts/tree/browser/treeModel.ts deleted file mode 100644 index 6d2999bf7e7..00000000000 --- a/src/vs/base/parts/tree/browser/treeModel.ts +++ /dev/null @@ -1,1494 +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 'vs/base/common/assert'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { IDisposable, combinedDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { INavigator } from 'vs/base/common/iterator'; -import * as _ from './tree'; -import { Event, Emitter, EventMultiplexer, Relay } from 'vs/base/common/event'; - -interface IMap { [id: string]: T; } -interface IItemMap extends IMap { } -interface ITraitMap extends IMap { } - -export class LockData { - - private _item: Item; - private _onDispose?= new Emitter(); - readonly onDispose: Event = this._onDispose!.event; - - constructor(item: Item) { - this._item = item; - } - - get item(): Item { - return this._item; - } - - dispose(): void { - if (this._onDispose) { - this._onDispose.fire(); - this._onDispose.dispose(); - this._onDispose = undefined; - } - } -} - -export class Lock { - - /* When refreshing tree items, the tree's structured can be altered, by - inserting and removing sub-items. This lock helps to manage several - possibly-structure-changing calls. - - API-wise, there are two possibly-structure-changing: refresh(...), - expand(...) and collapse(...). All these calls must call Lock#run(...). - - Any call to Lock#run(...) needs to provide the affecting item and a - callback to execute when unlocked. It must also return a promise - which fulfills once the operation ends. Once it is called, there - are three possibilities: - - - Nothing is currently running. The affecting item is remembered, and - the callback is executed. - - - Or, there are on-going operations. There are two outcomes: - - - The affecting item intersects with any other affecting items - of on-going run calls. In such a case, the given callback should - be executed only when the on-going one completes. - - - Or, it doesn't. In such a case, both operations can be run in - parallel. - - Note: two items A and B intersect if A is a descendant of B, or - vice-versa. - */ - - private locks: { [id: string]: LockData; }; - - constructor() { - this.locks = Object.create({}); - } - - public isLocked(item: Item): boolean { - return !!this.locks[item.id]; - } - - public run(item: Item, fn: () => Promise): Promise { - const lock = this.getLock(item); - - if (lock) { - return new Promise((c, e) => { - Event.once(lock.onDispose)(() => { - return this.run(item, fn).then(c, e); - }); - }); - } - - let result: Promise; - - return new Promise((c, e) => { - - if (item.isDisposed()) { - return e(new Error('Item is disposed.')); - } - - let lock = this.locks[item.id] = new LockData(item); - - result = fn().then((r) => { - delete this.locks[item.id]; - lock.dispose(); - - return r; - }).then(c, e); - - return result; - }); - } - - private getLock(item: Item): LockData | null { - let key: string; - - for (key in this.locks) { - let lock = this.locks[key]; - - if (item.intersects(lock.item)) { - return lock; - } - } - - return null; - } -} - -export class ItemRegistry { - - private _isDisposed = false; - private items: IMap<{ item: Item; disposable: IDisposable; }>; - - private _onDidRevealItem = new EventMultiplexer(); - readonly onDidRevealItem: Event = this._onDidRevealItem.event; - private _onExpandItem = new EventMultiplexer(); - readonly onExpandItem: Event = this._onExpandItem.event; - private _onDidExpandItem = new EventMultiplexer(); - readonly onDidExpandItem: Event = this._onDidExpandItem.event; - private _onCollapseItem = new EventMultiplexer(); - readonly onCollapseItem: Event = this._onCollapseItem.event; - private _onDidCollapseItem = new EventMultiplexer(); - readonly onDidCollapseItem: Event = this._onDidCollapseItem.event; - private _onDidAddTraitItem = new EventMultiplexer(); - readonly onDidAddTraitItem: Event = this._onDidAddTraitItem.event; - private _onDidRemoveTraitItem = new EventMultiplexer(); - readonly onDidRemoveTraitItem: Event = this._onDidRemoveTraitItem.event; - private _onDidRefreshItem = new EventMultiplexer(); - readonly onDidRefreshItem: Event = this._onDidRefreshItem.event; - private _onRefreshItemChildren = new EventMultiplexer(); - readonly onRefreshItemChildren: Event = this._onRefreshItemChildren.event; - private _onDidRefreshItemChildren = new EventMultiplexer(); - readonly onDidRefreshItemChildren: Event = this._onDidRefreshItemChildren.event; - private _onDidDisposeItem = new EventMultiplexer(); - readonly onDidDisposeItem: Event = this._onDidDisposeItem.event; - - constructor() { - this.items = {}; - } - - public register(item: Item): void { - Assert.ok(!this.isRegistered(item.id), 'item already registered: ' + item.id); - - const disposable = combinedDisposable( - this._onDidRevealItem.add(item.onDidReveal), - this._onExpandItem.add(item.onExpand), - this._onDidExpandItem.add(item.onDidExpand), - this._onCollapseItem.add(item.onCollapse), - this._onDidCollapseItem.add(item.onDidCollapse), - this._onDidAddTraitItem.add(item.onDidAddTrait), - this._onDidRemoveTraitItem.add(item.onDidRemoveTrait), - this._onDidRefreshItem.add(item.onDidRefresh), - this._onRefreshItemChildren.add(item.onRefreshChildren), - this._onDidRefreshItemChildren.add(item.onDidRefreshChildren), - this._onDidDisposeItem.add(item.onDidDispose) - ); - - this.items[item.id] = { item, disposable }; - } - - public deregister(item: Item): void { - Assert.ok(this.isRegistered(item.id), 'item not registered: ' + item.id); - this.items[item.id].disposable.dispose(); - delete this.items[item.id]; - } - - public isRegistered(id: string): boolean { - return this.items.hasOwnProperty(id); - } - - public getItem(id: string): Item | null { - const result = this.items[id]; - return result ? result.item : null; - } - - public dispose(): void { - this.items = {}; - - this._onDidRevealItem.dispose(); - this._onExpandItem.dispose(); - this._onDidExpandItem.dispose(); - this._onCollapseItem.dispose(); - this._onDidCollapseItem.dispose(); - this._onDidAddTraitItem.dispose(); - this._onDidRemoveTraitItem.dispose(); - this._onDidRefreshItem.dispose(); - this._onRefreshItemChildren.dispose(); - this._onDidRefreshItemChildren.dispose(); - - this._isDisposed = true; - } - - public isDisposed(): boolean { - return this._isDisposed; - } -} - -export interface IBaseItemEvent { - item: Item; -} - -export interface IItemRefreshEvent extends IBaseItemEvent { } -export interface IItemExpandEvent extends IBaseItemEvent { } -export interface IItemCollapseEvent extends IBaseItemEvent { } - -export interface IItemTraitEvent extends IBaseItemEvent { - trait: string; -} - -export interface IItemRevealEvent extends IBaseItemEvent { - relativeTop: number | null; -} - -export interface IItemChildrenRefreshEvent extends IBaseItemEvent { - isNested: boolean; -} - -export class Item { - - private registry: ItemRegistry; - private context: _.ITreeContext; - private element: any; - private lock: Lock; - - public id: string; - - private needsChildrenRefresh: boolean; - private doesHaveChildren: boolean; - - public parent: Item | null; - public previous: Item | null; - public next: Item | null; - public firstChild: Item | null; - public lastChild: Item | null; - - private height: number; - private depth: number; - - private visible: boolean; - private expanded: boolean; - - private traits: { [trait: string]: boolean; }; - - private readonly _onDidCreate = new Emitter(); - readonly onDidCreate: Event = this._onDidCreate.event; - private readonly _onDidReveal = new Emitter(); - readonly onDidReveal: Event = this._onDidReveal.event; - private readonly _onExpand = new Emitter(); - readonly onExpand: Event = this._onExpand.event; - private readonly _onDidExpand = new Emitter(); - readonly onDidExpand: Event = this._onDidExpand.event; - private readonly _onCollapse = new Emitter(); - readonly onCollapse: Event = this._onCollapse.event; - private readonly _onDidCollapse = new Emitter(); - readonly onDidCollapse: Event = this._onDidCollapse.event; - private readonly _onDidAddTrait = new Emitter(); - readonly onDidAddTrait: Event = this._onDidAddTrait.event; - private readonly _onDidRemoveTrait = new Emitter(); - readonly onDidRemoveTrait: Event = this._onDidRemoveTrait.event; - private readonly _onDidRefresh = new Emitter(); - readonly onDidRefresh: Event = this._onDidRefresh.event; - private readonly _onRefreshChildren = new Emitter(); - readonly onRefreshChildren: Event = this._onRefreshChildren.event; - private readonly _onDidRefreshChildren = new Emitter(); - readonly onDidRefreshChildren: Event = this._onDidRefreshChildren.event; - private readonly _onDidDispose = new Emitter(); - readonly onDidDispose: Event = this._onDidDispose.event; - - private _isDisposed: boolean; - - constructor(id: string, registry: ItemRegistry, context: _.ITreeContext, lock: Lock, element: any) { - this.registry = registry; - this.context = context; - this.lock = lock; - this.element = element; - - this.id = id; - this.registry.register(this); - - this.doesHaveChildren = this.context.dataSource.hasChildren(this.context.tree, this.element); - this.needsChildrenRefresh = true; - - this.parent = null; - this.previous = null; - this.next = null; - this.firstChild = null; - this.lastChild = null; - - this.traits = {}; - this.depth = 0; - this.expanded = !!(this.context.dataSource.shouldAutoexpand && this.context.dataSource.shouldAutoexpand(this.context.tree, element)); - - this._onDidCreate.fire(this); - - this.visible = this._isVisible(); - this.height = this._getHeight(); - - this._isDisposed = false; - } - - public getElement(): any { - return this.element; - } - - public hasChildren(): boolean { - return this.doesHaveChildren; - } - - public getDepth(): number { - return this.depth; - } - - public isVisible(): boolean { - return this.visible; - } - - public setVisible(value: boolean): void { - this.visible = value; - } - - public isExpanded(): boolean { - return this.expanded; - } - - /* protected */ public _setExpanded(value: boolean): void { - this.expanded = value; - } - - public reveal(relativeTop: number | null = null): void { - let eventData: IItemRevealEvent = { item: this, relativeTop: relativeTop }; - this._onDidReveal.fire(eventData); - } - - public expand(): Promise { - if (this.isExpanded() || !this.doesHaveChildren || this.lock.isLocked(this)) { - return Promise.resolve(false); - } - - let result = this.lock.run(this, () => { - if (this.isExpanded() || !this.doesHaveChildren) { - return Promise.resolve(false); - } - - let eventData: IItemExpandEvent = { item: this }; - let result: Promise; - this._onExpand.fire(eventData); - - if (this.needsChildrenRefresh) { - result = this.refreshChildren(false, true, true); - } else { - result = Promise.resolve(null); - } - - return result.then(() => { - this._setExpanded(true); - this._onDidExpand.fire(eventData); - return true; - }); - }); - - return result.then((r) => { - if (this.isDisposed()) { - return false; - } - - // Auto expand single child folders - if (this.context.options.autoExpandSingleChildren && r && this.firstChild !== null && this.firstChild === this.lastChild && this.firstChild.isVisible()) { - return this.firstChild.expand().then(() => { return true; }); - } - - return r; - }); - } - - public collapse(recursive: boolean = false): Promise { - if (recursive) { - let collapseChildrenPromise = Promise.resolve(null); - this.forEachChild((child) => { - collapseChildrenPromise = collapseChildrenPromise.then(() => child.collapse(true)); - }); - return collapseChildrenPromise.then(() => { - return this.collapse(false); - }); - } else { - if (!this.isExpanded() || this.lock.isLocked(this)) { - return Promise.resolve(false); - } - - return this.lock.run(this, () => { - let eventData: IItemCollapseEvent = { item: this }; - this._onCollapse.fire(eventData); - this._setExpanded(false); - this._onDidCollapse.fire(eventData); - - return Promise.resolve(true); - }); - } - } - - public addTrait(trait: string): void { - let eventData: IItemTraitEvent = { item: this, trait: trait }; - this.traits[trait] = true; - this._onDidAddTrait.fire(eventData); - } - - public removeTrait(trait: string): void { - let eventData: IItemTraitEvent = { item: this, trait: trait }; - delete this.traits[trait]; - this._onDidRemoveTrait.fire(eventData); - } - - public hasTrait(trait: string): boolean { - return this.traits[trait] || false; - } - - public getAllTraits(): string[] { - let result: string[] = []; - let trait: string; - for (trait in this.traits) { - if (this.traits.hasOwnProperty(trait) && this.traits[trait]) { - result.push(trait); - } - } - return result; - } - - public getHeight(): number { - return this.height; - } - - private refreshChildren(recursive: boolean, safe: boolean = false, force: boolean = false): Promise { - if (!force && !this.isExpanded()) { - const setNeedsChildrenRefresh = (item: Item) => { - item.needsChildrenRefresh = true; - item.forEachChild(setNeedsChildrenRefresh); - }; - - setNeedsChildrenRefresh(this); - - return Promise.resolve(this); - } - - this.needsChildrenRefresh = false; - - let doRefresh = () => { - let eventData: IItemChildrenRefreshEvent = { item: this, isNested: safe }; - this._onRefreshChildren.fire(eventData); - - let childrenPromise: Promise; - if (this.doesHaveChildren) { - childrenPromise = this.context.dataSource.getChildren(this.context.tree, this.element); - } else { - childrenPromise = Promise.resolve([]); - } - - const result = childrenPromise.then((elements: any[]) => { - if (this.isDisposed() || this.registry.isDisposed()) { - return Promise.resolve(null); - } - - if (!Array.isArray(elements)) { - return Promise.reject(new Error('Please return an array of children.')); - } - - elements = !elements ? [] : elements.slice(0); - elements = this.sort(elements); - - let staleItems: IItemMap = {}; - while (this.firstChild !== null) { - staleItems[this.firstChild.id] = this.firstChild; - this.removeChild(this.firstChild); - } - - for (let i = 0, len = elements.length; i < len; i++) { - let element = elements[i]; - let id = this.context.dataSource.getId(this.context.tree, element); - let item = staleItems[id] || new Item(id, this.registry, this.context, this.lock, element); - item.element = element; - if (recursive) { - item.needsChildrenRefresh = recursive; - } - delete staleItems[id]; - this.addChild(item); - } - - for (let staleItemId in staleItems) { - if (staleItems.hasOwnProperty(staleItemId)) { - staleItems[staleItemId].dispose(); - } - } - - if (recursive) { - return Promise.all(this.mapEachChild((child) => { - return child.doRefresh(recursive, true); - })); - } else { - return Promise.all(this.mapEachChild((child) => { - if (child.isExpanded() && child.needsChildrenRefresh) { - return child.doRefresh(recursive, true); - } else { - child.updateVisibility(); - return Promise.resolve(null); - } - })); - } - }); - - return result - .then(undefined, onUnexpectedError) - .then(() => this._onDidRefreshChildren.fire(eventData)); - }; - - return safe ? doRefresh() : this.lock.run(this, doRefresh); - } - - private doRefresh(recursive: boolean, safe: boolean = false): Promise { - this.doesHaveChildren = this.context.dataSource.hasChildren(this.context.tree, this.element); - this.height = this._getHeight(); - this.updateVisibility(); - - this._onDidRefresh.fire(this); - - return this.refreshChildren(recursive, safe); - } - - private updateVisibility(): void { - this.setVisible(this._isVisible()); - } - - public refresh(recursive: boolean): Promise { - return this.doRefresh(recursive); - } - - public getNavigator(): INavigator { - return new TreeNavigator(this); - } - - public intersects(other: Item): boolean { - return this.isAncestorOf(other) || other.isAncestorOf(this); - } - - private isAncestorOf(startItem: Item): boolean { - let item: Item | null = startItem; - while (item) { - if (item.id === this.id) { - return true; - } - item = item.parent; - } - return false; - } - - private addChild(item: Item, afterItem: Item | null = this.lastChild): void { - let isEmpty = this.firstChild === null; - let atHead = afterItem === null; - let atTail = afterItem === this.lastChild; - - if (isEmpty) { - this.firstChild = this.lastChild = item; - item.next = item.previous = null; - } else if (atHead) { - if (!this.firstChild) { - throw new Error('Invalid tree state'); - } - this.firstChild.previous = item; - item.next = this.firstChild; - item.previous = null; - this.firstChild = item; - } else if (atTail) { - if (!this.lastChild) { - throw new Error('Invalid tree state'); - } - this.lastChild.next = item; - item.next = null; - item.previous = this.lastChild; - this.lastChild = item; - } else { - item.previous = afterItem; - if (!afterItem) { - throw new Error('Invalid tree state'); - } - item.next = afterItem.next; - if (!afterItem.next) { - throw new Error('Invalid tree state'); - } - afterItem.next.previous = item; - afterItem.next = item; - } - - item.parent = this; - item.depth = this.depth + 1; - } - - private removeChild(item: Item): void { - let isFirstChild = this.firstChild === item; - let isLastChild = this.lastChild === item; - - if (isFirstChild && isLastChild) { - this.firstChild = this.lastChild = null; - } else if (isFirstChild) { - if (!item.next) { - throw new Error('Invalid tree state'); - } - item.next.previous = null; - this.firstChild = item.next; - } else if (isLastChild) { - if (!item.previous) { - throw new Error('Invalid tree state'); - } - item.previous.next = null; - this.lastChild = item.previous; - } else { - if (!item.next) { - throw new Error('Invalid tree state'); - } - item.next.previous = item.previous; - if (!item.previous) { - throw new Error('Invalid tree state'); - } - item.previous.next = item.next; - } - - item.parent = null; - item.depth = NaN; - } - - private forEachChild(fn: (child: Item) => void): void { - let child = this.firstChild; - let next: Item | null; - while (child) { - next = child.next; - fn(child); - child = next; - } - } - - private mapEachChild(fn: (child: Item) => T): T[] { - let result: T[] = []; - this.forEachChild((child) => { - result.push(fn(child)); - }); - return result; - } - - private sort(elements: any[]): any[] { - const sorter = this.context.sorter; - if (sorter) { - return elements.sort((element, otherElement) => { - return sorter.compare(this.context.tree, element, otherElement); - }); - } - - return elements; - } - - /* protected */ public _getHeight(): number { - if (!this.context.renderer) { - return 0; - } - return this.context.renderer.getHeight(this.context.tree, this.element); - } - - /* protected */ public _isVisible(): boolean { - if (!this.context.filter) { - return false; - } - return this.context.filter.isVisible(this.context.tree, this.element); - } - - public isDisposed(): boolean { - return this._isDisposed; - } - - public dispose(): void { - this.forEachChild((child) => child.dispose()); - - this.parent = null; - this.previous = null; - this.next = null; - this.firstChild = null; - this.lastChild = null; - - this._onDidDispose.fire(this); - - this.registry.deregister(this); - - this._onDidCreate.dispose(); - this._onDidReveal.dispose(); - this._onExpand.dispose(); - this._onDidExpand.dispose(); - this._onCollapse.dispose(); - this._onDidCollapse.dispose(); - this._onDidAddTrait.dispose(); - this._onDidRemoveTrait.dispose(); - this._onDidRefresh.dispose(); - this._onRefreshChildren.dispose(); - this._onDidRefreshChildren.dispose(); - this._onDidDispose.dispose(); - - this._isDisposed = true; - } -} - -class RootItem extends Item { - - constructor(id: string, registry: ItemRegistry, context: _.ITreeContext, lock: Lock, element: any) { - super(id, registry, context, lock, element); - } - - public isVisible(): boolean { - return false; - } - - public setVisible(value: boolean): void { - // no-op - } - - public isExpanded(): boolean { - return true; - } - - /* protected */ public _setExpanded(value: boolean): void { - // no-op - } - - public render(): void { - // no-op - } - - /* protected */ public _getHeight(): number { - return 0; - } - - /* protected */ public _isVisible(): boolean { - return false; - } -} - -export class TreeNavigator implements INavigator { - - private start: Item | null; - private item: Item | null; - - static lastDescendantOf(item: Item | null): Item | null { - if (!item) { - return null; - } - - if (item instanceof RootItem) { - return TreeNavigator.lastDescendantOf(item.lastChild); - } - - if (!item.isVisible()) { - return TreeNavigator.lastDescendantOf(item.previous); - } - - if (!item.isExpanded() || item.lastChild === null) { - return item; - } - - return TreeNavigator.lastDescendantOf(item.lastChild); - } - - constructor(item: Item | null, subTreeOnly: boolean = true) { - this.item = item; - this.start = subTreeOnly ? item : null; - } - - public current(): Item | null { - return this.item || null; - } - - public next(): Item | null { - if (this.item) { - do { - if ((this.item instanceof RootItem || (this.item.isVisible() && this.item.isExpanded())) && this.item.firstChild) { - this.item = this.item.firstChild; - } else if (this.item === this.start) { - this.item = null; - } else { - // select next brother, next uncle, next great-uncle, etc... - while (this.item && this.item !== this.start && !this.item.next) { - this.item = this.item.parent; - } - if (this.item === this.start) { - this.item = null; - } - this.item = !this.item ? null : this.item.next; - } - } while (this.item && !this.item.isVisible()); - } - return this.item || null; - } - - public previous(): Item | null { - if (this.item) { - do { - let previous = TreeNavigator.lastDescendantOf(this.item.previous); - if (previous) { - this.item = previous; - } else if (this.item.parent && this.item.parent !== this.start && this.item.parent.isVisible()) { - this.item = this.item.parent; - } else { - this.item = null; - } - } while (this.item && !this.item.isVisible()); - } - return this.item || null; - } - - public parent(): Item | null { - if (this.item) { - let parent = this.item.parent; - if (parent && parent !== this.start && parent.isVisible()) { - this.item = parent; - } else { - this.item = null; - } - } - return this.item || null; - } - - public first(): Item | null { - this.item = this.start; - this.next(); - return this.item || null; - } - - public last(): Item | null { - return TreeNavigator.lastDescendantOf(this.start); - } -} - -export interface IBaseEvent { - item: Item | null; -} - -export interface IInputEvent extends IBaseEvent { } - -export interface IRefreshEvent extends IBaseEvent { - recursive: boolean; -} - -export class TreeModel { - - private context: _.ITreeContext; - private lock!: Lock; - private input: Item | null; - private registry: ItemRegistry = new ItemRegistry(); - private registryDisposable: IDisposable = Disposable.None; - private traitsToItems: ITraitMap; - - private readonly _onSetInput = new Emitter(); - readonly onSetInput: Event = this._onSetInput.event; - private readonly _onDidSetInput = new Emitter(); - readonly onDidSetInput: Event = this._onDidSetInput.event; - private readonly _onRefresh = new Emitter(); - readonly onRefresh: Event = this._onRefresh.event; - private readonly _onDidRefresh = new Emitter(); - readonly onDidRefresh: Event = this._onDidRefresh.event; - private readonly _onDidHighlight = new Emitter<_.IHighlightEvent>(); - readonly onDidHighlight: Event<_.IHighlightEvent> = this._onDidHighlight.event; - private readonly _onDidSelect = new Emitter<_.ISelectionEvent>(); - readonly onDidSelect: Event<_.ISelectionEvent> = this._onDidSelect.event; - private readonly _onDidFocus = new Emitter<_.IFocusEvent>(); - readonly onDidFocus: Event<_.IFocusEvent> = this._onDidFocus.event; - - private _onDidRevealItem = new Relay(); - readonly onDidRevealItem: Event = this._onDidRevealItem.event; - private _onExpandItem = new Relay(); - readonly onExpandItem: Event = this._onExpandItem.event; - private _onDidExpandItem = new Relay(); - readonly onDidExpandItem: Event = this._onDidExpandItem.event; - private _onCollapseItem = new Relay(); - readonly onCollapseItem: Event = this._onCollapseItem.event; - private _onDidCollapseItem = new Relay(); - readonly onDidCollapseItem: Event = this._onDidCollapseItem.event; - private _onDidAddTraitItem = new Relay(); - readonly onDidAddTraitItem: Event = this._onDidAddTraitItem.event; - private _onDidRemoveTraitItem = new Relay(); - readonly onDidRemoveTraitItem: Event = this._onDidRemoveTraitItem.event; - private _onDidRefreshItem = new Relay(); - readonly onDidRefreshItem: Event = this._onDidRefreshItem.event; - private _onRefreshItemChildren = new Relay(); - readonly onRefreshItemChildren: Event = this._onRefreshItemChildren.event; - private _onDidRefreshItemChildren = new Relay(); - readonly onDidRefreshItemChildren: Event = this._onDidRefreshItemChildren.event; - private _onDidDisposeItem = new Relay(); - readonly onDidDisposeItem: Event = this._onDidDisposeItem.event; - - constructor(context: _.ITreeContext) { - this.context = context; - this.input = null; - this.traitsToItems = {}; - } - - public setInput(element: any): Promise { - let eventData: IInputEvent = { item: this.input }; - this._onSetInput.fire(eventData); - - this.setSelection([]); - this.setFocus(); - this.setHighlight(); - - this.lock = new Lock(); - - if (this.input) { - this.input.dispose(); - } - - if (this.registry) { - this.registry.dispose(); - this.registryDisposable.dispose(); - } - - this.registry = new ItemRegistry(); - - this._onDidRevealItem.input = this.registry.onDidRevealItem; - this._onExpandItem.input = this.registry.onExpandItem; - this._onDidExpandItem.input = this.registry.onDidExpandItem; - this._onCollapseItem.input = this.registry.onCollapseItem; - this._onDidCollapseItem.input = this.registry.onDidCollapseItem; - this._onDidAddTraitItem.input = this.registry.onDidAddTraitItem; - this._onDidRemoveTraitItem.input = this.registry.onDidRemoveTraitItem; - this._onDidRefreshItem.input = this.registry.onDidRefreshItem; - this._onRefreshItemChildren.input = this.registry.onRefreshItemChildren; - this._onDidRefreshItemChildren.input = this.registry.onDidRefreshItemChildren; - this._onDidDisposeItem.input = this.registry.onDidDisposeItem; - - this.registryDisposable = this.registry - .onDidDisposeItem(item => item.getAllTraits().forEach(trait => delete this.traitsToItems[trait][item.id])); - - let id = this.context.dataSource.getId(this.context.tree, element); - this.input = new RootItem(id, this.registry, this.context, this.lock, element); - eventData = { item: this.input }; - this._onDidSetInput.fire(eventData); - return this.refresh(this.input); - } - - public getInput(): any { - return this.input ? this.input.getElement() : null; - } - - public refresh(element: any = null, recursive: boolean = true): Promise { - let item = this.getItem(element); - - if (!item) { - return Promise.resolve(null); - } - - let eventData: IRefreshEvent = { item: item, recursive: recursive }; - this._onRefresh.fire(eventData); - return item.refresh(recursive).then(() => { - this._onDidRefresh.fire(eventData); - }); - } - - public expand(element: any): Promise { - let item = this.getItem(element); - - if (!item) { - return Promise.resolve(false); - } - - return item.expand(); - } - - public expandAll(elements?: any[]): Promise { - if (!elements) { - elements = []; - - let item: Item | null; - let nav = this.getNavigator(); - - while (item = nav.next()) { - elements.push(item); - } - } - - return this._expandAll(elements); - } - - private _expandAll(elements: any[]): Promise { - if (elements.length === 0) { - return Promise.resolve(null); - } - - const elementsToExpand: any[] = []; - const elementsToDelay: any[] = []; - - for (const element of elements) { - let item = this.getItem(element); - - if (item) { - elementsToExpand.push(element); - } else { - elementsToDelay.push(element); - } - } - - if (elementsToExpand.length === 0) { - return Promise.resolve(null); - } - - return this.__expandAll(elementsToExpand) - .then(() => this._expandAll(elementsToDelay)); - } - - private __expandAll(elements: any[]): Promise { - const promises: Array> = []; - for (let i = 0, len = elements.length; i < len; i++) { - promises.push(this.expand(elements[i])); - } - return Promise.all(promises); - } - - public collapse(element: any, recursive: boolean = false): Promise { - const item = this.getItem(element); - - if (!item) { - return Promise.resolve(false); - } - - return item.collapse(recursive); - } - - public collapseAll(elements: any[] | null = null, recursive: boolean = false): Promise { - if (!elements) { - elements = [this.input]; - recursive = true; - } - let promises: Array> = []; - for (let i = 0, len = elements.length; i < len; i++) { - promises.push(this.collapse(elements[i], recursive)); - } - return Promise.all(promises); - } - - public toggleExpansion(element: any, recursive: boolean = false): Promise { - return this.isExpanded(element) ? this.collapse(element, recursive) : this.expand(element); - } - - public toggleExpansionAll(elements: any[]): Promise { - let promises: Array> = []; - for (let i = 0, len = elements.length; i < len; i++) { - promises.push(this.toggleExpansion(elements[i])); - } - return Promise.all(promises); - } - - public isExpanded(element: any): boolean { - let item = this.getItem(element); - - if (!item) { - return false; - } - - return item.isExpanded(); - } - - public getExpandedElements(): any[] { - let result: any[] = []; - let item: Item | null; - let nav = this.getNavigator(); - - while (item = nav.next()) { - if (item.isExpanded()) { - result.push(item.getElement()); - } - } - - return result; - } - - public reveal(element: any, relativeTop: number | null = null): Promise { - return this.resolveUnknownParentChain(element).then((chain: any[]) => { - let result = Promise.resolve(null); - - chain.forEach((e) => { - result = result.then(() => this.expand(e)); - }); - - return result; - }).then(() => { - let item = this.getItem(element); - - if (item) { - return item.reveal(relativeTop); - } - }); - } - - private resolveUnknownParentChain(element: any): Promise { - return this.context.dataSource.getParent(this.context.tree, element).then((parent) => { - if (!parent) { - return Promise.resolve([]); - } - - return this.resolveUnknownParentChain(parent).then((result) => { - result.push(parent); - return result; - }); - }); - } - - public setHighlight(element?: any, eventPayload?: any): void { - this.setTraits('highlighted', element ? [element] : []); - let eventData: _.IHighlightEvent = { highlight: this.getHighlight(), payload: eventPayload }; - this._onDidHighlight.fire(eventData); - } - - public getHighlight(includeHidden: boolean = false): any { - let result = this.getElementsWithTrait('highlighted', includeHidden); - return result.length === 0 ? null : result[0]; - } - - public isHighlighted(element: any): boolean { - let item = this.getItem(element); - - if (!item) { - return false; - } - - return item.hasTrait('highlighted'); - } - - public select(element: any, eventPayload?: any): void { - this.selectAll([element], eventPayload); - } - - public selectAll(elements: any[], eventPayload?: any): void { - this.addTraits('selected', elements); - let eventData: _.ISelectionEvent = { selection: this.getSelection(), payload: eventPayload }; - this._onDidSelect.fire(eventData); - } - - public deselect(element: any, eventPayload?: any): void { - this.deselectAll([element], eventPayload); - } - - public deselectAll(elements: any[], eventPayload?: any): void { - this.removeTraits('selected', elements); - let eventData: _.ISelectionEvent = { selection: this.getSelection(), payload: eventPayload }; - this._onDidSelect.fire(eventData); - } - - public setSelection(elements: any[], eventPayload?: any): void { - this.setTraits('selected', elements); - let eventData: _.ISelectionEvent = { selection: this.getSelection(), payload: eventPayload }; - this._onDidSelect.fire(eventData); - } - - public isSelected(element: any): boolean { - let item = this.getItem(element); - - if (!item) { - return false; - } - - return item.hasTrait('selected'); - } - - public getSelection(includeHidden: boolean = false): any[] { - return this.getElementsWithTrait('selected', includeHidden); - } - - public selectNext(count: number = 1, clearSelection: boolean = true, eventPayload?: any): void { - let selection = this.getSelection(); - let item: Item = selection.length > 0 ? selection[0] : this.input; - let nextItem: Item | null; - let nav = this.getNavigator(item, false); - - for (let i = 0; i < count; i++) { - nextItem = nav.next(); - if (!nextItem) { - break; - } - item = nextItem; - } - - if (clearSelection) { - this.setSelection([item], eventPayload); - } else { - this.select(item, eventPayload); - } - } - - public selectPrevious(count: number = 1, clearSelection: boolean = true, eventPayload?: any): void { - let selection = this.getSelection(), - item: Item | null = null, - previousItem: Item | null = null; - - if (selection.length === 0) { - let nav = this.getNavigator(this.input); - - while (item = nav.next()) { - previousItem = item; - } - - item = previousItem; - - } else { - item = selection[0]; - let nav = this.getNavigator(item, false); - - for (let i = 0; i < count; i++) { - previousItem = nav.previous(); - if (!previousItem) { - break; - } - item = previousItem; - } - } - - if (clearSelection) { - this.setSelection([item], eventPayload); - } else { - this.select(item, eventPayload); - } - } - - public setFocus(element?: any, eventPayload?: any): void { - this.setTraits('focused', element ? [element] : []); - let eventData: _.IFocusEvent = { focus: this.getFocus(), payload: eventPayload }; - this._onDidFocus.fire(eventData); - } - - public isFocused(element: any): boolean { - let item = this.getItem(element); - - if (!item) { - return false; - } - - return item.hasTrait('focused'); - } - - public getFocus(includeHidden: boolean = false): any { - let result = this.getElementsWithTrait('focused', includeHidden); - return result.length === 0 ? null : result[0]; - } - - public focusNext(count: number = 1, eventPayload?: any): void { - let item: Item = this.getFocus() || this.input; - let nextItem: Item | null; - let nav = this.getNavigator(item, false); - - for (let i = 0; i < count; i++) { - nextItem = nav.next(); - if (!nextItem) { - break; - } - item = nextItem; - } - - this.setFocus(item, eventPayload); - } - - public focusPrevious(count: number = 1, eventPayload?: any): void { - let item: Item = this.getFocus() || this.input; - let previousItem: Item | null; - let nav = this.getNavigator(item, false); - - for (let i = 0; i < count; i++) { - previousItem = nav.previous(); - if (!previousItem) { - break; - } - item = previousItem; - } - - this.setFocus(item, eventPayload); - } - - public focusParent(eventPayload?: any): void { - let item: Item = this.getFocus() || this.input; - let nav = this.getNavigator(item, false); - let parent = nav.parent(); - - if (parent) { - this.setFocus(parent, eventPayload); - } - } - - public focusFirstChild(eventPayload?: any): void { - const item = this.getItem(this.getFocus() || this.input); - const nav = this.getNavigator(item, false); - const next = nav.next(); - const parent = nav.parent(); - - if (parent === item) { - this.setFocus(next, eventPayload); - } - } - - public focusFirst(eventPayload?: any, from?: any): void { - this.focusNth(0, eventPayload, from); - } - - public focusNth(index: number, eventPayload?: any, from?: any): void { - let navItem = this.getParent(from); - let nav = this.getNavigator(navItem); - let item = nav.first(); - for (let i = 0; i < index; i++) { - item = nav.next(); - } - - if (item) { - this.setFocus(item, eventPayload); - } - } - - public focusLast(eventPayload?: any, from?: any): void { - const navItem = this.getParent(from); - let item: Item | null; - if (from && navItem) { - item = navItem.lastChild; - } else { - const nav = this.getNavigator(navItem); - item = nav.last(); - } - - if (item) { - this.setFocus(item, eventPayload); - } - } - - private getParent(from?: any): Item | null { - if (from) { - const fromItem = this.getItem(from); - if (fromItem && fromItem.parent) { - return fromItem.parent; - } - } - - return this.getItem(this.input); - } - - public getNavigator(element: any = null, subTreeOnly: boolean = true): INavigator { - return new TreeNavigator(this.getItem(element), subTreeOnly); - } - - public getItem(element: any = null): Item | null { - if (element === null) { - return this.input; - } else if (element instanceof Item) { - return element; - } else if (typeof element === 'string') { - return this.registry.getItem(element); - } else { - return this.registry.getItem(this.context.dataSource.getId(this.context.tree, element)); - } - } - - public addTraits(trait: string, elements: any[]): void { - let items: IItemMap = this.traitsToItems[trait] || {}; - let item: Item | null; - for (let i = 0, len = elements.length; i < len; i++) { - item = this.getItem(elements[i]); - - if (item) { - item.addTrait(trait); - items[item.id] = item; - } - } - this.traitsToItems[trait] = items; - } - - public removeTraits(trait: string, elements: any[]): void { - let items: IItemMap = this.traitsToItems[trait] || {}; - let item: Item | null; - let id: string; - - if (elements.length === 0) { - for (id in items) { - if (items.hasOwnProperty(id)) { - item = items[id]; - item.removeTrait(trait); - } - } - - delete this.traitsToItems[trait]; - - } else { - for (let i = 0, len = elements.length; i < len; i++) { - item = this.getItem(elements[i]); - - if (item) { - item.removeTrait(trait); - delete items[item.id]; - } - } - } - } - - private setTraits(trait: string, elements: any[]): void { - if (elements.length === 0) { - this.removeTraits(trait, elements); - } else { - let items: { [id: string]: Item; } = {}; - let item: Item | null; - - for (let i = 0, len = elements.length; i < len; i++) { - item = this.getItem(elements[i]); - - if (item) { - items[item.id] = item; - } - } - - let traitItems: IItemMap = this.traitsToItems[trait] || {}; - let itemsToRemoveTrait: Item[] = []; - let id: string; - - for (id in traitItems) { - if (traitItems.hasOwnProperty(id)) { - if (items.hasOwnProperty(id)) { - delete items[id]; - } else { - itemsToRemoveTrait.push(traitItems[id]); - } - } - } - - for (let i = 0, len = itemsToRemoveTrait.length; i < len; i++) { - item = itemsToRemoveTrait[i]; - item.removeTrait(trait); - delete traitItems[item.id]; - } - - for (id in items) { - if (items.hasOwnProperty(id)) { - item = items[id]; - item.addTrait(trait); - traitItems[id] = item; - } - } - - this.traitsToItems[trait] = traitItems; - } - } - - private getElementsWithTrait(trait: string, includeHidden: boolean): any[] { - let elements: any[] = []; - let items = this.traitsToItems[trait] || {}; - let id: string; - for (id in items) { - if (items.hasOwnProperty(id) && (items[id].isVisible() || includeHidden)) { - elements.push(items[id].getElement()); - } - } - return elements; - } - - public dispose(): void { - this.registry.dispose(); - this._onSetInput.dispose(); - this._onDidSetInput.dispose(); - this._onRefresh.dispose(); - this._onDidRefresh.dispose(); - this._onDidHighlight.dispose(); - this._onDidSelect.dispose(); - this._onDidFocus.dispose(); - this._onDidRevealItem.dispose(); - this._onExpandItem.dispose(); - this._onDidExpandItem.dispose(); - this._onCollapseItem.dispose(); - this._onDidCollapseItem.dispose(); - this._onDidAddTraitItem.dispose(); - this._onDidRemoveTraitItem.dispose(); - this._onDidRefreshItem.dispose(); - this._onRefreshItemChildren.dispose(); - this._onDidRefreshItemChildren.dispose(); - this._onDidDisposeItem.dispose(); - } -} diff --git a/src/vs/base/parts/tree/browser/treeUtils.ts b/src/vs/base/parts/tree/browser/treeUtils.ts deleted file mode 100644 index 6d536a7fa5a..00000000000 --- a/src/vs/base/parts/tree/browser/treeUtils.ts +++ /dev/null @@ -1,18 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as _ from 'vs/base/parts/tree/browser/tree'; - -export function isEqualOrParent(tree: _.ITree, element: any, candidateParent: any): boolean { - const nav = tree.getNavigator(element); - - do { - if (element === candidateParent) { - return true; - } - } while (element = nav.parent()); - - return false; -} diff --git a/src/vs/base/parts/tree/browser/treeView.ts b/src/vs/base/parts/tree/browser/treeView.ts deleted file mode 100644 index 51d69f93036..00000000000 --- a/src/vs/base/parts/tree/browser/treeView.ts +++ /dev/null @@ -1,1682 +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 Platform from 'vs/base/common/platform'; -import * as Browser from 'vs/base/browser/browser'; -import * as Lifecycle from 'vs/base/common/lifecycle'; -import * as DOM from 'vs/base/browser/dom'; -import * as Diff from 'vs/base/common/diff/diff'; -import * as Touch from 'vs/base/browser/touch'; -import * as strings from 'vs/base/common/strings'; -import * as Mouse from 'vs/base/browser/mouseEvent'; -import * as Keyboard from 'vs/base/browser/keyboardEvent'; -import * as Model from 'vs/base/parts/tree/browser/treeModel'; -import * as dnd from './treeDnd'; -import { ArrayIterator, MappedIterator } from 'vs/base/common/iterator'; -import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; -import { ScrollbarVisibility } from 'vs/base/common/scrollable'; -import { HeightMap, IViewItem } from 'vs/base/parts/tree/browser/treeViewModel'; -import * as _ from 'vs/base/parts/tree/browser/tree'; -import { KeyCode } from 'vs/base/common/keyCodes'; -import { Event, Emitter } from 'vs/base/common/event'; -import { DataTransfers, StaticDND, IDragAndDropData } from 'vs/base/browser/dnd'; -import { DefaultTreestyler } from './treeDefaults'; -import { Delayer, timeout } from 'vs/base/common/async'; - -export interface IRow { - element: HTMLElement | null; - templateId: string; - templateData: any; -} - -function removeFromParent(element: HTMLElement): void { - try { - element.parentElement!.removeChild(element); - } catch (e) { - // this will throw if this happens due to a blur event, nasty business - } -} - -export class RowCache implements Lifecycle.IDisposable { - - private _cache: { [templateId: string]: IRow[]; } | null; - - constructor(private context: _.ITreeContext) { - this._cache = { '': [] }; - } - - public alloc(templateId: string): IRow { - let result = this.cache(templateId).pop(); - - if (!result) { - let content = document.createElement('div'); - content.className = 'content'; - - let row = document.createElement('div'); - row.appendChild(content); - - let templateData: any = null; - - try { - templateData = this.context.renderer!.renderTemplate(this.context.tree, templateId, content); - } catch (err) { - console.error('Tree usage error: exception while rendering template'); - console.error(err); - } - - result = { - element: row, - templateId: templateId, - templateData - }; - } - - return result; - } - - public release(templateId: string, row: IRow): void { - removeFromParent(row.element!); - this.cache(templateId).push(row); - } - - private cache(templateId: string): IRow[] { - return this._cache![templateId] || (this._cache![templateId] = []); - } - - public garbageCollect(): void { - if (this._cache) { - Object.keys(this._cache).forEach(templateId => { - this._cache![templateId].forEach(cachedRow => { - this.context.renderer!.disposeTemplate(this.context.tree, templateId, cachedRow.templateData); - cachedRow.element = null; - cachedRow.templateData = null; - }); - - delete this._cache![templateId]; - }); - } - } - - public dispose(): void { - this.garbageCollect(); - this._cache = null; - } -} - -export interface IViewContext extends _.ITreeContext { - cache: RowCache; - horizontalScrolling: boolean; -} - -export class ViewItem implements IViewItem { - - private context: IViewContext; - - public model: Model.Item; - public id: string; - protected row: IRow | null; - - public top: number; - public height: number; - public width: number = 0; - public onDragStart!: (e: DragEvent) => void; - - public needsRender: boolean = false; - public uri: string | null = null; - public unbindDragStart: Lifecycle.IDisposable = Lifecycle.Disposable.None; - public loadingTimer: any; - - public _styles: any; - private _draggable: boolean = false; - - constructor(context: IViewContext, model: Model.Item) { - this.context = context; - this.model = model; - - this.id = this.model.id; - this.row = null; - - this.top = 0; - this.height = model.getHeight(); - - this._styles = {}; - model.getAllTraits().forEach(t => this._styles[t] = true); - - if (model.isExpanded()) { - this.addClass('expanded'); - } - } - - set expanded(value: boolean) { - value ? this.addClass('expanded') : this.removeClass('expanded'); - } - - set loading(value: boolean) { - value ? this.addClass('codicon-loading') : this.removeClass('codicon-loading'); - } - - set draggable(value: boolean) { - this._draggable = value; - this.render(true); - } - - get draggable() { - return this._draggable; - } - - set dropTarget(value: boolean) { - value ? this.addClass('drop-target') : this.removeClass('drop-target'); - } - - public get element(): HTMLElement { - return (this.row && this.row.element)!; - } - - private _templateId: string | undefined; - private get templateId(): string { - return this._templateId || (this._templateId = (this.context.renderer!.getTemplateId && this.context.renderer!.getTemplateId(this.context.tree, this.model.getElement()))); - } - - public addClass(name: string): void { - this._styles[name] = true; - this.render(true); - } - - public removeClass(name: string): void { - delete this._styles[name]; // is this slow? - this.render(true); - } - - public render(skipUserRender = false): void { - if (!this.model || !this.element) { - return; - } - - let classes = ['monaco-tree-row']; - classes.push.apply(classes, Object.keys(this._styles)); - - if (this.model.hasChildren()) { - classes.push('has-children'); - } - - this.element.className = classes.join(' '); - this.element.draggable = this.draggable; - this.element.style.height = this.height + 'px'; - - // ARIA - this.element.setAttribute('role', 'treeitem'); - const accessibility = this.context.accessibilityProvider!; - const ariaLabel = accessibility.getAriaLabel(this.context.tree, this.model.getElement()); - if (ariaLabel) { - this.element.setAttribute('aria-label', ariaLabel); - } - if (accessibility.getPosInSet && accessibility.getSetSize) { - this.element.setAttribute('aria-setsize', accessibility.getSetSize()); - this.element.setAttribute('aria-posinset', accessibility.getPosInSet(this.context.tree, this.model.getElement())); - } - if (this.model.hasTrait('focused')) { - const base64Id = strings.safeBtoa(this.model.id); - - this.element.setAttribute('aria-selected', 'true'); - this.element.setAttribute('id', base64Id); - } else { - this.element.setAttribute('aria-selected', 'false'); - this.element.removeAttribute('id'); - } - if (this.model.hasChildren()) { - this.element.setAttribute('aria-expanded', String(!!this._styles['expanded'])); - } else { - this.element.removeAttribute('aria-expanded'); - } - this.element.setAttribute('aria-level', String(this.model.getDepth())); - - if (this.context.options.paddingOnRow) { - this.element.style.paddingLeft = this.context.options.twistiePixels! + ((this.model.getDepth() - 1) * this.context.options.indentPixels!) + 'px'; - } else { - this.element.style.paddingLeft = ((this.model.getDepth() - 1) * this.context.options.indentPixels!) + 'px'; - (this.row!.element!.firstElementChild).style.paddingLeft = this.context.options.twistiePixels + 'px'; - } - - let uri = this.context.dnd!.getDragURI(this.context.tree, this.model.getElement()); - - if (uri !== this.uri) { - if (this.unbindDragStart) { - this.unbindDragStart.dispose(); - } - - if (uri) { - this.uri = uri; - this.draggable = true; - this.unbindDragStart = DOM.addDisposableListener(this.element, 'dragstart', (e) => { - this.onDragStart(e); - }); - } else { - this.uri = null; - } - } - - if (!skipUserRender && this.element) { - let paddingLeft: number = 0; - if (this.context.horizontalScrolling) { - const style = window.getComputedStyle(this.element); - paddingLeft = parseFloat(style.paddingLeft!); - } - - if (this.context.horizontalScrolling) { - this.element.style.width = Browser.isFirefox ? '-moz-fit-content' : 'fit-content'; - } - - try { - this.context.renderer!.renderElement(this.context.tree, this.model.getElement(), this.templateId, this.row!.templateData); - } catch (err) { - console.error('Tree usage error: exception while rendering element'); - console.error(err); - } - - if (this.context.horizontalScrolling) { - this.width = DOM.getContentWidth(this.element) + paddingLeft; - this.element.style.width = ''; - } - } - } - - updateWidth(): any { - if (!this.context.horizontalScrolling || !this.element) { - return; - } - - const style = window.getComputedStyle(this.element); - const paddingLeft = parseFloat(style.paddingLeft!); - this.element.style.width = Browser.isFirefox ? '-moz-fit-content' : 'fit-content'; - this.width = DOM.getContentWidth(this.element) + paddingLeft; - this.element.style.width = ''; - } - - public insertInDOM(container: HTMLElement, afterElement: HTMLElement | null): void { - if (!this.row) { - this.row = this.context.cache.alloc(this.templateId); - - // used in reverse lookup from HTMLElement to Item - (this.element)[TreeView.BINDING] = this; - } - - if (this.element.parentElement) { - return; - } - - if (afterElement === null) { - container.appendChild(this.element); - } else { - try { - container.insertBefore(this.element, afterElement); - } catch (e) { - console.warn('Failed to locate previous tree element'); - container.appendChild(this.element); - } - } - - this.render(); - } - - public removeFromDOM(): void { - if (!this.row) { - return; - } - - this.unbindDragStart.dispose(); - - this.uri = null; - - (this.element)[TreeView.BINDING] = null; - this.context.cache.release(this.templateId, this.row); - this.row = null; - } - - public dispose(): void { - this.row = null; - } -} - -class RootViewItem extends ViewItem { - - constructor(context: IViewContext, model: Model.Item, wrapper: HTMLElement) { - super(context, model); - - this.row = { - element: wrapper, - templateData: null, - templateId: null! - }; - } - - public render(): void { - if (!this.model || !this.element) { - return; - } - - let classes = ['monaco-tree-wrapper']; - classes.push.apply(classes, Object.keys(this._styles)); - - if (this.model.hasChildren()) { - classes.push('has-children'); - } - - this.element.className = classes.join(' '); - } - - public insertInDOM(container: HTMLElement, afterElement: HTMLElement): void { - // noop - } - - public removeFromDOM(): void { - // noop - } -} - -interface IThrottledGestureEvent { - translationX: number; - translationY: number; -} - -function reactionEquals(one: _.IDragOverReaction, other: _.IDragOverReaction | null): boolean { - if (!one && !other) { - return true; - } else if (!one || !other) { - return false; - } else if (one.accept !== other.accept) { - return false; - } else if (one.bubble !== other.bubble) { - return false; - } else if (one.effect !== other.effect) { - return false; - } else { - return true; - } -} - -export class TreeView extends HeightMap { - - static readonly BINDING = 'monaco-tree-row'; - static readonly LOADING_DECORATION_DELAY = 800; - - private static counter: number = 0; - private instance: number; - - private context: IViewContext; - private modelListeners: Lifecycle.IDisposable[]; - private model: Model.TreeModel | null = null; - - private viewListeners: Lifecycle.IDisposable[]; - private domNode: HTMLElement; - private wrapper: HTMLElement; - private styleElement: HTMLStyleElement; - private treeStyler: _.ITreeStyler; - private rowsContainer: HTMLElement; - private scrollableElement: ScrollableElement; - private msGesture: MSGesture | undefined; - private lastPointerType: string = ''; - private lastClickTimeStamp: number = 0; - - private horizontalScrolling: boolean; - private contentWidthUpdateDelayer = new Delayer(50); - - private lastRenderTop: number; - private lastRenderHeight: number; - - private inputItem!: ViewItem; - private items: { [id: string]: ViewItem; }; - - private isRefreshing = false; - private refreshingPreviousChildrenIds: { [id: string]: string[] } = {}; - private currentDragAndDropData: IDragAndDropData | null = null; - private currentDropElement: any; - private currentDropElementReaction!: _.IDragOverReaction; - private currentDropTarget: ViewItem | null = null; - private shouldInvalidateDropReaction: boolean; - private currentDropTargets: ViewItem[] | null = null; - private currentDropDisposable: Lifecycle.IDisposable = Lifecycle.Disposable.None; - private gestureDisposable: Lifecycle.IDisposable = Lifecycle.Disposable.None; - private dragAndDropScrollInterval: number | null = null; - private dragAndDropScrollTimeout: number | null = null; - private dragAndDropMouseY: number | null = null; - - private didJustPressContextMenuKey: boolean; - - private highlightedItemWasDraggable: boolean = false; - private onHiddenScrollTop: number | null = null; - - private readonly _onDOMFocus = new Emitter(); - readonly onDOMFocus: Event = this._onDOMFocus.event; - - private readonly _onDOMBlur = new Emitter(); - readonly onDOMBlur: Event = this._onDOMBlur.event; - - private readonly _onDidScroll = new Emitter(); - readonly onDidScroll: Event = this._onDidScroll.event; - - constructor(context: _.ITreeContext, container: HTMLElement) { - super(); - - TreeView.counter++; - this.instance = TreeView.counter; - - const horizontalScrollMode = typeof context.options.horizontalScrollMode === 'undefined' ? ScrollbarVisibility.Hidden : context.options.horizontalScrollMode; - this.horizontalScrolling = horizontalScrollMode !== ScrollbarVisibility.Hidden; - - this.context = { - dataSource: context.dataSource, - renderer: context.renderer, - controller: context.controller, - dnd: context.dnd, - filter: context.filter, - sorter: context.sorter, - tree: context.tree, - accessibilityProvider: context.accessibilityProvider, - options: context.options, - cache: new RowCache(context), - horizontalScrolling: this.horizontalScrolling - }; - - this.modelListeners = []; - this.viewListeners = []; - - this.items = {}; - - this.domNode = document.createElement('div'); - this.domNode.className = `monaco-tree no-focused-item monaco-tree-instance-${this.instance}`; - // to allow direct tabbing into the tree instead of first focusing the tree - this.domNode.tabIndex = context.options.preventRootFocus ? -1 : 0; - - this.styleElement = DOM.createStyleSheet(this.domNode); - - this.treeStyler = context.styler || new DefaultTreestyler(this.styleElement, `monaco-tree-instance-${this.instance}`); - - // ARIA - this.domNode.setAttribute('role', 'tree'); - if (this.context.options.ariaLabel) { - this.domNode.setAttribute('aria-label', this.context.options.ariaLabel); - } - - if (this.context.options.alwaysFocused) { - DOM.addClass(this.domNode, 'focused'); - } - - if (!this.context.options.paddingOnRow) { - DOM.addClass(this.domNode, 'no-row-padding'); - } - - this.wrapper = document.createElement('div'); - this.wrapper.className = 'monaco-tree-wrapper'; - this.scrollableElement = new ScrollableElement(this.wrapper, { - alwaysConsumeMouseWheel: true, - horizontal: horizontalScrollMode, - vertical: (typeof context.options.verticalScrollMode !== 'undefined' ? context.options.verticalScrollMode : ScrollbarVisibility.Auto), - useShadows: context.options.useShadows - }); - this.scrollableElement.onScroll((e) => { - this.render(e.scrollTop, e.height, e.scrollLeft, e.width, e.scrollWidth); - this._onDidScroll.fire(); - }); - - if (Browser.isIE) { - this.wrapper.style.msTouchAction = 'none'; - this.wrapper.style.msContentZooming = 'none'; - } else { - this.gestureDisposable = Touch.Gesture.addTarget(this.wrapper); - } - - this.rowsContainer = document.createElement('div'); - this.rowsContainer.className = 'monaco-tree-rows'; - if (context.options.showTwistie) { - this.rowsContainer.className += ' show-twisties'; - } - - let focusTracker = DOM.trackFocus(this.domNode); - this.viewListeners.push(focusTracker.onDidFocus(() => this.onFocus())); - this.viewListeners.push(focusTracker.onDidBlur(() => this.onBlur())); - this.viewListeners.push(focusTracker); - - this.viewListeners.push(DOM.addDisposableListener(this.domNode, 'keydown', (e) => this.onKeyDown(e))); - this.viewListeners.push(DOM.addDisposableListener(this.domNode, 'keyup', (e) => this.onKeyUp(e))); - this.viewListeners.push(DOM.addDisposableListener(this.domNode, 'mousedown', (e) => this.onMouseDown(e))); - this.viewListeners.push(DOM.addDisposableListener(this.domNode, 'mouseup', (e) => this.onMouseUp(e))); - this.viewListeners.push(DOM.addDisposableListener(this.wrapper, 'auxclick', (e: MouseEvent) => { - if (e && e.button === 1) { - this.onMouseMiddleClick(e); - } - })); - this.viewListeners.push(DOM.addDisposableListener(this.wrapper, 'click', (e) => this.onClick(e))); - this.viewListeners.push(DOM.addDisposableListener(this.domNode, 'contextmenu', (e) => this.onContextMenu(e))); - this.viewListeners.push(DOM.addDisposableListener(this.wrapper, Touch.EventType.Tap, (e) => this.onTap(e))); - this.viewListeners.push(DOM.addDisposableListener(this.wrapper, Touch.EventType.Change, (e) => this.onTouchChange(e))); - - if (Browser.isIE) { - this.viewListeners.push(DOM.addDisposableListener(this.wrapper, 'MSPointerDown', (e) => this.onMsPointerDown(e))); - this.viewListeners.push(DOM.addDisposableListener(this.wrapper, 'MSGestureTap', (e) => this.onMsGestureTap(e))); - - // these events come too fast, we throttle them - this.viewListeners.push(DOM.addDisposableThrottledListener(this.wrapper, 'MSGestureChange', e => this.onThrottledMsGestureChange(e), (lastEvent, event) => { - event.stopPropagation(); - event.preventDefault(); - - let result = { translationY: event.translationY, translationX: event.translationX }; - - if (lastEvent) { - result.translationY += lastEvent.translationY; - result.translationX += lastEvent.translationX; - } - - return result; - })); - } - - this.viewListeners.push(DOM.addDisposableListener(window, 'dragover', (e) => this.onDragOver(e))); - this.viewListeners.push(DOM.addDisposableListener(this.wrapper, 'drop', (e) => this.onDrop(e))); - this.viewListeners.push(DOM.addDisposableListener(window, 'dragend', (e) => this.onDragEnd(e))); - this.viewListeners.push(DOM.addDisposableListener(window, 'dragleave', (e) => this.onDragOver(e))); - - this.wrapper.appendChild(this.rowsContainer); - this.domNode.appendChild(this.scrollableElement.getDomNode()); - container.appendChild(this.domNode); - - this.lastRenderTop = 0; - this.lastRenderHeight = 0; - - this.didJustPressContextMenuKey = false; - - this.currentDropTarget = null; - this.currentDropTargets = []; - this.shouldInvalidateDropReaction = false; - - this.dragAndDropScrollInterval = null; - this.dragAndDropScrollTimeout = null; - - this.onRowsChanged(); - this.layout(); - - this.setupMSGesture(); - - this.applyStyles(context.options); - } - - public applyStyles(styles: _.ITreeStyles): void { - this.treeStyler.style(styles); - } - - protected createViewItem(item: Model.Item): IViewItem { - return new ViewItem(this.context, item); - } - - public getHTMLElement(): HTMLElement { - return this.domNode; - } - - public focus(): void { - this.domNode.focus(); - } - - public isFocused(): boolean { - return document.activeElement === this.domNode; - } - - public blur(): void { - this.domNode.blur(); - } - - public onVisible(): void { - this.scrollTop = this.onHiddenScrollTop!; - this.onHiddenScrollTop = null; - this.setupMSGesture(); - } - - private setupMSGesture(): void { - if ((window).MSGesture) { - this.msGesture = new MSGesture(); - setTimeout(() => this.msGesture!.target = this.wrapper, 100); // TODO@joh, TODO@IETeam - } - } - - public onHidden(): void { - this.onHiddenScrollTop = this.scrollTop; - } - - private isTreeVisible(): boolean { - return this.onHiddenScrollTop === null; - } - - public layout(height?: number, width?: number): void { - if (!this.isTreeVisible()) { - return; - } - - this.viewHeight = height || DOM.getContentHeight(this.wrapper); // render - this.scrollHeight = this.getContentHeight(); - - if (this.horizontalScrolling) { - this.viewWidth = width || DOM.getContentWidth(this.wrapper); - } - } - - private render(scrollTop: number, viewHeight: number, scrollLeft: number, viewWidth: number, scrollWidth: number): void { - let i: number; - let stop: number; - - let renderTop = scrollTop; - let renderBottom = scrollTop + viewHeight; - let thisRenderBottom = this.lastRenderTop + this.lastRenderHeight; - - // when view scrolls down, start rendering from the renderBottom - for (i = this.indexAfter(renderBottom) - 1, stop = this.indexAt(Math.max(thisRenderBottom, renderTop)); i >= stop; i--) { - this.insertItemInDOM(this.itemAtIndex(i)); - } - - // when view scrolls up, start rendering from either this.renderTop or renderBottom - for (i = Math.min(this.indexAt(this.lastRenderTop), this.indexAfter(renderBottom)) - 1, stop = this.indexAt(renderTop); i >= stop; i--) { - this.insertItemInDOM(this.itemAtIndex(i)); - } - - // when view scrolls down, start unrendering from renderTop - for (i = this.indexAt(this.lastRenderTop), stop = Math.min(this.indexAt(renderTop), this.indexAfter(thisRenderBottom)); i < stop; i++) { - this.removeItemFromDOM(this.itemAtIndex(i)); - } - - // when view scrolls up, start unrendering from either renderBottom this.renderTop - for (i = Math.max(this.indexAfter(renderBottom), this.indexAt(this.lastRenderTop)), stop = this.indexAfter(thisRenderBottom); i < stop; i++) { - this.removeItemFromDOM(this.itemAtIndex(i)); - } - - let topItem = this.itemAtIndex(this.indexAt(renderTop)); - - if (topItem) { - this.rowsContainer.style.top = (topItem.top - renderTop) + 'px'; - } - - if (this.horizontalScrolling) { - this.rowsContainer.style.left = -scrollLeft + 'px'; - this.rowsContainer.style.width = `${Math.max(scrollWidth, viewWidth)}px`; - } - - this.lastRenderTop = renderTop; - this.lastRenderHeight = renderBottom - renderTop; - } - - public setModel(newModel: Model.TreeModel): void { - this.releaseModel(); - this.model = newModel; - - this.model.onRefresh(this.onRefreshing, this, this.modelListeners); - this.model.onDidRefresh(this.onRefreshed, this, this.modelListeners); - this.model.onSetInput(this.onClearingInput, this, this.modelListeners); - this.model.onDidSetInput(this.onSetInput, this, this.modelListeners); - this.model.onDidFocus(this.onModelFocusChange, this, this.modelListeners); - - this.model.onRefreshItemChildren(this.onItemChildrenRefreshing, this, this.modelListeners); - this.model.onDidRefreshItemChildren(this.onItemChildrenRefreshed, this, this.modelListeners); - this.model.onDidRefreshItem(this.onItemRefresh, this, this.modelListeners); - this.model.onExpandItem(this.onItemExpanding, this, this.modelListeners); - this.model.onDidExpandItem(this.onItemExpanded, this, this.modelListeners); - this.model.onCollapseItem(this.onItemCollapsing, this, this.modelListeners); - this.model.onDidRevealItem(this.onItemReveal, this, this.modelListeners); - this.model.onDidAddTraitItem(this.onItemAddTrait, this, this.modelListeners); - this.model.onDidRemoveTraitItem(this.onItemRemoveTrait, this, this.modelListeners); - } - - private onRefreshing(): void { - this.isRefreshing = true; - } - - private onRefreshed(): void { - this.isRefreshing = false; - this.onRowsChanged(); - } - - private onRowsChanged(scrollTop: number = this.scrollTop): void { - if (this.isRefreshing) { - return; - } - - this.scrollTop = scrollTop; - this.updateScrollWidth(); - } - - private updateScrollWidth(): void { - if (!this.horizontalScrolling) { - return; - } - - this.contentWidthUpdateDelayer.trigger(() => { - const keys = Object.keys(this.items); - let scrollWidth = 0; - - for (const key of keys) { - scrollWidth = Math.max(scrollWidth, this.items[key].width); - } - - this.scrollWidth = scrollWidth + 10 /* scrollbar */; - }); - } - - public focusNextPage(eventPayload?: any): void { - let lastPageIndex = this.indexAt(this.scrollTop + this.viewHeight); - lastPageIndex = lastPageIndex === 0 ? 0 : lastPageIndex - 1; - let lastPageElement = this.itemAtIndex(lastPageIndex).model.getElement(); - let currentlyFocusedElement = this.model!.getFocus(); - - if (currentlyFocusedElement !== lastPageElement) { - this.model!.setFocus(lastPageElement, eventPayload); - } else { - let previousScrollTop = this.scrollTop; - this.scrollTop += this.viewHeight; - - if (this.scrollTop !== previousScrollTop) { - - // Let the scroll event listener run - setTimeout(() => { - this.focusNextPage(eventPayload); - }, 0); - } - } - } - - public focusPreviousPage(eventPayload?: any): void { - let firstPageIndex: number; - - if (this.scrollTop === 0) { - firstPageIndex = this.indexAt(this.scrollTop); - } else { - firstPageIndex = this.indexAfter(this.scrollTop - 1); - } - - let firstPageElement = this.itemAtIndex(firstPageIndex).model.getElement(); - let currentlyFocusedElement = this.model!.getFocus(); - - if (currentlyFocusedElement !== firstPageElement) { - this.model!.setFocus(firstPageElement, eventPayload); - } else { - let previousScrollTop = this.scrollTop; - this.scrollTop -= this.viewHeight; - - if (this.scrollTop !== previousScrollTop) { - - // Let the scroll event listener run - setTimeout(() => { - this.focusPreviousPage(eventPayload); - }, 0); - } - } - } - - public get viewHeight() { - const scrollDimensions = this.scrollableElement.getScrollDimensions(); - return scrollDimensions.height; - } - - public set viewHeight(height: number) { - this.scrollableElement.setScrollDimensions({ height }); - } - - private set scrollHeight(scrollHeight: number) { - scrollHeight = scrollHeight + (this.horizontalScrolling ? 10 : 0); - this.scrollableElement.setScrollDimensions({ scrollHeight }); - } - - public get viewWidth(): number { - const scrollDimensions = this.scrollableElement.getScrollDimensions(); - return scrollDimensions.width; - } - - public set viewWidth(viewWidth: number) { - this.scrollableElement.setScrollDimensions({ width: viewWidth }); - } - - private set scrollWidth(scrollWidth: number) { - this.scrollableElement.setScrollDimensions({ scrollWidth }); - } - - public get scrollTop(): number { - const scrollPosition = this.scrollableElement.getScrollPosition(); - return scrollPosition.scrollTop; - } - - public set scrollTop(scrollTop: number) { - const scrollHeight = this.getContentHeight() + (this.horizontalScrolling ? 10 : 0); - this.scrollableElement.setScrollDimensions({ scrollHeight }); - this.scrollableElement.setScrollPosition({ scrollTop }); - } - - public getScrollPosition(): number { - const height = this.getContentHeight() - this.viewHeight; - return height <= 0 ? 1 : this.scrollTop / height; - } - - public setScrollPosition(pos: number): void { - const height = this.getContentHeight() - this.viewHeight; - this.scrollTop = height * pos; - } - - // Events - - private onClearingInput(e: Model.IInputEvent): void { - let item = e.item; - if (item) { - this.onRemoveItems(new MappedIterator(item.getNavigator(), item => item && item.id)); - this.onRowsChanged(); - } - } - - private onSetInput(e: Model.IInputEvent): void { - this.context.cache.garbageCollect(); - this.inputItem = new RootViewItem(this.context, e.item, this.wrapper); - } - - private onItemChildrenRefreshing(e: Model.IItemChildrenRefreshEvent): void { - let item = e.item; - let viewItem = this.items[item.id]; - - if (viewItem && this.context.options.showLoading) { - viewItem.loadingTimer = setTimeout(() => { - viewItem.loadingTimer = 0; - viewItem.loading = true; - }, TreeView.LOADING_DECORATION_DELAY); - } - - if (!e.isNested) { - let childrenIds: string[] = []; - let navigator = item.getNavigator(); - let childItem: Model.Item | null; - - while (childItem = navigator.next()) { - childrenIds.push(childItem.id); - } - - this.refreshingPreviousChildrenIds[item.id] = childrenIds; - } - } - - private onItemChildrenRefreshed(e: Model.IItemChildrenRefreshEvent): void { - let item = e.item; - let viewItem = this.items[item.id]; - - if (viewItem) { - if (viewItem.loadingTimer) { - clearTimeout(viewItem.loadingTimer); - viewItem.loadingTimer = 0; - } - - viewItem.loading = false; - } - - if (!e.isNested) { - let previousChildrenIds = this.refreshingPreviousChildrenIds[item.id]; - let afterModelItems: Model.Item[] = []; - let navigator = item.getNavigator(); - let childItem: Model.Item | null; - - while (childItem = navigator.next()) { - afterModelItems.push(childItem); - } - - let skipDiff = Math.abs(previousChildrenIds.length - afterModelItems.length) > 1000; - let diff: Diff.IDiffChange[] = []; - let doToInsertItemsAlreadyExist: boolean = false; - - if (!skipDiff) { - const lcs = new Diff.LcsDiff( - { - getElements: () => previousChildrenIds - }, - { - getElements: () => afterModelItems.map(item => item.id) - }, - null - ); - - diff = lcs.ComputeDiff(false).changes; - - // this means that the result of the diff algorithm would result - // in inserting items that were already registered. this can only - // happen if the data provider returns bad ids OR if the sorting - // of the elements has changed - doToInsertItemsAlreadyExist = diff.some(d => { - if (d.modifiedLength > 0) { - for (let i = d.modifiedStart, len = d.modifiedStart + d.modifiedLength; i < len; i++) { - if (this.items.hasOwnProperty(afterModelItems[i].id)) { - return true; - } - } - } - return false; - }); - } - - // 50 is an optimization number, at some point we're better off - // just replacing everything - if (!skipDiff && !doToInsertItemsAlreadyExist && diff.length < 50) { - for (const diffChange of diff) { - - if (diffChange.originalLength > 0) { - this.onRemoveItems(new ArrayIterator(previousChildrenIds, diffChange.originalStart, diffChange.originalStart + diffChange.originalLength)); - } - - if (diffChange.modifiedLength > 0) { - let beforeItem: Model.Item | null = afterModelItems[diffChange.modifiedStart - 1] || item; - beforeItem = beforeItem.getDepth() > 0 ? beforeItem : null; - - this.onInsertItems(new ArrayIterator(afterModelItems, diffChange.modifiedStart, diffChange.modifiedStart + diffChange.modifiedLength), beforeItem ? beforeItem.id : null); - } - } - - } else if (skipDiff || diff.length) { - this.onRemoveItems(new ArrayIterator(previousChildrenIds)); - this.onInsertItems(new ArrayIterator(afterModelItems), item.getDepth() > 0 ? item.id : null); - } - - if (skipDiff || diff.length) { - this.onRowsChanged(); - } - } - } - - private onItemRefresh(item: Model.Item): void { - this.onItemsRefresh([item]); - } - - private onItemsRefresh(items: Model.Item[]): void { - this.onRefreshItemSet(items.filter(item => this.items.hasOwnProperty(item.id))); - this.onRowsChanged(); - } - - private onItemExpanding(e: Model.IItemExpandEvent): void { - let viewItem = this.items[e.item.id]; - if (viewItem) { - viewItem.expanded = true; - } - } - - private onItemExpanded(e: Model.IItemExpandEvent): void { - let item = e.item; - let viewItem = this.items[item.id]; - if (viewItem) { - viewItem.expanded = true; - - let height = this.onInsertItems(item.getNavigator(), item.id) || 0; - let scrollTop = this.scrollTop; - - if (viewItem.top + viewItem.height <= this.scrollTop) { - scrollTop += height; - } - - this.onRowsChanged(scrollTop); - } - } - - private onItemCollapsing(e: Model.IItemCollapseEvent): void { - let item = e.item; - let viewItem = this.items[item.id]; - if (viewItem) { - viewItem.expanded = false; - this.onRemoveItems(new MappedIterator(item.getNavigator(), item => item && item.id)); - this.onRowsChanged(); - } - } - - private onItemReveal(e: Model.IItemRevealEvent): void { - let item = e.item; - let relativeTop = e.relativeTop; - let viewItem = this.items[item.id]; - if (viewItem) { - if (relativeTop !== null) { - relativeTop = relativeTop < 0 ? 0 : relativeTop; - relativeTop = relativeTop > 1 ? 1 : relativeTop; - - // y = mx + b - let m = viewItem.height - this.viewHeight; - this.scrollTop = m * relativeTop + viewItem.top; - } else { - let viewItemBottom = viewItem.top + viewItem.height; - let wrapperBottom = this.scrollTop + this.viewHeight; - - if (viewItem.top < this.scrollTop) { - this.scrollTop = viewItem.top; - } else if (viewItemBottom >= wrapperBottom) { - this.scrollTop = viewItemBottom - this.viewHeight; - } - } - } - } - - private onItemAddTrait(e: Model.IItemTraitEvent): void { - let item = e.item; - let trait = e.trait; - let viewItem = this.items[item.id]; - if (viewItem) { - viewItem.addClass(trait); - } - if (trait === 'highlighted') { - DOM.addClass(this.domNode, trait); - - // Ugly Firefox fix: input fields can't be selected if parent nodes are draggable - if (viewItem) { - this.highlightedItemWasDraggable = !!viewItem.draggable; - if (viewItem.draggable) { - viewItem.draggable = false; - } - } - } - } - - private onItemRemoveTrait(e: Model.IItemTraitEvent): void { - let item = e.item; - let trait = e.trait; - let viewItem = this.items[item.id]; - if (viewItem) { - viewItem.removeClass(trait); - } - if (trait === 'highlighted') { - DOM.removeClass(this.domNode, trait); - - // Ugly Firefox fix: input fields can't be selected if parent nodes are draggable - if (this.highlightedItemWasDraggable) { - viewItem.draggable = true; - } - this.highlightedItemWasDraggable = false; - } - } - - private onModelFocusChange(): void { - const focus = this.model && this.model.getFocus(); - - DOM.toggleClass(this.domNode, 'no-focused-item', !focus); - - // ARIA - if (focus) { - this.domNode.setAttribute('aria-activedescendant', strings.safeBtoa(this.context.dataSource.getId(this.context.tree, focus))); - } else { - this.domNode.removeAttribute('aria-activedescendant'); - } - } - - // HeightMap "events" - - public onInsertItem(item: ViewItem): void { - item.onDragStart = (e) => { this.onDragStart(item, e); }; - item.needsRender = true; - this.refreshViewItem(item); - this.items[item.id] = item; - } - - public onRefreshItem(item: ViewItem, needsRender = false): void { - item.needsRender = item.needsRender || needsRender; - this.refreshViewItem(item); - } - - public onRemoveItem(item: ViewItem): void { - this.removeItemFromDOM(item); - item.dispose(); - delete this.items[item.id]; - } - - // ViewItem refresh - - private refreshViewItem(item: ViewItem): void { - item.render(); - - if (this.shouldBeRendered(item)) { - this.insertItemInDOM(item); - } else { - this.removeItemFromDOM(item); - } - } - - // DOM Events - - private onClick(e: MouseEvent): void { - if (this.lastPointerType && this.lastPointerType !== 'mouse') { - return; - } - - let event = new Mouse.StandardMouseEvent(e); - let item = this.getItemAround(event.target); - - if (!item) { - return; - } - - if (Browser.isIE && Date.now() - this.lastClickTimeStamp < 300) { - // IE10+ doesn't set the detail property correctly. While IE10 simply - // counts the number of clicks, IE11 reports always 1. To align with - // other browser, we set the value to 2 if clicks events come in a 300ms - // sequence. - event.detail = 2; - } - this.lastClickTimeStamp = Date.now(); - - this.context.controller!.onClick(this.context.tree, item.model.getElement(), event); - } - - private onMouseMiddleClick(e: MouseEvent): void { - if (!this.context.controller!.onMouseMiddleClick!) { - return; - } - - let event = new Mouse.StandardMouseEvent(e); - let item = this.getItemAround(event.target); - - if (!item) { - return; - } - this.context.controller!.onMouseMiddleClick!(this.context.tree, item.model.getElement(), event); - } - - private onMouseDown(e: MouseEvent): void { - this.didJustPressContextMenuKey = false; - - if (!this.context.controller!.onMouseDown!) { - return; - } - - if (this.lastPointerType && this.lastPointerType !== 'mouse') { - return; - } - - let event = new Mouse.StandardMouseEvent(e); - - if (event.ctrlKey && Platform.isNative && Platform.isMacintosh) { - return; - } - - let item = this.getItemAround(event.target); - - if (!item) { - return; - } - - this.context.controller!.onMouseDown!(this.context.tree, item.model.getElement(), event); - } - - private onMouseUp(e: MouseEvent): void { - if (!this.context.controller!.onMouseUp!) { - return; - } - - if (this.lastPointerType && this.lastPointerType !== 'mouse') { - return; - } - - let event = new Mouse.StandardMouseEvent(e); - - if (event.ctrlKey && Platform.isNative && Platform.isMacintosh) { - return; - } - - let item = this.getItemAround(event.target); - - if (!item) { - return; - } - - this.context.controller!.onMouseUp!(this.context.tree, item.model.getElement(), event); - } - - private onTap(e: Touch.GestureEvent): void { - let item = this.getItemAround(e.initialTarget); - - if (!item) { - return; - } - - this.context.controller!.onTap(this.context.tree, item.model.getElement(), e); - } - - private onTouchChange(event: Touch.GestureEvent): void { - event.preventDefault(); - event.stopPropagation(); - - this.scrollTop -= event.translationY; - } - - private onContextMenu(keyboardEvent: KeyboardEvent): void; - private onContextMenu(mouseEvent: MouseEvent): void; - private onContextMenu(event: KeyboardEvent | MouseEvent): void { - let resultEvent: _.ContextMenuEvent; - let element: any; - - if (event instanceof KeyboardEvent || this.didJustPressContextMenuKey) { - this.didJustPressContextMenuKey = false; - - let keyboardEvent = new Keyboard.StandardKeyboardEvent(event); - element = this.model!.getFocus(); - - let position: DOM.IDomNodePagePosition; - - if (!element) { - element = this.model!.getInput(); - position = DOM.getDomNodePagePosition(this.inputItem.element); - } else { - const id = this.context.dataSource.getId(this.context.tree, element); - const viewItem = this.items[id!]; - position = DOM.getDomNodePagePosition(viewItem.element); - } - - resultEvent = new _.KeyboardContextMenuEvent(position.left + position.width, position.top, keyboardEvent); - - } else { - let mouseEvent = new Mouse.StandardMouseEvent(event); - let item = this.getItemAround(mouseEvent.target); - - if (!item) { - return; - } - - element = item.model.getElement(); - resultEvent = new _.MouseContextMenuEvent(mouseEvent); - } - - this.context.controller!.onContextMenu(this.context.tree, element, resultEvent); - } - - private onKeyDown(e: KeyboardEvent): void { - let event = new Keyboard.StandardKeyboardEvent(e); - - this.didJustPressContextMenuKey = event.keyCode === KeyCode.ContextMenu || (event.shiftKey && event.keyCode === KeyCode.F10); - - if (event.target && event.target.tagName && event.target.tagName.toLowerCase() === 'input') { - return; // Ignore event if target is a form input field (avoids browser specific issues) - } - - if (this.didJustPressContextMenuKey) { - event.preventDefault(); - event.stopPropagation(); - } - - this.context.controller!.onKeyDown(this.context.tree, event); - } - - private onKeyUp(e: KeyboardEvent): void { - if (this.didJustPressContextMenuKey) { - this.onContextMenu(e); - } - - this.didJustPressContextMenuKey = false; - this.context.controller!.onKeyUp(this.context.tree, new Keyboard.StandardKeyboardEvent(e)); - } - - private onDragStart(item: ViewItem, e: any): void { - if (this.model!.getHighlight()) { - return; - } - - let element = item.model.getElement(); - let selection = this.model!.getSelection(); - let elements: any[]; - - if (selection.indexOf(element) > -1) { - elements = selection; - } else { - elements = [element]; - } - - e.dataTransfer.effectAllowed = 'copyMove'; - e.dataTransfer.setData(DataTransfers.RESOURCES, JSON.stringify([item.uri])); - if (e.dataTransfer.setDragImage) { - let label: string; - - if (this.context.dnd!.getDragLabel) { - label = this.context.dnd!.getDragLabel!(this.context.tree, elements); - } else { - label = String(elements.length); - } - - const dragImage = document.createElement('div'); - dragImage.className = 'monaco-tree-drag-image'; - dragImage.textContent = label; - document.body.appendChild(dragImage); - e.dataTransfer.setDragImage(dragImage, -10, -10); - setTimeout(() => document.body.removeChild(dragImage), 0); - } - - this.currentDragAndDropData = new dnd.ElementsDragAndDropData(elements); - StaticDND.CurrentDragAndDropData = new dnd.ExternalElementsDragAndDropData(elements); - - this.context.dnd!.onDragStart(this.context.tree, this.currentDragAndDropData, new Mouse.DragMouseEvent(e)); - } - - private setupDragAndDropScrollInterval(): void { - let viewTop = DOM.getTopLeftOffset(this.wrapper).top; - - if (!this.dragAndDropScrollInterval) { - this.dragAndDropScrollInterval = window.setInterval(() => { - if (this.dragAndDropMouseY === null) { - return; - } - - let diff = this.dragAndDropMouseY - viewTop; - let scrollDiff = 0; - let upperLimit = this.viewHeight - 35; - - if (diff < 35) { - scrollDiff = Math.max(-14, 0.2 * (diff - 35)); - } else if (diff > upperLimit) { - scrollDiff = Math.min(14, 0.2 * (diff - upperLimit)); - } - - this.scrollTop += scrollDiff; - }, 10); - - this.cancelDragAndDropScrollTimeout(); - - this.dragAndDropScrollTimeout = window.setTimeout(() => { - this.cancelDragAndDropScrollInterval(); - this.dragAndDropScrollTimeout = null; - }, 1000); - } - } - - private cancelDragAndDropScrollInterval(): void { - if (this.dragAndDropScrollInterval) { - window.clearInterval(this.dragAndDropScrollInterval); - this.dragAndDropScrollInterval = null; - } - - this.cancelDragAndDropScrollTimeout(); - } - - private cancelDragAndDropScrollTimeout(): void { - if (this.dragAndDropScrollTimeout) { - window.clearTimeout(this.dragAndDropScrollTimeout); - this.dragAndDropScrollTimeout = null; - } - } - - private onDragOver(e: DragEvent): boolean { - e.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome) - - let event = new Mouse.DragMouseEvent(e); - - let viewItem = this.getItemAround(event.target); - - if (!viewItem || (event.posx === 0 && event.posy === 0 && event.browserEvent.type === DOM.EventType.DRAG_LEAVE)) { - // dragging outside of tree - - if (this.currentDropTarget) { - // clear previously hovered element feedback - - this.currentDropTargets!.forEach(i => i.dropTarget = false); - this.currentDropTargets = []; - this.currentDropDisposable.dispose(); - } - - this.cancelDragAndDropScrollInterval(); - this.currentDropTarget = null; - this.currentDropElement = null; - this.dragAndDropMouseY = null; - - return false; - } - - // dragging inside the tree - this.setupDragAndDropScrollInterval(); - this.dragAndDropMouseY = event.posy; - - if (!this.currentDragAndDropData) { - // just started dragging - - if (StaticDND.CurrentDragAndDropData) { - this.currentDragAndDropData = StaticDND.CurrentDragAndDropData; - } else { - if (!event.dataTransfer.types) { - return false; - } - - this.currentDragAndDropData = new dnd.DesktopDragAndDropData(); - } - } - - this.currentDragAndDropData.update((event.browserEvent as DragEvent).dataTransfer!); - - let element: any; - let item: Model.Item | null = viewItem.model; - let reaction: _.IDragOverReaction | null; - - // check the bubble up behavior - do { - element = item ? item.getElement() : this.model!.getInput(); - reaction = this.context.dnd!.onDragOver(this.context.tree, this.currentDragAndDropData, element, event); - - if (!reaction || reaction.bubble !== _.DragOverBubble.BUBBLE_UP) { - break; - } - - item = item && item.parent; - } while (item); - - if (!item) { - this.currentDropElement = null; - return false; - } - - let canDrop = reaction && reaction.accept; - - if (canDrop) { - this.currentDropElement = item.getElement(); - event.preventDefault(); - event.dataTransfer.dropEffect = reaction!.effect === _.DragOverEffect.COPY ? 'copy' : 'move'; - } else { - this.currentDropElement = null; - } - - // item is the model item where drop() should be called - - // can be null - let currentDropTarget = item.id === this.inputItem.id ? this.inputItem : this.items[item.id]; - - if (this.shouldInvalidateDropReaction || this.currentDropTarget !== currentDropTarget || !reactionEquals(this.currentDropElementReaction, reaction)) { - this.shouldInvalidateDropReaction = false; - - if (this.currentDropTarget) { - this.currentDropTargets!.forEach(i => i.dropTarget = false); - this.currentDropTargets = []; - this.currentDropDisposable.dispose(); - } - - this.currentDropTarget = currentDropTarget; - this.currentDropElementReaction = reaction!; - - if (canDrop) { - // setup hover feedback for drop target - - if (this.currentDropTarget) { - this.currentDropTarget.dropTarget = true; - this.currentDropTargets!.push(this.currentDropTarget); - } - - if (reaction!.bubble === _.DragOverBubble.BUBBLE_DOWN) { - let nav = item.getNavigator(); - let child: Model.Item | null; - while (child = nav.next()) { - viewItem = this.items[child.id]; - if (viewItem) { - viewItem.dropTarget = true; - this.currentDropTargets!.push(viewItem); - } - } - } - - if (reaction!.autoExpand) { - const timeoutPromise = timeout(500); - this.currentDropDisposable = Lifecycle.toDisposable(() => timeoutPromise.cancel()); - - timeoutPromise - .then(() => this.context.tree.expand(this.currentDropElement)) - .then(() => this.shouldInvalidateDropReaction = true); - } - } - } - - return true; - } - - private onDrop(e: DragEvent): void { - if (this.currentDropElement) { - let event = new Mouse.DragMouseEvent(e); - event.preventDefault(); - this.currentDragAndDropData!.update((event.browserEvent as DragEvent).dataTransfer!); - this.context.dnd!.drop(this.context.tree, this.currentDragAndDropData!, this.currentDropElement, event); - this.onDragEnd(e); - } - this.cancelDragAndDropScrollInterval(); - } - - private onDragEnd(e: DragEvent): void { - if (this.currentDropTarget) { - this.currentDropTargets!.forEach(i => i.dropTarget = false); - this.currentDropTargets = []; - } - - this.currentDropDisposable.dispose(); - - this.cancelDragAndDropScrollInterval(); - this.currentDragAndDropData = null; - StaticDND.CurrentDragAndDropData = undefined; - this.currentDropElement = null; - this.currentDropTarget = null; - this.dragAndDropMouseY = null; - } - - private onFocus(): void { - if (!this.context.options.alwaysFocused) { - DOM.addClass(this.domNode, 'focused'); - } - - this._onDOMFocus.fire(); - } - - private onBlur(): void { - if (!this.context.options.alwaysFocused) { - DOM.removeClass(this.domNode, 'focused'); - } - - this.domNode.removeAttribute('aria-activedescendant'); // ARIA - - this._onDOMBlur.fire(); - } - - // MS specific DOM Events - - private onMsPointerDown(event: MSPointerEvent): void { - if (!this.msGesture) { - return; - } - - // Circumvent IE11 breaking change in e.pointerType & TypeScript's stale definitions - let pointerType = event.pointerType; - if (pointerType === ((event).MSPOINTER_TYPE_MOUSE || 'mouse')) { - this.lastPointerType = 'mouse'; - return; - } else if (pointerType === ((event).MSPOINTER_TYPE_TOUCH || 'touch')) { - this.lastPointerType = 'touch'; - } else { - return; - } - - event.stopPropagation(); - event.preventDefault(); - - this.msGesture.addPointer(event.pointerId); - } - - private onThrottledMsGestureChange(event: IThrottledGestureEvent): void { - this.scrollTop -= event.translationY; - } - - private onMsGestureTap(event: MSGestureEvent): void { - (event).initialTarget = document.elementFromPoint(event.clientX, event.clientY); - this.onTap(event); - } - - // DOM changes - - private insertItemInDOM(item: ViewItem): void { - let elementAfter: HTMLElement | null = null; - let itemAfter = this.itemAfter(item); - - if (itemAfter && itemAfter.element) { - elementAfter = itemAfter.element; - } - - item.insertInDOM(this.rowsContainer, elementAfter); - } - - private removeItemFromDOM(item: ViewItem): void { - if (!item) { - return; - } - - item.removeFromDOM(); - } - - // Helpers - - private shouldBeRendered(item: ViewItem): boolean { - return item.top < this.lastRenderTop + this.lastRenderHeight && item.top + item.height > this.lastRenderTop; - } - - private getItemAround(element: HTMLElement): ViewItem | undefined { - let candidate: ViewItem = this.inputItem; - let el: HTMLElement | null = element; - - do { - if ((el)[TreeView.BINDING]) { - candidate = (el)[TreeView.BINDING]; - } - - if (el === this.wrapper || el === this.domNode) { - return candidate; - } - - if (el === this.scrollableElement.getDomNode() || el === document.body) { - return undefined; - } - } while (el = el.parentElement); - - return undefined; - } - - // Cleanup - - private releaseModel(): void { - if (this.model) { - this.modelListeners = Lifecycle.dispose(this.modelListeners); - this.model = null; - } - } - - public dispose(): void { - // TODO@joao: improve - this.scrollableElement.dispose(); - - this.releaseModel(); - - this.viewListeners = Lifecycle.dispose(this.viewListeners); - - this._onDOMFocus.dispose(); - this._onDOMBlur.dispose(); - - if (this.domNode.parentNode) { - this.domNode.parentNode.removeChild(this.domNode); - } - - if (this.items) { - Object.keys(this.items).forEach(key => this.items[key].removeFromDOM()); - } - - if (this.context.cache) { - this.context.cache.dispose(); - } - this.gestureDisposable.dispose(); - - super.dispose(); - } -} diff --git a/src/vs/base/parts/tree/browser/treeViewModel.ts b/src/vs/base/parts/tree/browser/treeViewModel.ts deleted file mode 100644 index 823f0bb0a9d..00000000000 --- a/src/vs/base/parts/tree/browser/treeViewModel.ts +++ /dev/null @@ -1,235 +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 { INextIterator, ArrayIterator } from 'vs/base/common/iterator'; -import { Item } from './treeModel'; - -export interface IViewItem { - model: Item; - top: number; - height: number; - width: number; -} - -export class HeightMap { - - private heightMap: IViewItem[] = []; - private indexes: { [item: string]: number; } = {}; - - getContentHeight(): number { - let last = this.heightMap[this.heightMap.length - 1]; - return !last ? 0 : last.top + last.height; - } - - onInsertItems(iterator: INextIterator, afterItemId: string | null = null): number | undefined { - let item: Item | null = null; - let viewItem: IViewItem; - let i: number, j: number; - let totalSize: number; - let sizeDiff = 0; - - if (afterItemId === null) { - i = 0; - totalSize = 0; - } else { - i = this.indexes[afterItemId] + 1; - viewItem = this.heightMap[i - 1]; - - if (!viewItem) { - console.error('view item doesnt exist'); - return undefined; - } - - totalSize = viewItem.top + viewItem.height; - } - - let boundSplice = this.heightMap.splice.bind(this.heightMap, i, 0); - - let itemsToInsert: IViewItem[] = []; - - while (item = iterator.next()) { - viewItem = this.createViewItem(item); - viewItem.top = totalSize + sizeDiff; - - this.indexes[item.id] = i++; - itemsToInsert.push(viewItem); - sizeDiff += viewItem.height; - } - - boundSplice.apply(this.heightMap, itemsToInsert); - - for (j = i; j < this.heightMap.length; j++) { - viewItem = this.heightMap[j]; - viewItem.top += sizeDiff; - this.indexes[viewItem.model.id] = j; - } - - for (j = itemsToInsert.length - 1; j >= 0; j--) { - this.onInsertItem(itemsToInsert[j]); - } - - for (j = this.heightMap.length - 1; j >= i; j--) { - this.onRefreshItem(this.heightMap[j]); - } - - return sizeDiff; - } - - onInsertItem(item: IViewItem): void { - // noop - } - - // Contiguous items - onRemoveItems(iterator: INextIterator): void { - let itemId: string | null = null; - let viewItem: IViewItem; - let startIndex: number | null = null; - let i = 0; - let sizeDiff = 0; - - while (itemId = iterator.next()) { - i = this.indexes[itemId]; - viewItem = this.heightMap[i]; - - if (!viewItem) { - console.error('view item doesnt exist'); - return; - } - - sizeDiff -= viewItem.height; - delete this.indexes[itemId]; - this.onRemoveItem(viewItem); - - if (startIndex === null) { - startIndex = i; - } - } - - if (sizeDiff === 0 || startIndex === null) { - return; - } - - this.heightMap.splice(startIndex, i - startIndex + 1); - - for (i = startIndex; i < this.heightMap.length; i++) { - viewItem = this.heightMap[i]; - viewItem.top += sizeDiff; - this.indexes[viewItem.model.id] = i; - this.onRefreshItem(viewItem); - } - } - - onRemoveItem(item: IViewItem): void { - // noop - } - - onRefreshItemSet(items: Item[]): void { - let sortedItems = items.sort((a, b) => this.indexes[a.id] - this.indexes[b.id]); - this.onRefreshItems(new ArrayIterator(sortedItems)); - } - - // Ordered, but not necessarily contiguous items - onRefreshItems(iterator: INextIterator): void { - let item: Item | null = null; - let viewItem: IViewItem; - let newHeight: number; - let i: number, j: number | null = null; - let cummDiff = 0; - - while (item = iterator.next()) { - i = this.indexes[item.id]; - - for (; cummDiff !== 0 && j !== null && j < i; j++) { - viewItem = this.heightMap[j]; - viewItem.top += cummDiff; - this.onRefreshItem(viewItem); - } - - viewItem = this.heightMap[i]; - newHeight = item.getHeight(); - viewItem.top += cummDiff; - cummDiff += newHeight - viewItem.height; - viewItem.height = newHeight; - this.onRefreshItem(viewItem, true); - - j = i + 1; - } - - if (cummDiff !== 0 && j !== null) { - for (; j < this.heightMap.length; j++) { - viewItem = this.heightMap[j]; - viewItem.top += cummDiff; - this.onRefreshItem(viewItem); - } - } - } - - onRefreshItem(item: IViewItem, needsRender: boolean = false): void { - // noop - } - - itemsCount(): number { - return this.heightMap.length; - } - - itemAt(position: number): string { - return this.heightMap[this.indexAt(position)].model.id; - } - - withItemsInRange(start: number, end: number, fn: (item: string) => void): void { - start = this.indexAt(start); - end = this.indexAt(end); - for (let i = start; i <= end; i++) { - fn(this.heightMap[i].model.id); - } - } - - indexAt(position: number): number { - let left = 0; - let right = this.heightMap.length; - let center: number; - let item: IViewItem; - - // Binary search - while (left < right) { - center = Math.floor((left + right) / 2); - item = this.heightMap[center]; - - if (position < item.top) { - right = center; - } else if (position >= item.top + item.height) { - if (left === center) { - break; - } - left = center; - } else { - return center; - } - } - - return this.heightMap.length; - } - - indexAfter(position: number): number { - return Math.min(this.indexAt(position) + 1, this.heightMap.length); - } - - itemAtIndex(index: number): IViewItem { - return this.heightMap[index]; - } - - itemAfter(item: IViewItem): IViewItem { - return this.heightMap[this.indexes[item.model.id] + 1] || null; - } - - protected createViewItem(item: Item): IViewItem { - throw new Error('not implemented'); - } - - dispose(): void { - this.heightMap = []; - this.indexes = {}; - } -} diff --git a/src/vs/base/parts/tree/test/browser/treeModel.test.ts b/src/vs/base/parts/tree/test/browser/treeModel.test.ts deleted file mode 100644 index 18d27eefada..00000000000 --- a/src/vs/base/parts/tree/test/browser/treeModel.test.ts +++ /dev/null @@ -1,1662 +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 lifecycle from 'vs/base/common/lifecycle'; -import * as _ from 'vs/base/parts/tree/browser/tree'; -import * as model from 'vs/base/parts/tree/browser/treeModel'; -import * as TreeDefaults from 'vs/base/parts/tree/browser/treeDefaults'; -import { Event, Emitter } from 'vs/base/common/event'; -import { timeout } from 'vs/base/common/async'; - -export class FakeRenderer { - - public getHeight(tree: _.ITree, element: any): number { - return 20; - } - - public getTemplateId(tree: _.ITree, element: any): string { - return 'fake'; - } - - public renderTemplate(tree: _.ITree, templateId: string, container: any): any { - return null; - } - - public renderElement(tree: _.ITree, element: any, templateId: string, templateData: any): void { - // noop - } - - public disposeTemplate(tree: _.ITree, templateId: string, templateData: any): void { - // noop - } -} - -class TreeContext implements _.ITreeContext { - - public tree: _.ITree = null!; - public options: _.ITreeOptions = { autoExpandSingleChildren: true }; - public dataSource: _.IDataSource; - public renderer: _.IRenderer; - public controller?: _.IController; - public dnd?: _.IDragAndDrop; - public filter: _.IFilter; - public sorter: _.ISorter; - - constructor(public configuration: _.ITreeConfiguration) { - this.dataSource = configuration.dataSource; - this.renderer = configuration.renderer || new FakeRenderer(); - this.controller = configuration.controller; - this.dnd = configuration.dnd; - this.filter = configuration.filter || new TreeDefaults.DefaultFilter(); - this.sorter = configuration.sorter || new TreeDefaults.DefaultSorter(); - } -} - -class TreeModel extends model.TreeModel { - - constructor(configuration: _.ITreeConfiguration) { - super(new TreeContext(configuration)); - } -} - -class EventCounter { - - private listeners: lifecycle.IDisposable[]; - private _count: number; - - constructor() { - this.listeners = []; - this._count = 0; - } - - public listen(event: Event, fn: ((e: T) => void) | null = null): () => void { - let r = event(data => { - this._count++; - if (fn) { - fn(data); - } - }); - - this.listeners.push(r); - - return () => { - let idx = this.listeners.indexOf(r); - if (idx > -1) { - this.listeners.splice(idx, 1); - r.dispose(); - } - }; - } - - public up(): void { - this._count++; - } - - public get count(): number { - return this._count; - } - - public dispose(): void { - this.listeners = lifecycle.dispose(this.listeners); - this._count = -1; - } -} - -const SAMPLE: any = { - ONE: { id: 'one' }, - - AB: { - id: 'ROOT', children: [ - { - id: 'a', children: [ - { id: 'aa' }, - { id: 'ab' } - ] - }, - { id: 'b' }, - { - id: 'c', children: [ - { id: 'ca' }, - { id: 'cb' } - ] - } - ] - }, - - DEEP: { - id: 'ROOT', children: [ - { - id: 'a', children: [ - { - id: 'x', children: [ - { id: 'xa' }, - { id: 'xb' }, - ] - } - ] - }, - { id: 'b' } - ] - }, - - DEEP2: { - id: 'ROOT', children: [ - { - id: 'a', children: [ - { - id: 'x', children: [ - { id: 'xa' }, - { id: 'xb' }, - ] - }, - { id: 'y' } - ] - }, - { id: 'b' } - ] - } -}; - -class TestDataSource implements _.IDataSource { - public getId(tree: _.ITree, element: any): string { - return element.id; - } - - public hasChildren(tree: _.ITree, element: any): boolean { - return !!element.children; - } - - public getChildren(tree: _.ITree, element: any): Promise { - return Promise.resolve(element.children); - } - - public getParent(tree: _.ITree, element: any): Promise { - throw new Error('Not implemented'); - } -} - -suite('TreeModel', () => { - let model: model.TreeModel; - let counter: EventCounter; - - setup(() => { - counter = new EventCounter(); - model = new TreeModel({ - dataSource: new TestDataSource() - }); - }); - - teardown(() => { - counter.dispose(); - model.dispose(); - }); - - test('setInput, getInput', () => { - model.setInput(SAMPLE.ONE); - assert.equal(model.getInput(), SAMPLE.ONE); - }); - - test('refresh() refreshes all', () => { - return model.setInput(SAMPLE.AB).then(() => { - counter.listen(model.onRefresh); // 1 - counter.listen(model.onDidRefresh); // 1 - counter.listen(model.onDidRefreshItem); // 4 - counter.listen(model.onRefreshItemChildren); // 1 - counter.listen(model.onDidRefreshItemChildren); // 1 - return model.refresh(null); - }).then(() => { - assert.equal(counter.count, 8); - }); - }); - - test('refresh(root) refreshes all', () => { - return model.setInput(SAMPLE.AB).then(() => { - counter.listen(model.onRefresh); // 1 - counter.listen(model.onDidRefresh); // 1 - counter.listen(model.onDidRefreshItem); // 4 - counter.listen(model.onRefreshItemChildren); // 1 - counter.listen(model.onDidRefreshItemChildren); // 1 - return model.refresh(SAMPLE.AB); - }).then(() => { - assert.equal(counter.count, 8); - }); - }); - - test('refresh(root, false) refreshes the root', () => { - return model.setInput(SAMPLE.AB).then(() => { - counter.listen(model.onRefresh); // 1 - counter.listen(model.onDidRefresh); // 1 - counter.listen(model.onDidRefreshItem); // 1 - counter.listen(model.onRefreshItemChildren); // 1 - counter.listen(model.onDidRefreshItemChildren); // 1 - return model.refresh(SAMPLE.AB, false); - }).then(() => { - assert.equal(counter.count, 5); - }); - }); - - test('refresh(collapsed element) does not refresh descendants', () => { - return model.setInput(SAMPLE.AB).then(() => { - counter.listen(model.onRefresh); // 1 - counter.listen(model.onDidRefresh); // 1 - counter.listen(model.onDidRefreshItem); // 1 - counter.listen(model.onRefreshItemChildren); // 0 - counter.listen(model.onDidRefreshItemChildren); // 0 - return model.refresh(SAMPLE.AB.children[0]); - }).then(() => { - assert.equal(counter.count, 3); - }); - }); - - test('refresh(expanded element) refreshes the element and descendants', () => { - return model.setInput(SAMPLE.AB).then(() => { - return model.expand(SAMPLE.AB.children[0]).then(() => { - counter.listen(model.onRefresh); // 1 - counter.listen(model.onDidRefresh); // 1 - counter.listen(model.onDidRefreshItem); // 3 - counter.listen(model.onRefreshItemChildren); // 1 - counter.listen(model.onDidRefreshItemChildren); // 1 - return model.refresh(SAMPLE.AB.children[0]); - }); - }).then(() => { - assert.equal(counter.count, 7); - }); - }); - - test('refresh(element, false) refreshes the element', () => { - return model.setInput(SAMPLE.AB).then(() => { - return model.expand(SAMPLE.AB.children[0]).then(() => { - counter.listen(model.onRefresh); // 1 - counter.listen(model.onDidRefresh); // 1 - counter.listen(model.onDidRefreshItem, item => { // 1 - assert.equal(item.id, 'a'); - counter.up(); - }); - counter.listen(model.onRefreshItemChildren); // 1 - counter.listen(model.onDidRefreshItemChildren); // 1 - return model.refresh(SAMPLE.AB.children[0], false); - }); - }).then(() => { - assert.equal(counter.count, 6); - }); - }); - - test('depths', () => { - return model.setInput(SAMPLE.AB).then(() => { - return model.expandAll(['a', 'c']).then(() => { - counter.listen(model.onDidRefreshItem, item => { - switch (item.id) { - case 'ROOT': assert.equal(item.getDepth(), 0); break; - case 'a': assert.equal(item.getDepth(), 1); break; - case 'aa': assert.equal(item.getDepth(), 2); break; - case 'ab': assert.equal(item.getDepth(), 2); break; - case 'b': assert.equal(item.getDepth(), 1); break; - case 'c': assert.equal(item.getDepth(), 1); break; - case 'ca': assert.equal(item.getDepth(), 2); break; - case 'cb': assert.equal(item.getDepth(), 2); break; - default: return; - } - counter.up(); - }); - - return model.refresh(); - }); - }).then(() => { - assert.equal(counter.count, 16); - }); - }); - - test('intersections', () => { - return model.setInput(SAMPLE.AB).then(() => { - return model.expandAll(['a', 'c']).then(() => { - // going internals - const r = (model).registry; - - assert(r.getItem('a').intersects(r.getItem('a'))); - assert(r.getItem('a').intersects(r.getItem('aa'))); - assert(r.getItem('a').intersects(r.getItem('ab'))); - assert(r.getItem('aa').intersects(r.getItem('a'))); - assert(r.getItem('ab').intersects(r.getItem('a'))); - assert(!r.getItem('aa').intersects(r.getItem('ab'))); - assert(!r.getItem('a').intersects(r.getItem('b'))); - assert(!r.getItem('a').intersects(r.getItem('c'))); - assert(!r.getItem('a').intersects(r.getItem('ca'))); - assert(!r.getItem('aa').intersects(r.getItem('ca'))); - }); - }); - }); -}); - -suite('TreeModel - TreeNavigator', () => { - let model: model.TreeModel; - let counter: EventCounter; - - setup(() => { - counter = new EventCounter(); - model = new TreeModel({ - dataSource: new TestDataSource() - }); - }); - - teardown(() => { - counter.dispose(); - model.dispose(); - }); - - test('next()', () => { - return model.setInput(SAMPLE.AB).then(() => { - const nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'a'); - assert.equal(nav.next()!.id, 'b'); - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.next() && false, null); - }); - }); - - test('previous()', () => { - return model.setInput(SAMPLE.AB).then(() => { - const nav = model.getNavigator(); - - nav.next(); - nav.next(); - - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.previous()!.id, 'b'); - assert.equal(nav.previous()!.id, 'a'); - assert.equal(nav.previous() && false, null); - }); - }); - - test('parent()', () => { - return model.setInput(SAMPLE.AB).then(() => { - return model.expandAll([{ id: 'a' }, { id: 'c' }]).then(() => { - const nav = model.getNavigator(); - - assert.equal(nav.next()!.id, 'a'); - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.parent()!.id, 'a'); - - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'ab'); - assert.equal(nav.parent()!.id, 'a'); - - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'ab'); - assert.equal(nav.next()!.id, 'b'); - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.next()!.id, 'ca'); - assert.equal(nav.parent()!.id, 'c'); - - assert.equal(nav.parent() && false, null); - }); - }); - }); - - test('next() - scoped', () => { - return model.setInput(SAMPLE.AB).then(() => { - const nav = model.getNavigator(SAMPLE.AB.children[0]); - return model.expand({ id: 'a' }).then(() => { - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'ab'); - assert.equal(nav.next() && false, null); - }); - }); - }); - - test('previous() - scoped', () => { - return model.setInput(SAMPLE.AB).then(() => { - const nav = model.getNavigator(SAMPLE.AB.children[0]); - return model.expand({ id: 'a' }).then(() => { - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'ab'); - assert.equal(nav.previous()!.id, 'aa'); - assert.equal(nav.previous() && false, null); - }); - }); - }); - - test('parent() - scoped', () => { - return model.setInput(SAMPLE.AB).then(() => { - return model.expandAll([{ id: 'a' }, { id: 'c' }]).then(() => { - const nav = model.getNavigator(SAMPLE.AB.children[0]); - - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'ab'); - assert.equal(nav.parent() && false, null); - }); - }); - }); - - test('next() - non sub tree only', () => { - return model.setInput(SAMPLE.AB).then(() => { - const nav = model.getNavigator(SAMPLE.AB.children[0], false); - return model.expand({ id: 'a' }).then(() => { - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'ab'); - assert.equal(nav.next()!.id, 'b'); - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.next() && false, null); - }); - }); - }); - - test('previous() - non sub tree only', () => { - return model.setInput(SAMPLE.AB).then(() => { - const nav = model.getNavigator(SAMPLE.AB.children[0], false); - return model.expand({ id: 'a' }).then(() => { - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'ab'); - assert.equal(nav.next()!.id, 'b'); - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.previous()!.id, 'b'); - assert.equal(nav.previous()!.id, 'ab'); - assert.equal(nav.previous()!.id, 'aa'); - assert.equal(nav.previous()!.id, 'a'); - assert.equal(nav.previous() && false, null); - }); - }); - }); - - test('parent() - non sub tree only', () => { - return model.setInput(SAMPLE.AB).then(() => { - return model.expandAll([{ id: 'a' }, { id: 'c' }]).then(() => { - const nav = model.getNavigator(SAMPLE.AB.children[0], false); - - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'ab'); - assert.equal(nav.parent()!.id, 'a'); - assert.equal(nav.parent() && false, null); - }); - }); - }); - - test('deep next() - scoped', () => { - return model.setInput(SAMPLE.DEEP).then(() => { - return model.expand(SAMPLE.DEEP.children[0]).then(() => { - return model.expand(SAMPLE.DEEP.children[0].children[0]).then(() => { - const nav = model.getNavigator(SAMPLE.DEEP.children[0].children[0]); - assert.equal(nav.next()!.id, 'xa'); - assert.equal(nav.next()!.id, 'xb'); - assert.equal(nav.next() && false, null); - }); - }); - }); - }); - - test('deep previous() - scoped', () => { - return model.setInput(SAMPLE.DEEP).then(() => { - return model.expand(SAMPLE.DEEP.children[0]).then(() => { - return model.expand(SAMPLE.DEEP.children[0].children[0]).then(() => { - const nav = model.getNavigator(SAMPLE.DEEP.children[0].children[0]); - assert.equal(nav.next()!.id, 'xa'); - assert.equal(nav.next()!.id, 'xb'); - assert.equal(nav.previous()!.id, 'xa'); - assert.equal(nav.previous() && false, null); - }); - }); - }); - }); - - test('last()', () => { - return model.setInput(SAMPLE.AB).then(() => { - return model.expandAll([{ id: 'a' }, { id: 'c' }]).then(() => { - const nav = model.getNavigator(); - assert.equal(nav.last()!.id, 'cb'); - }); - }); - }); -}); - -suite('TreeModel - Expansion', () => { - let model: model.TreeModel; - let counter: EventCounter; - - setup(() => { - counter = new EventCounter(); - model = new TreeModel({ - dataSource: new TestDataSource() - }); - }); - - teardown(() => { - counter.dispose(); - model.dispose(); - }); - - test('collapse, expand', () => { - return model.setInput(SAMPLE.AB).then(() => { - counter.listen(model.onExpandItem, (e) => { - assert.equal(e.item.id, 'a'); - const nav = model.getNavigator(e.item); - assert.equal(nav.next() && false, null); - }); - - counter.listen(model.onDidExpandItem, (e) => { - assert.equal(e.item.id, 'a'); - const nav = model.getNavigator(e.item); - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'ab'); - assert.equal(nav.next() && false, null); - }); - - assert(!model.isExpanded(SAMPLE.AB.children[0])); - - let nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'a'); - assert.equal(nav.next()!.id, 'b'); - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.next() && false, null); - - assert.equal(model.getExpandedElements().length, 0); - - return model.expand(SAMPLE.AB.children[0]).then(() => { - assert(model.isExpanded(SAMPLE.AB.children[0])); - - nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'a'); - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'ab'); - assert.equal(nav.next()!.id, 'b'); - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.next() && false, null); - - const expandedElements = model.getExpandedElements(); - assert.equal(expandedElements.length, 1); - assert.equal(expandedElements[0].id, 'a'); - - assert.equal(counter.count, 2); - }); - }); - }); - - test('toggleExpansion', () => { - return model.setInput(SAMPLE.AB).then(() => { - assert(!model.isExpanded(SAMPLE.AB.children[0])); - - return model.toggleExpansion(SAMPLE.AB.children[0]).then(() => { - assert(model.isExpanded(SAMPLE.AB.children[0])); - assert(!model.isExpanded(SAMPLE.AB.children[0].children[0])); - - return model.toggleExpansion(SAMPLE.AB.children[0].children[0]).then(() => { - assert(!model.isExpanded(SAMPLE.AB.children[0].children[0])); - - return model.toggleExpansion(SAMPLE.AB.children[0]).then(() => { - assert(!model.isExpanded(SAMPLE.AB.children[0])); - }); - }); - }); - }); - }); - - test('collapseAll', () => { - return model.setInput(SAMPLE.DEEP2).then(() => { - return model.expand(SAMPLE.DEEP2.children[0]).then(() => { - return model.expand(SAMPLE.DEEP2.children[0].children[0]).then(() => { - - assert(model.isExpanded(SAMPLE.DEEP2.children[0])); - assert(model.isExpanded(SAMPLE.DEEP2.children[0].children[0])); - - return model.collapseAll().then(() => { - assert(!model.isExpanded(SAMPLE.DEEP2.children[0])); - - return model.expand(SAMPLE.DEEP2.children[0]).then(() => { - assert(!model.isExpanded(SAMPLE.DEEP2.children[0].children[0])); - }); - }); - }); - }); - }); - }); - - test('auto expand single child folders', () => { - return model.setInput(SAMPLE.DEEP).then(() => { - return model.expand(SAMPLE.DEEP.children[0]).then(() => { - assert(model.isExpanded(SAMPLE.DEEP.children[0])); - assert(model.isExpanded(SAMPLE.DEEP.children[0].children[0])); - }); - }); - }); - - test('expand can trigger refresh', () => { - // MUnit.expect(16); - return model.setInput(SAMPLE.AB).then(() => { - - assert(!model.isExpanded(SAMPLE.AB.children[0])); - - let nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'a'); - assert.equal(nav.next()!.id, 'b'); - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.next() && false, null); - - const f: () => void = counter.listen(model.onRefreshItemChildren, (e) => { - assert.equal(e.item.id, 'a'); - f(); - }); - - const g: () => void = counter.listen(model.onDidRefreshItemChildren, (e) => { - assert.equal(e.item.id, 'a'); - g(); - }); - - return model.expand(SAMPLE.AB.children[0]).then(() => { - assert(model.isExpanded(SAMPLE.AB.children[0])); - - nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'a'); - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'ab'); - assert.equal(nav.next()!.id, 'b'); - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.next() && false, null); - - assert.equal(counter.count, 2); - }); - }); - }); - - test('top level collapsed', () => { - return model.setInput(SAMPLE.AB).then(() => { - return model.collapseAll([{ id: 'a' }, { id: 'b' }, { id: 'c' }]).then(() => { - const nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'a'); - assert.equal(nav.next()!.id, 'b'); - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.previous()!.id, 'b'); - assert.equal(nav.previous()!.id, 'a'); - assert.equal(nav.previous() && false, null); - }); - }); - }); - - test('shouldAutoexpand', () => { - // setup - const model = new TreeModel({ - dataSource: { - getId: (_, e) => e, - hasChildren: (_, e) => true, - getChildren: (_, e) => { - if (e === 'root') { return Promise.resolve(['a', 'b', 'c']); } - if (e === 'b') { return Promise.resolve(['b1']); } - return Promise.resolve([]); - }, - getParent: (_, e): Promise => { throw new Error('not implemented'); }, - shouldAutoexpand: (_, e) => e === 'b' - } - }); - - return model.setInput('root').then(() => { - return model.refresh('root', true); - }).then(() => { - assert(!model.isExpanded('a')); - assert(model.isExpanded('b')); - assert(!model.isExpanded('c')); - }); - }); -}); - -class TestFilter implements _.IFilter { - - public fn: (element: any) => boolean; - - constructor() { - this.fn = () => true; - } - - public isVisible(tree: _.ITree, element: any): boolean { - return this.fn(element); - } -} - -suite('TreeModel - Filter', () => { - let model: model.TreeModel; - let counter: EventCounter; - let filter: TestFilter; - - setup(() => { - counter = new EventCounter(); - filter = new TestFilter(); - model = new TreeModel({ - dataSource: new TestDataSource(), - filter: filter - }); - }); - - teardown(() => { - counter.dispose(); - model.dispose(); - }); - - test('no filter', () => { - return model.setInput(SAMPLE.AB).then(() => { - - return model.expandAll([{ id: 'a' }, { id: 'c' }]).then(() => { - const nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'a'); - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'ab'); - assert.equal(nav.next()!.id, 'b'); - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.next()!.id, 'ca'); - assert.equal(nav.next()!.id, 'cb'); - - assert.equal(nav.previous()!.id, 'ca'); - assert.equal(nav.previous()!.id, 'c'); - assert.equal(nav.previous()!.id, 'b'); - assert.equal(nav.previous()!.id, 'ab'); - assert.equal(nav.previous()!.id, 'aa'); - assert.equal(nav.previous()!.id, 'a'); - assert.equal(nav.previous() && false, null); - }); - }); - }); - - test('filter all', () => { - filter.fn = () => false; - - return model.setInput(SAMPLE.AB).then(() => { - return model.refresh().then(() => { - const nav = model.getNavigator(); - assert.equal(nav.next() && false, null); - }); - }); - }); - - test('simple filter', () => { - // hide elements that do not start with 'a' - filter.fn = (e) => e.id[0] === 'a'; - - return model.setInput(SAMPLE.AB).then(() => { - return model.expand({ id: 'a' }).then(() => { - - const nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'a'); - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'ab'); - assert.equal(nav.previous()!.id, 'aa'); - assert.equal(nav.previous()!.id, 'a'); - assert.equal(nav.previous() && false, null); - }); - }); - }); - - test('simple filter 2', () => { - // hide 'ab' - filter.fn = (e) => e.id !== 'ab'; - - return model.setInput(SAMPLE.AB).then(() => { - return model.expand({ id: 'a' }).then(() => { - const nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'a'); - assert.equal(nav.next()!.id, 'aa'); - assert.equal(nav.next()!.id, 'b'); - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.next() && false, null); - }); - }); - }); - - test('simple filter, opposite', () => { - // hide elements that start with 'a' - filter.fn = (e) => e.id[0] !== 'a'; - - return model.setInput(SAMPLE.AB).then(() => { - return model.expand({ id: 'c' }).then(() => { - - const nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'b'); - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.next()!.id, 'ca'); - assert.equal(nav.next()!.id, 'cb'); - assert.equal(nav.previous()!.id, 'ca'); - assert.equal(nav.previous()!.id, 'c'); - assert.equal(nav.previous()!.id, 'b'); - assert.equal(nav.previous() && false, null); - }); - }); - }); - - test('simple filter, mischieving', () => { - // hide the element 'a' - filter.fn = (e) => e.id !== 'a'; - - return model.setInput(SAMPLE.AB).then(() => { - return model.expand({ id: 'c' }).then(() => { - - const nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'b'); - assert.equal(nav.next()!.id, 'c'); - assert.equal(nav.next()!.id, 'ca'); - assert.equal(nav.next()!.id, 'cb'); - assert.equal(nav.previous()!.id, 'ca'); - assert.equal(nav.previous()!.id, 'c'); - assert.equal(nav.previous()!.id, 'b'); - assert.equal(nav.previous() && false, null); - }); - }); - }); - - test('simple filter & previous', () => { - // hide 'b' - filter.fn = (e) => e.id !== 'b'; - - return model.setInput(SAMPLE.AB).then(() => { - const nav = model.getNavigator({ id: 'c' }, false); - assert.equal(nav.previous()!.id, 'a'); - assert.equal(nav.previous() && false, null); - }); - }); -}); - -suite('TreeModel - Traits', () => { - let model: model.TreeModel; - let counter: EventCounter; - - setup(() => { - counter = new EventCounter(); - model = new TreeModel({ - dataSource: new TestDataSource() - }); - }); - - teardown(() => { - counter.dispose(); - model.dispose(); - }); - - test('Selection', () => { - return model.setInput(SAMPLE.AB).then(() => { - assert.equal(model.getSelection().length, 0); - model.select(SAMPLE.AB.children[1]); - assert(model.isSelected(SAMPLE.AB.children[1])); - assert.equal(model.getSelection().length, 1); - model.select(SAMPLE.AB.children[0]); - assert(model.isSelected(SAMPLE.AB.children[0])); - assert.equal(model.getSelection().length, 2); - model.select(SAMPLE.AB.children[2]); - assert(model.isSelected(SAMPLE.AB.children[2])); - assert.equal(model.getSelection().length, 3); - model.deselect(SAMPLE.AB.children[0]); - assert(!model.isSelected(SAMPLE.AB.children[0])); - assert.equal(model.getSelection().length, 2); - model.setSelection([]); - assert(!model.isSelected(SAMPLE.AB.children[0])); - assert(!model.isSelected(SAMPLE.AB.children[1])); - assert(!model.isSelected(SAMPLE.AB.children[2])); - assert.equal(model.getSelection().length, 0); - model.selectAll([SAMPLE.AB.children[0], SAMPLE.AB.children[1], SAMPLE.AB.children[2]]); - assert.equal(model.getSelection().length, 3); - model.select(SAMPLE.AB.children[0]); - assert.equal(model.getSelection().length, 3); - model.deselectAll([SAMPLE.AB.children[0], SAMPLE.AB.children[1], SAMPLE.AB.children[2]]); - assert.equal(model.getSelection().length, 0); - model.deselect(SAMPLE.AB.children[0]); - assert.equal(model.getSelection().length, 0); - - model.setSelection([SAMPLE.AB.children[0]]); - assert.equal(model.getSelection().length, 1); - assert(model.isSelected(SAMPLE.AB.children[0])); - assert(!model.isSelected(SAMPLE.AB.children[1])); - assert(!model.isSelected(SAMPLE.AB.children[2])); - - model.setSelection([SAMPLE.AB.children[0], SAMPLE.AB.children[1], SAMPLE.AB.children[2]]); - assert.equal(model.getSelection().length, 3); - assert(model.isSelected(SAMPLE.AB.children[0])); - assert(model.isSelected(SAMPLE.AB.children[1])); - assert(model.isSelected(SAMPLE.AB.children[2])); - - model.setSelection([SAMPLE.AB.children[1], SAMPLE.AB.children[2]]); - assert.equal(model.getSelection().length, 2); - assert(!model.isSelected(SAMPLE.AB.children[0])); - assert(model.isSelected(SAMPLE.AB.children[1])); - assert(model.isSelected(SAMPLE.AB.children[2])); - - model.setSelection([]); - assert.deepEqual(model.getSelection(), []); - assert.equal(model.getSelection().length, 0); - assert(!model.isSelected(SAMPLE.AB.children[0])); - assert(!model.isSelected(SAMPLE.AB.children[1])); - assert(!model.isSelected(SAMPLE.AB.children[2])); - - model.selectNext(); - assert.equal(model.getSelection().length, 1); - assert(model.isSelected(SAMPLE.AB.children[0])); - - model.selectNext(); - assert.equal(model.getSelection().length, 1); - assert(model.isSelected(SAMPLE.AB.children[1])); - - model.selectNext(); - assert.equal(model.getSelection().length, 1); - assert(model.isSelected(SAMPLE.AB.children[2])); - - model.selectNext(); - assert.equal(model.getSelection().length, 1); - assert(model.isSelected(SAMPLE.AB.children[2])); - - model.selectPrevious(); - assert.equal(model.getSelection().length, 1); - assert(model.isSelected(SAMPLE.AB.children[1])); - - model.selectPrevious(); - assert.equal(model.getSelection().length, 1); - assert(model.isSelected(SAMPLE.AB.children[0])); - - model.selectPrevious(); - assert.equal(model.getSelection().length, 1); - assert(model.isSelected(SAMPLE.AB.children[0])); - - model.selectNext(2); - assert.equal(model.getSelection().length, 1); - assert(model.isSelected(SAMPLE.AB.children[2])); - - model.selectPrevious(4); - assert.equal(model.getSelection().length, 1); - assert(model.isSelected(SAMPLE.AB.children[0])); - - assert.equal(model.isSelected(SAMPLE.AB.children[0]), true); - assert.equal(model.isSelected(SAMPLE.AB.children[2]), false); - }); - }); - - test('Focus', () => { - return model.setInput(SAMPLE.AB).then(() => { - assert(!model.getFocus()); - model.setFocus(SAMPLE.AB.children[1]); - assert(model.isFocused(SAMPLE.AB.children[1])); - assert(model.getFocus()); - model.setFocus(SAMPLE.AB.children[0]); - assert(model.isFocused(SAMPLE.AB.children[0])); - assert(model.getFocus()); - model.setFocus(SAMPLE.AB.children[2]); - assert(model.isFocused(SAMPLE.AB.children[2])); - assert(model.getFocus()); - model.setFocus(); - assert(!model.isFocused(SAMPLE.AB.children[0])); - assert(!model.isFocused(SAMPLE.AB.children[1])); - assert(!model.isFocused(SAMPLE.AB.children[2])); - assert(!model.getFocus()); - - model.setFocus(SAMPLE.AB.children[0]); - assert(model.getFocus()); - assert(model.isFocused(SAMPLE.AB.children[0])); - assert(!model.isFocused(SAMPLE.AB.children[1])); - assert(!model.isFocused(SAMPLE.AB.children[2])); - - model.setFocus(); - assert(!model.getFocus()); - assert(!model.isFocused(SAMPLE.AB.children[0])); - assert(!model.isFocused(SAMPLE.AB.children[1])); - assert(!model.isFocused(SAMPLE.AB.children[2])); - - model.focusNext(); - assert(model.getFocus()); - assert(model.isFocused(SAMPLE.AB.children[0])); - - model.focusNext(); - assert(model.getFocus()); - assert(model.isFocused(SAMPLE.AB.children[1])); - - model.focusNext(); - assert(model.getFocus()); - assert(model.isFocused(SAMPLE.AB.children[2])); - - model.focusNext(); - assert(model.getFocus()); - assert(model.isFocused(SAMPLE.AB.children[2])); - - model.focusPrevious(); - assert(model.getFocus()); - assert(model.isFocused(SAMPLE.AB.children[1])); - - model.focusPrevious(); - assert(model.getFocus()); - assert(model.isFocused(SAMPLE.AB.children[0])); - - model.focusPrevious(); - assert(model.getFocus()); - assert(model.isFocused(SAMPLE.AB.children[0])); - - model.focusNext(2); - assert(model.getFocus()); - assert(model.isFocused(SAMPLE.AB.children[2])); - - model.focusPrevious(4); - assert(model.getFocus()); - assert(model.isFocused(SAMPLE.AB.children[0])); - - assert.equal(model.isFocused(SAMPLE.AB.children[0]), true); - assert.equal(model.isFocused(SAMPLE.AB.children[2]), false); - - model.focusFirst(); - assert(model.isFocused(SAMPLE.AB.children[0])); - model.focusNth(0); - assert(model.isFocused(SAMPLE.AB.children[0])); - model.focusNth(1); - assert(model.isFocused(SAMPLE.AB.children[1])); - }); - }); - - test('Highlight', () => { - return model.setInput(SAMPLE.AB).then(() => { - assert(!model.getHighlight()); - model.setHighlight(SAMPLE.AB.children[1]); - assert(model.isHighlighted(SAMPLE.AB.children[1])); - assert(model.getHighlight()); - model.setHighlight(SAMPLE.AB.children[0]); - assert(model.isHighlighted(SAMPLE.AB.children[0])); - assert(model.getHighlight()); - model.setHighlight(SAMPLE.AB.children[2]); - assert(model.isHighlighted(SAMPLE.AB.children[2])); - assert(model.getHighlight()); - model.setHighlight(); - assert(!model.isHighlighted(SAMPLE.AB.children[0])); - assert(!model.isHighlighted(SAMPLE.AB.children[1])); - assert(!model.isHighlighted(SAMPLE.AB.children[2])); - assert(!model.getHighlight()); - - model.setHighlight(SAMPLE.AB.children[0]); - assert(model.getHighlight()); - assert(model.isHighlighted(SAMPLE.AB.children[0])); - assert(!model.isHighlighted(SAMPLE.AB.children[1])); - assert(!model.isHighlighted(SAMPLE.AB.children[2])); - - assert.equal(model.isHighlighted(SAMPLE.AB.children[0]), true); - assert.equal(model.isHighlighted(SAMPLE.AB.children[2]), false); - - model.setHighlight(); - assert(!model.getHighlight()); - assert(!model.isHighlighted(SAMPLE.AB.children[0])); - assert(!model.isHighlighted(SAMPLE.AB.children[1])); - assert(!model.isHighlighted(SAMPLE.AB.children[2])); - }); - }); -}); - -class DynamicModel implements _.IDataSource { - - private data: any; - public promiseFactory: { (): Promise; } | null; - - private readonly _onGetChildren = new Emitter(); - readonly onGetChildren: Event = this._onGetChildren.event; - - private readonly _onDidGetChildren = new Emitter(); - readonly onDidGetChildren: Event = this._onDidGetChildren.event; - - constructor() { - this.data = { root: [] }; - this.promiseFactory = null; - } - - public addChild(parent: string, child: string): void { - if (!this.data[parent]) { - this.data[parent] = []; - } - this.data[parent].push(child); - } - - public removeChild(parent: string, child: string): void { - this.data[parent].splice(this.data[parent].indexOf(child), 1); - if (this.data[parent].length === 0) { - delete this.data[parent]; - } - } - - public move(element: string, oldParent: string, newParent: string): void { - this.removeChild(oldParent, element); - this.addChild(newParent, element); - } - - public rename(parent: string, oldName: string, newName: string): void { - this.removeChild(parent, oldName); - this.addChild(parent, newName); - } - - public getId(tree: _.ITree, element: any): string { - return element; - } - - public hasChildren(tree: _.ITree, element: any): boolean { - return !!this.data[element]; - } - - public getChildren(tree: _.ITree, element: any): Promise { - this._onGetChildren.fire(element); - const result = this.promiseFactory ? this.promiseFactory() : Promise.resolve(null); - return result.then(() => { - this._onDidGetChildren.fire(element); - return Promise.resolve(this.data[element]); - }); - } - - public getParent(tree: _.ITree, element: any): Promise { - throw new Error('Not implemented'); - } -} - -suite('TreeModel - Dynamic data model', () => { - let model: model.TreeModel; - let dataModel: DynamicModel; - let counter: EventCounter; - - setup(() => { - counter = new EventCounter(); - dataModel = new DynamicModel(); - model = new TreeModel({ - dataSource: dataModel, - }); - }); - - teardown(() => { - counter.dispose(); - model.dispose(); - }); - - test('items get property disposed', () => { - dataModel.addChild('root', 'grandfather'); - dataModel.addChild('grandfather', 'father'); - dataModel.addChild('father', 'son'); - dataModel.addChild('father', 'daughter'); - dataModel.addChild('son', 'baby'); - - return model.setInput('root').then(() => { - return model.expandAll(['grandfather', 'father', 'son']).then(() => { - dataModel.removeChild('grandfather', 'father'); - - const items = ['baby', 'son', 'daughter', 'father']; - let times = 0; - counter.listen(model.onDidDisposeItem, item => { - assert.equal(items[times++], item.id); - }); - - return model.refresh().then(() => { - assert.equal(times, items.length); - assert.equal(counter.count, 4); - }); - }); - }); - }); - - test('addChild, removeChild, collapse', () => { - dataModel.addChild('root', 'super'); - dataModel.addChild('root', 'hyper'); - dataModel.addChild('root', 'mega'); - - return model.setInput('root').then(() => { - let nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'super'); - assert.equal(nav.next()!.id, 'hyper'); - assert.equal(nav.next()!.id, 'mega'); - assert.equal(nav.next() && false, null); - - dataModel.removeChild('root', 'hyper'); - return model.refresh().then(() => { - nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'super'); - assert.equal(nav.next()!.id, 'mega'); - assert.equal(nav.next() && false, null); - - dataModel.addChild('mega', 'micro'); - dataModel.addChild('mega', 'nano'); - dataModel.addChild('mega', 'pico'); - - return model.refresh().then(() => { - return model.expand('mega').then(() => { - nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'super'); - assert.equal(nav.next()!.id, 'mega'); - assert.equal(nav.next()!.id, 'micro'); - assert.equal(nav.next()!.id, 'nano'); - assert.equal(nav.next()!.id, 'pico'); - assert.equal(nav.next() && false, null); - - model.collapse('mega'); - nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'super'); - assert.equal(nav.next()!.id, 'mega'); - assert.equal(nav.next() && false, null); - }); - }); - }); - }); - }); - - test('move', () => { - dataModel.addChild('root', 'super'); - dataModel.addChild('super', 'apples'); - dataModel.addChild('super', 'bananas'); - dataModel.addChild('super', 'pears'); - dataModel.addChild('root', 'hyper'); - dataModel.addChild('root', 'mega'); - - return model.setInput('root').then(() => { - - return model.expand('super').then(() => { - - let nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'super'); - assert.equal(nav.next()!.id, 'apples'); - assert.equal(nav.next()!.id, 'bananas'); - assert.equal(nav.next()!.id, 'pears'); - assert.equal(nav.next()!.id, 'hyper'); - assert.equal(nav.next()!.id, 'mega'); - assert.equal(nav.next() && false, null); - - dataModel.move('bananas', 'super', 'hyper'); - dataModel.move('apples', 'super', 'mega'); - - return model.refresh().then(() => { - - return model.expandAll(['hyper', 'mega']).then(() => { - nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'super'); - assert.equal(nav.next()!.id, 'pears'); - assert.equal(nav.next()!.id, 'hyper'); - assert.equal(nav.next()!.id, 'bananas'); - assert.equal(nav.next()!.id, 'mega'); - assert.equal(nav.next()!.id, 'apples'); - assert.equal(nav.next() && false, null); - }); - }); - }); - }); - }); - - test('refreshing grandfather recursively should not refresh collapsed father\'s children immediately', () => { - dataModel.addChild('root', 'grandfather'); - dataModel.addChild('grandfather', 'father'); - dataModel.addChild('father', 'son'); - - return model.setInput('root').then(() => { - return model.expand('grandfather').then(() => { - return model.collapse('father').then(() => { - let times = 0; - let listener = dataModel.onGetChildren((element) => { - times++; - assert.equal(element, 'grandfather'); - }); - - return model.refresh('grandfather').then(() => { - assert.equal(times, 1); - listener.dispose(); - - listener = dataModel.onGetChildren((element) => { - times++; - assert.equal(element, 'father'); - }); - - return model.expand('father').then(() => { - assert.equal(times, 2); - listener.dispose(); - }); - }); - }); - }); - }); - }); - - test('simultaneously refreshing two disjoint elements should parallelize the refreshes', () => { - dataModel.addChild('root', 'father'); - dataModel.addChild('root', 'mother'); - dataModel.addChild('father', 'son'); - dataModel.addChild('mother', 'daughter'); - - return model.setInput('root').then(() => { - return model.expand('father').then(() => { - return model.expand('mother').then(() => { - - let nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'father'); - assert.equal(nav.next()!.id, 'son'); - assert.equal(nav.next()!.id, 'mother'); - assert.equal(nav.next()!.id, 'daughter'); - assert.equal(nav.next() && false, null); - - dataModel.removeChild('father', 'son'); - dataModel.removeChild('mother', 'daughter'); - dataModel.addChild('father', 'brother'); - dataModel.addChild('mother', 'sister'); - - dataModel.promiseFactory = () => { return timeout(0); }; - - let getTimes = 0; - let gotTimes = 0; - const getListener = dataModel.onGetChildren((element) => { getTimes++; }); - const gotListener = dataModel.onDidGetChildren((element) => { gotTimes++; }); - - const p1 = model.refresh('father'); - assert.equal(getTimes, 1); - assert.equal(gotTimes, 0); - - const p2 = model.refresh('mother'); - assert.equal(getTimes, 2); - assert.equal(gotTimes, 0); - - return Promise.all([p1, p2]).then(() => { - assert.equal(getTimes, 2); - assert.equal(gotTimes, 2); - - nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'father'); - assert.equal(nav.next()!.id, 'brother'); - assert.equal(nav.next()!.id, 'mother'); - assert.equal(nav.next()!.id, 'sister'); - assert.equal(nav.next() && false, null); - - getListener.dispose(); - gotListener.dispose(); - }); - }); - }); - }); - }); - - test('simultaneously recursively refreshing two intersecting elements should concatenate the refreshes - ancestor first', () => { - dataModel.addChild('root', 'grandfather'); - dataModel.addChild('grandfather', 'father'); - dataModel.addChild('father', 'son'); - - return model.setInput('root').then(() => { - return model.expand('grandfather').then(() => { - return model.expand('father').then(() => { - let nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'grandfather'); - assert.equal(nav.next()!.id, 'father'); - assert.equal(nav.next()!.id, 'son'); - assert.equal(nav.next() && false, null); - - let refreshTimes = 0; - counter.listen(model.onDidRefreshItem, (e) => { refreshTimes++; }); - - let getTimes = 0; - const getListener = dataModel.onGetChildren((element) => { getTimes++; }); - - let gotTimes = 0; - const gotListener = dataModel.onDidGetChildren((element) => { gotTimes++; }); - - const p1Completes: Array<(value?: any) => void> = []; - dataModel.promiseFactory = () => { return new Promise((c) => { p1Completes.push(c); }); }; - - model.refresh('grandfather').then(() => { - // just a single get - assert.equal(refreshTimes, 1); // (+1) grandfather - assert.equal(getTimes, 1); - assert.equal(gotTimes, 0); - - // unblock the first get - p1Completes.shift()!(); - - // once the first get is unblocked, the second get should appear - assert.equal(refreshTimes, 2); // (+1) first father refresh - assert.equal(getTimes, 2); - assert.equal(gotTimes, 1); - - let p2Complete: () => void; - dataModel.promiseFactory = () => { return new Promise((c) => { p2Complete = c; }); }; - const p2 = model.refresh('father'); - - // same situation still - assert.equal(refreshTimes, 3); // (+1) second father refresh - assert.equal(getTimes, 2); - assert.equal(gotTimes, 1); - - // unblock the second get - p1Completes.shift()!(); - - // the third get should have appeared, it should've been waiting for the second one - assert.equal(refreshTimes, 4); // (+1) first son request - assert.equal(getTimes, 3); - assert.equal(gotTimes, 2); - - p2Complete!(); - - // all good - assert.equal(refreshTimes, 5); // (+1) second son request - assert.equal(getTimes, 3); - assert.equal(gotTimes, 3); - - return p2.then(() => { - nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'grandfather'); - assert.equal(nav.next()!.id, 'father'); - assert.equal(nav.next()!.id, 'son'); - assert.equal(nav.next() && false, null); - - getListener.dispose(); - gotListener.dispose(); - }); - }); - }); - }); - }); - }); - - test('refreshing an empty element that adds children should still keep it collapsed', () => { - dataModel.addChild('root', 'grandfather'); - dataModel.addChild('grandfather', 'father'); - - return model.setInput('root').then(() => { - return model.expand('grandfather').then(() => { - return model.expand('father').then(() => { - assert(!model.isExpanded('father')); - - dataModel.addChild('father', 'son'); - - return model.refresh('father').then(() => { - assert(!model.isExpanded('father')); - }); - }); - }); - }); - }); - - test('refreshing a collapsed element that adds children should still keep it collapsed', () => { - dataModel.addChild('root', 'grandfather'); - dataModel.addChild('grandfather', 'father'); - dataModel.addChild('father', 'son'); - - return model.setInput('root').then(() => { - return model.expand('grandfather').then(() => { - return model.expand('father').then(() => { - return model.collapse('father').then(() => { - assert(!model.isExpanded('father')); - - dataModel.addChild('father', 'daughter'); - - return model.refresh('father').then(() => { - assert(!model.isExpanded('father')); - }); - }); - }); - }); - }); - }); - - test('recursively refreshing an ancestor of an expanded element, should keep that element expanded', () => { - dataModel.addChild('root', 'grandfather'); - dataModel.addChild('grandfather', 'father'); - dataModel.addChild('father', 'son'); - - return model.setInput('root').then(() => { - return model.expand('grandfather').then(() => { - return model.expand('father').then(() => { - assert(model.isExpanded('grandfather')); - assert(model.isExpanded('father')); - - return model.refresh('grandfather').then(() => { - assert(model.isExpanded('grandfather')); - assert(model.isExpanded('father')); - }); - }); - }); - }); - }); - - test('recursively refreshing an ancestor of a collapsed element, should keep that element collapsed', () => { - dataModel.addChild('root', 'grandfather'); - dataModel.addChild('grandfather', 'father'); - dataModel.addChild('father', 'son'); - - return model.setInput('root').then(() => { - return model.expand('grandfather').then(() => { - return model.expand('father').then(() => { - return model.collapse('father').then(() => { - assert(model.isExpanded('grandfather')); - assert(!model.isExpanded('father')); - - return model.refresh('grandfather').then(() => { - assert(model.isExpanded('grandfather')); - assert(!model.isExpanded('father')); - }); - }); - }); - }); - }); - }); - - test('Bug 10855:[explorer] quickly deleting things causes NPE in tree - intersectsLock should always be called when trying to unlock', () => { - dataModel.addChild('root', 'father'); - dataModel.addChild('father', 'son'); - dataModel.addChild('root', 'mother'); - dataModel.addChild('mother', 'daughter'); - - return model.setInput('root').then(() => { - - // delay expansions and refreshes - dataModel.promiseFactory = () => { return timeout(0); }; - - const promises: Promise[] = []; - - promises.push(model.expand('father')); - dataModel.removeChild('root', 'father'); - promises.push(model.refresh('root')); - - promises.push(model.expand('mother')); - dataModel.removeChild('root', 'mother'); - promises.push(model.refresh('root')); - - return Promise.all(promises).then(() => { - assert(true, 'all good'); - }, (errs) => { - assert(false, 'should not fail'); - }); - }); - }); -}); - -suite('TreeModel - bugs', () => { - let counter: EventCounter; - - setup(() => { - counter = new EventCounter(); - }); - - teardown(() => { - counter.dispose(); - }); - - /** - * This bug occurs when an item is expanded right during its removal - */ - test('Bug 10566:[tree] build viewlet is broken after some time', () => { - // setup - let model = new TreeModel({ - dataSource: { - getId: (_, e) => e, - hasChildren: (_, e) => e === 'root' || e === 'bart', - getChildren: (_, e) => { - if (e === 'root') { return getRootChildren(); } - if (e === 'bart') { return getBartChildren(); } - return Promise.resolve([]); - }, - getParent: (_, e): Promise => { throw new Error('not implemented'); }, - } - }); - - let listeners = []; - - // helpers - const getGetRootChildren = (children: string[], millis = 0) => () => timeout(millis).then(() => children); - let getRootChildren = getGetRootChildren(['homer', 'bart', 'lisa', 'marge', 'maggie'], 0); - const getGetBartChildren = (millis = 0) => () => timeout(millis).then(() => ['milhouse', 'nelson']); - const getBartChildren = getGetBartChildren(0); - - // item expanding should not exist! - counter.listen(model.onExpandItem, () => { assert(false, 'should never receive item:expanding event'); }); - counter.listen(model.onDidExpandItem, () => { assert(false, 'should never receive item:expanded event'); }); - - return model.setInput('root').then(() => { - - // remove bart - getRootChildren = getGetRootChildren(['homer', 'lisa', 'marge', 'maggie'], 10); - - // refresh root - const p1 = model.refresh('root', true).then(() => { - assert(true); - }, () => { - assert(false, 'should never reach this'); - }); - - // at the same time, try to expand bart! - const p2 = model.expand('bart').then(() => { - assert(false, 'should never reach this'); - }, () => { - assert(true, 'bart should fail to expand since he was removed meanwhile'); - }); - - // what now? - return Promise.all([p1, p2]); - - }).then(() => { - - // teardown - while (listeners.length > 0) { listeners.pop()(); } - listeners = null; - model.dispose(); - - assert.equal(counter.count, 0); - }); - }); - - test('collapsed resolved parent should also update all children visibility on refresh', async function () { - const counter = new EventCounter(); - const dataModel = new DynamicModel(); - - let isSonVisible = true; - const filter: _.IFilter = { - isVisible(_, element) { - return element !== 'son' || isSonVisible; - } - }; - - const model = new TreeModel({ dataSource: dataModel, filter }); - - dataModel.addChild('root', 'father'); - dataModel.addChild('father', 'son'); - - await model.setInput('root'); - await model.expand('father'); - - let nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'father'); - assert.equal(nav.next()!.id, 'son'); - assert.equal(nav.next(), null); - - await model.collapse('father'); - isSonVisible = false; - - await model.refresh(undefined, true); - await model.expand('father'); - - nav = model.getNavigator(); - assert.equal(nav.next()!.id, 'father'); - assert.equal(nav.next(), null); - - counter.dispose(); - model.dispose(); - }); -}); diff --git a/src/vs/base/parts/tree/test/browser/treeViewModel.test.ts b/src/vs/base/parts/tree/test/browser/treeViewModel.test.ts deleted file mode 100644 index 97c2846ebb6..00000000000 --- a/src/vs/base/parts/tree/test/browser/treeViewModel.test.ts +++ /dev/null @@ -1,252 +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 { ArrayIterator } from 'vs/base/common/iterator'; -import { HeightMap, IViewItem } from 'vs/base/parts/tree/browser/treeViewModel'; - -function makeItem(id: any, height: any): any { - return { - id: id, - getHeight: function () { return height; }, - isExpanded: function () { return false; }, - getAllTraits: () => [] - }; -} - -function makeItems(...args: any[]) { - let r: any[] = []; - - for (let i = 0; i < args.length; i += 2) { - r.push(makeItem(args[i], args[i + 1])); - } - - return r; -} - -function makeNavigator(...args: any[]): any { - let items = makeItems.apply(null, args); - let i = 0; - - return { - next: function () { - return items[i++] || null; - } - }; -} - -class TestHeightMap extends HeightMap { - - protected createViewItem(item: any): IViewItem { - return { - model: item, - top: 0, - height: item.getHeight(), - width: 0 - }; - } -} - -suite('TreeView - HeightMap', () => { - let rangeMap: HeightMap; - - setup(() => { - rangeMap = new TestHeightMap(); - rangeMap.onInsertItems(makeNavigator('a', 3, 'b', 30, 'c', 25, 'd', 2)); - }); - - teardown(() => { - rangeMap.dispose(); - rangeMap = null!; - }); - - test('simple', () => { - assert.equal(rangeMap.itemAt(0), 'a'); - assert.equal(rangeMap.itemAt(2), 'a'); - assert.equal(rangeMap.itemAt(3), 'b'); - assert.equal(rangeMap.itemAt(32), 'b'); - assert.equal(rangeMap.itemAt(33), 'c'); - assert.equal(rangeMap.itemAt(40), 'c'); - assert.equal(rangeMap.itemAt(57), 'c'); - assert.equal(rangeMap.itemAt(58), 'd'); - assert.equal(rangeMap.itemAt(59), 'd'); - assert.throws(() => rangeMap.itemAt(60)); - }); - - test('onInsertItems at beginning', () => { - let navigator = makeNavigator('x', 4, 'y', 20, 'z', 8); - rangeMap.onInsertItems(navigator); - - assert.equal(rangeMap.itemAt(0), 'x'); - assert.equal(rangeMap.itemAt(3), 'x'); - assert.equal(rangeMap.itemAt(4), 'y'); - assert.equal(rangeMap.itemAt(23), 'y'); - assert.equal(rangeMap.itemAt(24), 'z'); - assert.equal(rangeMap.itemAt(31), 'z'); - assert.equal(rangeMap.itemAt(32), 'a'); - assert.equal(rangeMap.itemAt(34), 'a'); - assert.equal(rangeMap.itemAt(35), 'b'); - assert.equal(rangeMap.itemAt(64), 'b'); - assert.equal(rangeMap.itemAt(65), 'c'); - assert.equal(rangeMap.itemAt(89), 'c'); - assert.equal(rangeMap.itemAt(90), 'd'); - assert.equal(rangeMap.itemAt(91), 'd'); - assert.throws(() => rangeMap.itemAt(92)); - }); - - test('onInsertItems in middle', () => { - let navigator = makeNavigator('x', 4, 'y', 20, 'z', 8); - rangeMap.onInsertItems(navigator, 'a'); - - assert.equal(rangeMap.itemAt(0), 'a'); - assert.equal(rangeMap.itemAt(2), 'a'); - assert.equal(rangeMap.itemAt(3), 'x'); - assert.equal(rangeMap.itemAt(6), 'x'); - assert.equal(rangeMap.itemAt(7), 'y'); - assert.equal(rangeMap.itemAt(26), 'y'); - assert.equal(rangeMap.itemAt(27), 'z'); - assert.equal(rangeMap.itemAt(34), 'z'); - assert.equal(rangeMap.itemAt(35), 'b'); - assert.equal(rangeMap.itemAt(64), 'b'); - assert.equal(rangeMap.itemAt(65), 'c'); - assert.equal(rangeMap.itemAt(89), 'c'); - assert.equal(rangeMap.itemAt(90), 'd'); - assert.equal(rangeMap.itemAt(91), 'd'); - assert.throws(() => rangeMap.itemAt(92)); - }); - - test('onInsertItems at end', () => { - let navigator = makeNavigator('x', 4, 'y', 20, 'z', 8); - rangeMap.onInsertItems(navigator, 'd'); - - assert.equal(rangeMap.itemAt(0), 'a'); - assert.equal(rangeMap.itemAt(2), 'a'); - assert.equal(rangeMap.itemAt(3), 'b'); - assert.equal(rangeMap.itemAt(32), 'b'); - assert.equal(rangeMap.itemAt(33), 'c'); - assert.equal(rangeMap.itemAt(57), 'c'); - assert.equal(rangeMap.itemAt(58), 'd'); - assert.equal(rangeMap.itemAt(59), 'd'); - assert.equal(rangeMap.itemAt(60), 'x'); - assert.equal(rangeMap.itemAt(63), 'x'); - assert.equal(rangeMap.itemAt(64), 'y'); - assert.equal(rangeMap.itemAt(83), 'y'); - assert.equal(rangeMap.itemAt(84), 'z'); - assert.equal(rangeMap.itemAt(91), 'z'); - assert.throws(() => rangeMap.itemAt(92)); - }); - - test('onRemoveItems at beginning', () => { - rangeMap.onRemoveItems(new ArrayIterator(['a', 'b'])); - - assert.equal(rangeMap.itemAt(0), 'c'); - assert.equal(rangeMap.itemAt(24), 'c'); - assert.equal(rangeMap.itemAt(25), 'd'); - assert.equal(rangeMap.itemAt(26), 'd'); - assert.throws(() => rangeMap.itemAt(27)); - }); - - test('onRemoveItems in middle', () => { - rangeMap.onRemoveItems(new ArrayIterator(['c'])); - - assert.equal(rangeMap.itemAt(0), 'a'); - assert.equal(rangeMap.itemAt(2), 'a'); - assert.equal(rangeMap.itemAt(3), 'b'); - assert.equal(rangeMap.itemAt(32), 'b'); - assert.equal(rangeMap.itemAt(33), 'd'); - assert.equal(rangeMap.itemAt(34), 'd'); - assert.throws(() => rangeMap.itemAt(35)); - }); - - test('onRemoveItems at end', () => { - rangeMap.onRemoveItems(new ArrayIterator(['c', 'd'])); - - assert.equal(rangeMap.itemAt(0), 'a'); - assert.equal(rangeMap.itemAt(2), 'a'); - assert.equal(rangeMap.itemAt(3), 'b'); - assert.equal(rangeMap.itemAt(32), 'b'); - assert.throws(() => rangeMap.itemAt(33)); - }); - - test('onRefreshItems at beginning', () => { - let navigator = makeNavigator('a', 1, 'b', 1); - rangeMap.onRefreshItems(navigator); - - assert.equal(rangeMap.itemAt(0), 'a'); - assert.equal(rangeMap.itemAt(1), 'b'); - assert.equal(rangeMap.itemAt(2), 'c'); - assert.equal(rangeMap.itemAt(26), 'c'); - assert.equal(rangeMap.itemAt(27), 'd'); - assert.equal(rangeMap.itemAt(28), 'd'); - assert.throws(() => rangeMap.itemAt(29)); - }); - - test('onRefreshItems in middle', () => { - let navigator = makeNavigator('b', 40, 'c', 4); - rangeMap.onRefreshItems(navigator); - - assert.equal(rangeMap.itemAt(0), 'a'); - assert.equal(rangeMap.itemAt(2), 'a'); - assert.equal(rangeMap.itemAt(3), 'b'); - assert.equal(rangeMap.itemAt(42), 'b'); - assert.equal(rangeMap.itemAt(43), 'c'); - assert.equal(rangeMap.itemAt(46), 'c'); - assert.equal(rangeMap.itemAt(47), 'd'); - assert.equal(rangeMap.itemAt(48), 'd'); - assert.throws(() => rangeMap.itemAt(49)); - }); - - test('onRefreshItems at end', () => { - let navigator = makeNavigator('d', 22); - rangeMap.onRefreshItems(navigator); - - assert.equal(rangeMap.itemAt(0), 'a'); - assert.equal(rangeMap.itemAt(2), 'a'); - assert.equal(rangeMap.itemAt(3), 'b'); - assert.equal(rangeMap.itemAt(32), 'b'); - assert.equal(rangeMap.itemAt(33), 'c'); - assert.equal(rangeMap.itemAt(57), 'c'); - assert.equal(rangeMap.itemAt(58), 'd'); - assert.equal(rangeMap.itemAt(79), 'd'); - assert.throws(() => rangeMap.itemAt(80)); - }); - - test('withItemsInRange', () => { - let i = 0; - let itemsInRange = ['a', 'b']; - rangeMap.withItemsInRange(2, 27, function (item) { assert.equal(item, itemsInRange[i++]); }); - assert.equal(i, itemsInRange.length); - - i = 0; - itemsInRange = ['a', 'b']; - rangeMap.withItemsInRange(0, 3, function (item) { assert.equal(item, itemsInRange[i++]); }); - assert.equal(i, itemsInRange.length); - - i = 0; - itemsInRange = ['a']; - rangeMap.withItemsInRange(0, 2, function (item) { assert.equal(item, itemsInRange[i++]); }); - assert.equal(i, itemsInRange.length); - - i = 0; - itemsInRange = ['a']; - rangeMap.withItemsInRange(0, 2, function (item) { assert.equal(item, itemsInRange[i++]); }); - assert.equal(i, itemsInRange.length); - - i = 0; - itemsInRange = ['b', 'c']; - rangeMap.withItemsInRange(15, 39, function (item) { assert.equal(item, itemsInRange[i++]); }); - assert.equal(i, itemsInRange.length); - - i = 0; - itemsInRange = ['a', 'b', 'c', 'd']; - rangeMap.withItemsInRange(1, 58, function (item) { assert.equal(item, itemsInRange[i++]); }); - assert.equal(i, itemsInRange.length); - - i = 0; - itemsInRange = ['c', 'd']; - rangeMap.withItemsInRange(45, 58, function (item) { assert.equal(item, itemsInRange[i++]); }); - assert.equal(i, itemsInRange.length); - }); -}); diff --git a/src/vs/workbench/test/browser/actionRegistry.test.ts b/src/vs/base/test/browser/actionbar.test.ts similarity index 78% rename from src/vs/workbench/test/browser/actionRegistry.test.ts rename to src/vs/base/test/browser/actionbar.test.ts index 50a0332971d..0a57211472b 100644 --- a/src/vs/workbench/test/browser/actionRegistry.test.ts +++ b/src/vs/base/test/browser/actionbar.test.ts @@ -4,13 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { prepareActions } from 'vs/workbench/browser/actions'; +import { Separator, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action } from 'vs/base/common/actions'; -suite('Workbench action registry', () => { +suite('Actionbar', () => { - test('Workbench Action Bar prepareActions()', function () { + test('prepareActions()', function () { let a1 = new Separator(); let a2 = new Separator(); let a3 = new Action('a3'); diff --git a/src/vs/base/test/browser/markdownRenderer.test.ts b/src/vs/base/test/browser/markdownRenderer.test.ts index 499565a641c..6ec7827c752 100644 --- a/src/vs/base/test/browser/markdownRenderer.test.ts +++ b/src/vs/base/test/browser/markdownRenderer.test.ts @@ -6,7 +6,9 @@ import * as assert from 'assert'; import * as marked from 'vs/base/common/marked/marked'; import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; -import { MarkdownString } from 'vs/base/common/htmlContent'; +import { MarkdownString, IMarkdownString } from 'vs/base/common/htmlContent'; +import { URI } from 'vs/base/common/uri'; +import { parse } from 'vs/base/common/marshalling'; suite('MarkdownRenderer', () => { suite('Images', () => { @@ -98,4 +100,21 @@ suite('MarkdownRenderer', () => { }); + test('npm Hover Run Script not working #90855', function () { + + const md: IMarkdownString = JSON.parse('{"value":"[Run Script](command:npm.runScriptFromHover?%7B%22documentUri%22%3A%7B%22%24mid%22%3A1%2C%22fsPath%22%3A%22c%3A%5C%5CUsers%5C%5Cjrieken%5C%5CCode%5C%5C_sample%5C%5Cfoo%5C%5Cpackage.json%22%2C%22_sep%22%3A1%2C%22external%22%3A%22file%3A%2F%2F%2Fc%253A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22path%22%3A%22%2Fc%3A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22scheme%22%3A%22file%22%7D%2C%22script%22%3A%22echo%22%7D \\"Run the script as a task\\")","supportThemeIcons":false,"isTrusted":true,"uris":{"__uri_e49443":{"$mid":1,"fsPath":"c:\\\\Users\\\\jrieken\\\\Code\\\\_sample\\\\foo\\\\package.json","_sep":1,"external":"file:///c%3A/Users/jrieken/Code/_sample/foo/package.json","path":"/c:/Users/jrieken/Code/_sample/foo/package.json","scheme":"file"},"command:npm.runScriptFromHover?%7B%22documentUri%22%3A%7B%22%24mid%22%3A1%2C%22fsPath%22%3A%22c%3A%5C%5CUsers%5C%5Cjrieken%5C%5CCode%5C%5C_sample%5C%5Cfoo%5C%5Cpackage.json%22%2C%22_sep%22%3A1%2C%22external%22%3A%22file%3A%2F%2F%2Fc%253A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22path%22%3A%22%2Fc%3A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22scheme%22%3A%22file%22%7D%2C%22script%22%3A%22echo%22%7D":{"$mid":1,"path":"npm.runScriptFromHover","scheme":"command","query":"{\\"documentUri\\":\\"__uri_e49443\\",\\"script\\":\\"echo\\"}"}}}'); + const element = renderMarkdown(md); + + const anchor = element.querySelector('a')!; + assert.ok(anchor); + assert.ok(anchor.dataset['href']); + + const uri = URI.parse(anchor.dataset['href']!); + + const data = <{ script: string, documentUri: URI }>parse(decodeURIComponent(uri.query)); + assert.ok(data); + assert.equal(data.script, 'echo'); + assert.ok(data.documentUri.toString().startsWith('file:///c%3A/')); + }); + }); diff --git a/src/vs/base/test/common/codicon.test.ts b/src/vs/base/test/common/codicon.test.ts index b3fdb5bde1b..8d974ea0ad4 100644 --- a/src/vs/base/test/common/codicon.test.ts +++ b/src/vs/base/test/common/codicon.test.ts @@ -2,9 +2,11 @@ * 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 { IMatch } from 'vs/base/common/filters'; import { matchesFuzzyCodiconAware, parseCodicons, IParsedCodicons } from 'vs/base/common/codicon'; +import { stripCodicons } from 'vs/base/common/codicons'; export interface ICodiconFilter { // Returns null if word doesn't match. @@ -64,3 +66,13 @@ suite('Codicon', () => { ]); }); }); + +suite('Codicons', () => { + + test('stripCodicons', () => { + assert.equal(stripCodicons('Hello World'), 'Hello World'); + assert.equal(stripCodicons('$(Hello World'), '$(Hello World'); + assert.equal(stripCodicons('$(Hello) World'), ' World'); + assert.equal(stripCodicons('$(Hello) W$(oi)rld'), ' Wrld'); + }); +}); diff --git a/src/vs/base/parts/quickopen/test/common/quickOpenScorer.test.ts b/src/vs/base/test/common/fuzzyScorer.test.ts similarity index 79% rename from src/vs/base/parts/quickopen/test/common/quickOpenScorer.test.ts rename to src/vs/base/test/common/fuzzyScorer.test.ts index a7bbe6fb2a0..5849f5139b5 100644 --- a/src/vs/base/parts/quickopen/test/common/quickOpenScorer.test.ts +++ b/src/vs/base/test/common/fuzzyScorer.test.ts @@ -4,10 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as scorer from 'vs/base/parts/quickopen/common/quickOpenScorer'; +import * as scorer from 'vs/base/common/fuzzyScorer'; import { URI } from 'vs/base/common/uri'; import { basename, dirname, sep } from 'vs/base/common/path'; import { isWindows } from 'vs/base/common/platform'; +import { Schemas } from 'vs/base/common/network'; class ResourceAccessorClass implements scorer.IItemAccessor { @@ -41,22 +42,30 @@ class NullAccessorClass implements scorer.IItemAccessor { } } -function _doScore(target: string, query: string, fuzzy: boolean): scorer.Score { - return scorer.score(target, query, query.toLowerCase(), fuzzy); +function _doScore(target: string, query: string, fuzzy: boolean): scorer.FuzzyScore { + const preparedQuery = scorer.prepareQuery(query); + + return scorer.scoreFuzzy(target, preparedQuery.normalized, preparedQuery.normalizedLowercase, fuzzy); } -function scoreItem(item: T, query: string, fuzzy: boolean, accessor: scorer.IItemAccessor, cache: scorer.ScorerCache): scorer.IItemScore { - return scorer.scoreItem(item, scorer.prepareQuery(query), fuzzy, accessor, cache); +function _doScore2(target: string, query: string): scorer.FuzzyScore2 { + const preparedQuery = scorer.prepareQuery(query); + + return scorer.scoreFuzzy2(target, preparedQuery); } -function compareItemsByScore(itemA: T, itemB: T, query: string, fuzzy: boolean, accessor: scorer.IItemAccessor, cache: scorer.ScorerCache, fallbackComparer = scorer.fallbackCompare): number { - return scorer.compareItemsByScore(itemA, itemB, scorer.prepareQuery(query), fuzzy, accessor, cache, fallbackComparer); +function scoreItem(item: T, query: string, fuzzy: boolean, accessor: scorer.IItemAccessor, cache: scorer.FuzzyScorerCache): scorer.IItemScore { + return scorer.scoreItemFuzzy(item, scorer.prepareQuery(query), fuzzy, accessor, cache); +} + +function compareItemsByScore(itemA: T, itemB: T, query: string, fuzzy: boolean, accessor: scorer.IItemAccessor, cache: scorer.FuzzyScorerCache): number { + return scorer.compareItemsByFuzzyScore(itemA, itemB, scorer.prepareQuery(query), fuzzy, accessor, cache); } const NullAccessor = new NullAccessorClass(); -let cache: scorer.ScorerCache = Object.create(null); +let cache: scorer.FuzzyScorerCache = Object.create(null); -suite('Quick Open Scorer', () => { +suite('Fuzzy Scorer', () => { setup(() => { cache = Object.create(null); @@ -65,7 +74,7 @@ suite('Quick Open Scorer', () => { test('score (fuzzy)', function () { const target = 'HeLlo-World'; - const scores: scorer.Score[] = []; + const scores: scorer.FuzzyScore[] = []; scores.push(_doScore(target, 'HelLo-World', true)); // direct case match scores.push(_doScore(target, 'hello-world', true)); // direct mix-case match scores.push(_doScore(target, 'HW', true)); // direct case prefix (multiple) @@ -180,6 +189,49 @@ suite('Quick Open Scorer', () => { assert.ok(pathRes.score > noRes.score); }); + test('scoreItem - multiple', function () { + const resource = URI.file('/xyz/some/path/someFile123.txt'); + + let res1 = scoreItem(resource, 'xyz some', true, ResourceAccessor, cache); + assert.ok(res1.score); + assert.equal(res1.labelMatch?.length, 1); + assert.equal(res1.labelMatch![0].start, 0); + assert.equal(res1.labelMatch![0].end, 4); + assert.equal(res1.descriptionMatch?.length, 1); + assert.equal(res1.descriptionMatch![0].start, 1); + assert.equal(res1.descriptionMatch![0].end, 4); + + let res2 = scoreItem(resource, 'some xyz', true, ResourceAccessor, cache); + assert.ok(res2.score); + assert.equal(res1.score, res2.score); + assert.equal(res2.labelMatch?.length, 1); + assert.equal(res2.labelMatch![0].start, 0); + assert.equal(res2.labelMatch![0].end, 4); + assert.equal(res2.descriptionMatch?.length, 1); + assert.equal(res2.descriptionMatch![0].start, 1); + assert.equal(res2.descriptionMatch![0].end, 4); + + let res3 = scoreItem(resource, 'some xyz file file123', true, ResourceAccessor, cache); + assert.ok(res3.score); + assert.ok(res3.score > res2.score); + assert.equal(res3.labelMatch?.length, 1); + assert.equal(res3.labelMatch![0].start, 0); + assert.equal(res3.labelMatch![0].end, 11); + assert.equal(res3.descriptionMatch?.length, 1); + assert.equal(res3.descriptionMatch![0].start, 1); + assert.equal(res3.descriptionMatch![0].end, 4); + + let res4 = scoreItem(resource, 'path z y', true, ResourceAccessor, cache); + assert.ok(res4.score); + assert.ok(res4.score < res2.score); + assert.equal(res4.labelMatch?.length, 0); + assert.equal(res4.descriptionMatch?.length, 2); + assert.equal(res4.descriptionMatch![0].start, 2); + assert.equal(res4.descriptionMatch![0].end, 4); + assert.equal(res4.descriptionMatch![1].start, 10); + assert.equal(res4.descriptionMatch![1].end, 14); + }); + test('scoreItem - invalid input', function () { let res = scoreItem(null, null!, true, ResourceAccessor, cache); @@ -279,6 +331,19 @@ suite('Quick Open Scorer', () => { assert.ok(!res.score); }); + test('scoreItem - match if using slash or backslash (local, remote resource)', function () { + const localResource = URI.file('abcde/super/duper'); + const remoteResource = URI.from({ scheme: Schemas.vscodeRemote, path: 'abcde/super/duper' }); + + for (const resource of [localResource, remoteResource]) { + let res = scoreItem(resource, 'abcde\\super\\duper', true, ResourceAccessor, cache); + assert.ok(res.score); + + res = scoreItem(resource, 'abcde/super/duper', true, ResourceAccessor, cache); + assert.ok(res.score); + } + }); + test('compareItemsByScore - identity', function () { const resourceA = URI.file('/some/path/fileA.txt'); const resourceB = URI.file('/some/path/other/fileB.txt'); @@ -509,33 +574,13 @@ suite('Quick Open Scorer', () => { assert.equal(res[2], resourceC); }); - test('compareFilesByScore - allow to provide fallback sorter (bug #31591)', function () { - const resourceA = URI.file('virtual/vscode.d.ts'); - const resourceB = URI.file('vscode/src/vs/vscode.d.ts'); + test('compareFilesByScore - prefer matches in label over description if scores are otherwise equal', function () { + const resourceA = URI.file('parts/quick/arrow-left-dark.svg'); + const resourceB = URI.file('parts/quickopen/quickopen.ts'); - let query = 'vscode'; + let query = 'partsquick'; - let res = [resourceA, resourceB].sort((r1, r2) => { - return compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache, (r1, r2, query, ResourceAccessor) => { - if (r1 as any /* TS fail */ === resourceA) { - return -1; - } - - return 1; - }); - }); - assert.equal(res[0], resourceA); - assert.equal(res[1], resourceB); - - res = [resourceB, resourceA].sort((r1, r2) => { - return compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache, (r1, r2, query, ResourceAccessor) => { - if (r1 as any /* TS fail */ === resourceB) { - return -1; - } - - return 1; - }); - }); + let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache)); assert.equal(res[0], resourceB); assert.equal(res[1], resourceA); }); @@ -826,11 +871,108 @@ suite('Quick Open Scorer', () => { assert.equal(res[0], resourceB); }); - test('prepareSearchForScoring', () => { - assert.equal(scorer.prepareQuery(' f*a ').value, 'fa'); - assert.equal(scorer.prepareQuery('model Tester.ts').value, 'modelTester.ts'); - assert.equal(scorer.prepareQuery('Model Tester.ts').lowercase, 'modeltester.ts'); + test('prepareQuery', () => { + assert.equal(scorer.prepareQuery(' f*a ').normalized, 'fa'); + assert.equal(scorer.prepareQuery('model Tester.ts').original, 'model Tester.ts'); + assert.equal(scorer.prepareQuery('model Tester.ts').originalLowercase, 'model Tester.ts'.toLowerCase()); + assert.equal(scorer.prepareQuery('model Tester.ts').normalized, 'modelTester.ts'); + assert.equal(scorer.prepareQuery('Model Tester.ts').normalizedLowercase, 'modeltester.ts'); assert.equal(scorer.prepareQuery('ModelTester.ts').containsPathSeparator, false); assert.equal(scorer.prepareQuery('Model' + sep + 'Tester.ts').containsPathSeparator, true); + + // with spaces + let query = scorer.prepareQuery('He*llo World'); + assert.equal(query.original, 'He*llo World'); + assert.equal(query.normalized, 'HelloWorld'); + assert.equal(query.normalizedLowercase, 'HelloWorld'.toLowerCase()); + assert.equal(query.values?.length, 2); + assert.equal(query.values?.[0].original, 'He*llo'); + assert.equal(query.values?.[0].normalized, 'Hello'); + assert.equal(query.values?.[0].normalizedLowercase, 'Hello'.toLowerCase()); + assert.equal(query.values?.[1].original, 'World'); + assert.equal(query.values?.[1].normalized, 'World'); + assert.equal(query.values?.[1].normalizedLowercase, 'World'.toLowerCase()); + + let restoredQuery = scorer.pieceToQuery(query.values!); + assert.equal(restoredQuery.original, query.original); + assert.equal(restoredQuery.values?.length, query.values?.length); + assert.equal(restoredQuery.containsPathSeparator, query.containsPathSeparator); + + // with spaces that are empty + query = scorer.prepareQuery(' Hello World '); + assert.equal(query.original, ' Hello World '); + assert.equal(query.originalLowercase, ' Hello World '.toLowerCase()); + assert.equal(query.normalized, 'HelloWorld'); + assert.equal(query.normalizedLowercase, 'HelloWorld'.toLowerCase()); + assert.equal(query.values?.length, 2); + assert.equal(query.values?.[0].original, 'Hello'); + assert.equal(query.values?.[0].originalLowercase, 'Hello'.toLowerCase()); + assert.equal(query.values?.[0].normalized, 'Hello'); + assert.equal(query.values?.[0].normalizedLowercase, 'Hello'.toLowerCase()); + assert.equal(query.values?.[1].original, 'World'); + assert.equal(query.values?.[1].originalLowercase, 'World'.toLowerCase()); + assert.equal(query.values?.[1].normalized, 'World'); + assert.equal(query.values?.[1].normalizedLowercase, 'World'.toLowerCase()); + + // Path related + if (isWindows) { + assert.equal(scorer.prepareQuery('C:\\some\\path').pathNormalized, 'C:\\some\\path'); + assert.equal(scorer.prepareQuery('C:\\some\\path').normalized, 'C:\\some\\path'); + assert.equal(scorer.prepareQuery('C:\\some\\path').containsPathSeparator, true); + assert.equal(scorer.prepareQuery('C:/some/path').pathNormalized, 'C:\\some\\path'); + assert.equal(scorer.prepareQuery('C:/some/path').normalized, 'C:\\some\\path'); + assert.equal(scorer.prepareQuery('C:/some/path').containsPathSeparator, true); + } else { + assert.equal(scorer.prepareQuery('/some/path').pathNormalized, '/some/path'); + assert.equal(scorer.prepareQuery('/some/path').normalized, '/some/path'); + assert.equal(scorer.prepareQuery('/some/path').containsPathSeparator, true); + assert.equal(scorer.prepareQuery('\\some\\path').pathNormalized, '/some/path'); + assert.equal(scorer.prepareQuery('\\some\\path').normalized, '/some/path'); + assert.equal(scorer.prepareQuery('\\some\\path').containsPathSeparator, true); + } + }); + + test('fuzzyScore2 (multiple queries)', function () { + const target = 'HeLlo-World'; + + const [firstSingleScore, firstSingleMatches] = _doScore2(target, 'HelLo'); + const [secondSingleScore, secondSingleMatches] = _doScore2(target, 'World'); + const firstAndSecondSingleMatches = [...firstSingleMatches || [], ...secondSingleMatches || []]; + + let [multiScore, multiMatches] = _doScore2(target, 'HelLo World'); + + function assertScore() { + assert.ok(multiScore ?? 0 >= ((firstSingleScore ?? 0) + (secondSingleScore ?? 0))); + for (let i = 0; multiMatches && i < multiMatches.length; i++) { + const multiMatch = multiMatches[i]; + const firstAndSecondSingleMatch = firstAndSecondSingleMatches[i]; + + if (multiMatch && firstAndSecondSingleMatch) { + assert.equal(multiMatch.start, firstAndSecondSingleMatch.start); + assert.equal(multiMatch.end, firstAndSecondSingleMatch.end); + } else { + assert.fail(); + } + } + } + + function assertNoScore() { + assert.equal(multiScore, 0); + assert.equal(multiMatches.length, 0); + } + + assertScore(); + + [multiScore, multiMatches] = _doScore2(target, 'World HelLo'); + assertScore(); + + [multiScore, multiMatches] = _doScore2(target, 'World HelLo World'); + assertScore(); + + [multiScore, multiMatches] = _doScore2(target, 'World HelLo Nothing'); + assertNoScore(); + + [multiScore, multiMatches] = _doScore2(target, 'More Nothing'); + assertNoScore(); }); }); diff --git a/src/vs/base/test/common/hash.test.ts b/src/vs/base/test/common/hash.test.ts index 58b5904b63d..3225caf7b23 100644 --- a/src/vs/base/test/common/hash.test.ts +++ b/src/vs/base/test/common/hash.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { hash } from 'vs/base/common/hash'; +import { hash, StringSHA1 } from 'vs/base/common/hash'; suite('Hash', () => { test('string', () => { @@ -53,4 +53,28 @@ suite('Hash', () => { assert.notEqual(a, b); }); + function checkSHA1(strings: string[], expected: string) { + const hash = new StringSHA1(); + for (const str of strings) { + hash.update(str); + } + const actual = hash.digest(); + assert.equal(actual, expected); + } + + test('sha1-1', () => { + checkSHA1(['\udd56'], '9bdb77276c1852e1fb067820472812fcf6084024'); + }); + + test('sha1-2', () => { + checkSHA1(['\udb52'], '9bdb77276c1852e1fb067820472812fcf6084024'); + }); + + test('sha1-3', () => { + checkSHA1(['\uda02ꑍ'], '9b483a471f22fe7e09d83f221871a987244bbd3f'); + }); + + test('sha1-4', () => { + checkSHA1(['hello'], 'aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d'); + }); }); diff --git a/src/vs/base/test/common/history.test.ts b/src/vs/base/test/common/history.test.ts index fc32e74324b..8b92348be6f 100644 --- a/src/vs/base/test/common/history.test.ts +++ b/src/vs/base/test/common/history.test.ts @@ -106,6 +106,40 @@ suite('History Navigator', () => { assert.deepEqual(['2', '3', '1'], toArray(testObject)); }); + test('previous returns null if the current position is the first one', () => { + const testObject = new HistoryNavigator(['1', '2', '3']); + + testObject.first(); + + assert.deepEqual(testObject.previous(), null); + }); + + test('previous returns object if the current position is not the first one', () => { + const testObject = new HistoryNavigator(['1', '2', '3']); + + testObject.first(); + testObject.next(); + + assert.deepEqual(testObject.previous(), '1'); + }); + + test('next returns null if the current position is the last one', () => { + const testObject = new HistoryNavigator(['1', '2', '3']); + + testObject.last(); + + assert.deepEqual(testObject.next(), null); + }); + + test('next returns object if the current position is not the last one', () => { + const testObject = new HistoryNavigator(['1', '2', '3']); + + testObject.last(); + testObject.previous(); + + assert.deepEqual(testObject.next(), '3'); + }); + test('clear', () => { const testObject = new HistoryNavigator(['a', 'b', 'c']); assert.equal(testObject.previous(), 'c'); diff --git a/src/vs/base/test/common/iterator.test.ts b/src/vs/base/test/common/iterator.test.ts index b7a165c5095..1c55ff69be7 100644 --- a/src/vs/base/test/common/iterator.test.ts +++ b/src/vs/base/test/common/iterator.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { Iterator } from 'vs/base/common/iterator'; +import { Iterator, Iterable } from 'vs/base/common/iterator'; suite('Iterator', () => { test('concat', () => { @@ -16,4 +16,25 @@ suite('Iterator', () => { assert.deepEqual(actual, [1, 2, 3, 4, 5, 6, 7, 8, 9]); }); -}); \ No newline at end of file +}); + +suite('Iterable', function () { + + const customIterable = new class { + + *[Symbol.iterator]() { + yield 'one'; + yield 'two'; + yield 'three'; + } + }; + + test('first', function () { + + assert.equal(Iterable.first([]), undefined); + assert.equal(Iterable.first([1]), 1); + assert.equal(Iterable.first(customIterable), 'one'); + assert.equal(Iterable.first(customIterable), 'one'); // fresh + }); + +}); diff --git a/src/vs/base/test/common/linkedList.test.ts b/src/vs/base/test/common/linkedList.test.ts index 7dc178dbbc0..e63fda850ac 100644 --- a/src/vs/base/test/common/linkedList.test.ts +++ b/src/vs/base/test/common/linkedList.test.ts @@ -16,9 +16,12 @@ suite('LinkedList', function () { // assert toArray assert.deepEqual(list.toArray(), elements); - // assert iterator - for (let iter = list.iterator(), element = iter.next(); !element.done; element = iter.next()) { - assert.equal(elements.shift(), element.value); + // assert Symbol.iterator (1) + assert.deepEqual([...list], elements); + + // assert Symbol.iterator (2) + for (const item of list) { + assert.equal(item, elements.shift()); } assert.equal(elements.length, 0); } diff --git a/src/vs/base/test/common/map.test.ts b/src/vs/base/test/common/map.test.ts index 8afd0496f24..4ce9fbd7d1e 100644 --- a/src/vs/base/test/common/map.test.ts +++ b/src/vs/base/test/common/map.test.ts @@ -3,10 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ResourceMap, TernarySearchTree, PathIterator, StringIterator, LinkedMap, Touch, LRUCache, mapToSerializable, serializableToMap } from 'vs/base/common/map'; +import { ResourceMap, TernarySearchTree, PathIterator, StringIterator, LinkedMap, Touch, LRUCache } from 'vs/base/common/map'; import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; -import { IteratorResult } from 'vs/base/common/iterator'; suite('Map', () => { @@ -630,17 +629,4 @@ suite('Map', () => { // assert.equal(map.get(windowsFile), 'true'); // assert.equal(map.get(uncFile), 'true'); // }); - - test('mapToSerializable / serializableToMap', function () { - const map = new Map(); - map.set('1', 'foo'); - map.set('2', null!); - map.set('3', 'bar'); - - const map2 = serializableToMap(mapToSerializable(map)); - assert.equal(map2.size, map.size); - assert.equal(map2.get('1'), map.get('1')); - assert.equal(map2.get('2'), map.get('2')); - assert.equal(map2.get('3'), map.get('3')); - }); }); diff --git a/src/vs/base/test/common/normalization.test.ts b/src/vs/base/test/common/normalization.test.ts new file mode 100644 index 00000000000..aebe84a0c6e --- /dev/null +++ b/src/vs/base/test/common/normalization.test.ts @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { removeAccents } from 'vs/base/common/normalization'; + +suite('Normalization', () => { + + test('removeAccents', function () { + assert.equal(removeAccents('joào'), 'joao'); + assert.equal(removeAccents('joáo'), 'joao'); + assert.equal(removeAccents('joâo'), 'joao'); + assert.equal(removeAccents('joäo'), 'joao'); + // assert.equal(strings.removeAccents('joæo'), 'joao'); // not an accent + assert.equal(removeAccents('joão'), 'joao'); + assert.equal(removeAccents('joåo'), 'joao'); + assert.equal(removeAccents('joåo'), 'joao'); + assert.equal(removeAccents('joāo'), 'joao'); + + assert.equal(removeAccents('fôo'), 'foo'); + assert.equal(removeAccents('föo'), 'foo'); + assert.equal(removeAccents('fòo'), 'foo'); + assert.equal(removeAccents('fóo'), 'foo'); + // assert.equal(strings.removeAccents('fœo'), 'foo'); + // assert.equal(strings.removeAccents('føo'), 'foo'); + assert.equal(removeAccents('fōo'), 'foo'); + assert.equal(removeAccents('fõo'), 'foo'); + + assert.equal(removeAccents('andrè'), 'andre'); + assert.equal(removeAccents('andré'), 'andre'); + assert.equal(removeAccents('andrê'), 'andre'); + assert.equal(removeAccents('andrë'), 'andre'); + assert.equal(removeAccents('andrē'), 'andre'); + assert.equal(removeAccents('andrė'), 'andre'); + assert.equal(removeAccents('andrę'), 'andre'); + + assert.equal(removeAccents('hvîc'), 'hvic'); + assert.equal(removeAccents('hvïc'), 'hvic'); + assert.equal(removeAccents('hvíc'), 'hvic'); + assert.equal(removeAccents('hvīc'), 'hvic'); + assert.equal(removeAccents('hvįc'), 'hvic'); + assert.equal(removeAccents('hvìc'), 'hvic'); + + assert.equal(removeAccents('ûdo'), 'udo'); + assert.equal(removeAccents('üdo'), 'udo'); + assert.equal(removeAccents('ùdo'), 'udo'); + assert.equal(removeAccents('údo'), 'udo'); + assert.equal(removeAccents('ūdo'), 'udo'); + + assert.equal(removeAccents('heÿ'), 'hey'); + + // assert.equal(strings.removeAccents('gruß'), 'grus'); + assert.equal(removeAccents('gruś'), 'grus'); + assert.equal(removeAccents('gruš'), 'grus'); + + assert.equal(removeAccents('çool'), 'cool'); + assert.equal(removeAccents('ćool'), 'cool'); + assert.equal(removeAccents('čool'), 'cool'); + + assert.equal(removeAccents('ñice'), 'nice'); + assert.equal(removeAccents('ńice'), 'nice'); + }); +}); diff --git a/src/vs/base/test/common/resources.test.ts b/src/vs/base/test/common/resources.test.ts index b45ea4fc2f8..9732aacf05a 100644 --- a/src/vs/base/test/common/resources.test.ts +++ b/src/vs/base/test/common/resources.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { dirname, basename, distinctParents, joinPath, isEqual, isEqualOrParent, hasToIgnoreCase, normalizePath, isAbsolutePath, relativePath, removeTrailingPathSeparator, hasTrailingPathSeparator, resolvePath, addTrailingPathSeparator } from 'vs/base/common/resources'; +import { dirname, basename, distinctParents, joinPath, isEqual, isEqualOrParent, hasToIgnoreCase, normalizePath, isAbsolutePath, relativePath, removeTrailingPathSeparator, hasTrailingPathSeparator, resolvePath, addTrailingPathSeparator, getComparisonKey } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { isWindows } from 'vs/base/common/platform'; import { toSlashes } from 'vs/base/common/extpath'; @@ -66,6 +66,8 @@ suite('Resources', () => { // does not explode (https://github.com/Microsoft/vscode/issues/41987) dirname(URI.from({ scheme: 'file', authority: '/users/someone/portal.h' })); + + assert.equal(dirname(URI.parse('foo://a/b/c?q')).toString(), 'foo://a/b?q'); }); test('basename', () => { @@ -156,6 +158,7 @@ suite('Resources', () => { assert.equal(normalizePath(URI.parse('foo://a/foo/foo/./../some/../bar')).toString(), 'foo://a/foo/bar'); assert.equal(normalizePath(URI.parse('foo://a')).toString(), 'foo://a'); assert.equal(normalizePath(URI.parse('foo://a/')).toString(), 'foo://a/'); + assert.equal(normalizePath(URI.parse('foo://a/foo/./bar?q=1')).toString(), URI.parse('foo://a/foo/bar?q%3D1').toString()); }); test('isAbsolute', () => { @@ -233,7 +236,7 @@ suite('Resources', () => { }); function assertEqualURI(actual: URI, expected: URI, message?: string) { - if (!isEqual(expected, actual)) { + if (!isEqual(expected, actual, hasToIgnoreCase(expected), false)) { assert.equal(actual.toString(), expected.toString(), message); } } @@ -259,7 +262,7 @@ suite('Resources', () => { assertRelativePath(URI.parse('foo://a'), URI.parse('foo://a'), ''); assertRelativePath(URI.parse('foo://a/'), URI.parse('foo://a/'), ''); assertRelativePath(URI.parse('foo://a/'), URI.parse('foo://a'), ''); - assertRelativePath(URI.parse('foo://a/foo?q'), URI.parse('foo://a/foo/bar#h'), 'bar'); + assertRelativePath(URI.parse('foo://a/foo?q'), URI.parse('foo://a/foo/bar#h'), 'bar', true); assertRelativePath(URI.parse('foo://'), URI.parse('foo://a/b'), undefined); assertRelativePath(URI.parse('foo://a2/b'), URI.parse('foo://a/b'), undefined); assertRelativePath(URI.parse('goo://a/b'), URI.parse('foo://a/b'), undefined); @@ -343,26 +346,44 @@ suite('Resources', () => { }); + function assertIsEqual(u1: URI, u2: URI, ignoreCase: boolean, expected: boolean) { + assert.equal(isEqual(u1, u2, ignoreCase), expected, `${u1.toString()}${expected ? '===' : '!=='}${u2.toString()}`); + assert.equal(getComparisonKey(u1, ignoreCase) === getComparisonKey(u2, ignoreCase), expected, `comparison keys ${u1.toString()}, ${u2.toString()}`); + assert.equal(isEqualOrParent(u1, u2, ignoreCase), expected, `isEqualOrParent ${u1.toString()}, ${u2.toString()}`); + } + + test('isEqual', () => { let fileURI = isWindows ? URI.file('c:\\foo\\bar') : URI.file('/foo/bar'); let fileURI2 = isWindows ? URI.file('C:\\foo\\Bar') : URI.file('/foo/Bar'); - assert.equal(isEqual(fileURI, fileURI, true), true); - assert.equal(isEqual(fileURI, fileURI, false), true); - assert.equal(isEqual(fileURI, fileURI, hasToIgnoreCase(fileURI)), true); - assert.equal(isEqual(fileURI, fileURI2, true), true); - assert.equal(isEqual(fileURI, fileURI2, false), false); + assertIsEqual(fileURI, fileURI, true, true); + assertIsEqual(fileURI, fileURI, false, true); + assertIsEqual(fileURI, fileURI, hasToIgnoreCase(fileURI), true); + assertIsEqual(fileURI, fileURI2, true, true); + assertIsEqual(fileURI, fileURI2, false, false); let fileURI3 = URI.parse('foo://server:453/foo/bar'); let fileURI4 = URI.parse('foo://server:453/foo/Bar'); - assert.equal(isEqual(fileURI3, fileURI3, true), true); - assert.equal(isEqual(fileURI3, fileURI3, false), true); - assert.equal(isEqual(fileURI3, fileURI3, hasToIgnoreCase(fileURI3)), true); - assert.equal(isEqual(fileURI3, fileURI4, true), true); - assert.equal(isEqual(fileURI3, fileURI4, false), false); + assertIsEqual(fileURI3, fileURI3, true, true); + assertIsEqual(fileURI3, fileURI3, false, true); + assertIsEqual(fileURI3, fileURI3, hasToIgnoreCase(fileURI3), true); + assertIsEqual(fileURI3, fileURI4, true, true); + assertIsEqual(fileURI3, fileURI4, false, false); - assert.equal(isEqual(fileURI, fileURI3, true), false); + assertIsEqual(fileURI, fileURI3, true, false); - assert.equal(isEqual(URI.parse('foo://server'), URI.parse('foo://server/')), true); + assertIsEqual(URI.parse('foo://server'), URI.parse('foo://server/'), true, true); + assertIsEqual(URI.parse('foo://server/foo'), URI.parse('foo://server/foo/'), true, false); + assertIsEqual(URI.parse('foo://server/foo'), URI.parse('foo://server/foo?'), true, true); + + let fileURI5 = URI.parse('foo://server:453/foo/bar?q=1'); + let fileURI6 = URI.parse('foo://server:453/foo/bar#xy'); + + assertIsEqual(fileURI5, fileURI5, true, true); + assertIsEqual(fileURI5, fileURI3, true, false); + assertIsEqual(fileURI6, fileURI6, true, true); + assertIsEqual(fileURI6, fileURI5, true, false); + assertIsEqual(fileURI6, fileURI3, true, true); }); test('isEqualOrParent', () => { @@ -388,5 +409,12 @@ suite('Resources', () => { assert.equal(isEqualOrParent(fileURI3, fileURI4, false), true, '14'); assert.equal(isEqualOrParent(fileURI3, fileURI, true), false, '15'); assert.equal(isEqualOrParent(fileURI5, fileURI5, true), true, '16'); + + let fileURI6 = URI.parse('foo://server:453/foo?q=1'); + let fileURI7 = URI.parse('foo://server:453/foo/bar?q=1'); + assert.equal(isEqualOrParent(fileURI6, fileURI5, true), false, '17'); + assert.equal(isEqualOrParent(fileURI6, fileURI6, true), true, '18'); + assert.equal(isEqualOrParent(fileURI7, fileURI6, true), true, '19'); + assert.equal(isEqualOrParent(fileURI7, fileURI5, true), false, '20'); }); }); diff --git a/src/vs/base/test/common/strings.test.ts b/src/vs/base/test/common/strings.test.ts index 600df87cfca..52ad8e5f493 100644 --- a/src/vs/base/test/common/strings.test.ts +++ b/src/vs/base/test/common/strings.test.ts @@ -92,15 +92,6 @@ suite('Strings', () => { assert.strictEqual(strings.format('Foo {0} Bar. {1}', '(foo)', '.test'), 'Foo (foo) Bar. .test'); }); - test('overlap', () => { - assert.equal(strings.overlap('foobar', 'arr, I am a priate'), 2); - assert.equal(strings.overlap('no', 'overlap'), 1); - assert.equal(strings.overlap('no', '0verlap'), 0); - assert.equal(strings.overlap('nothing', ''), 0); - assert.equal(strings.overlap('', 'nothing'), 0); - assert.equal(strings.overlap('full', 'full'), 4); - assert.equal(strings.overlap('full', 'fulloverlap'), 4); - }); test('lcut', () => { assert.strictEqual(strings.lcut('foo bar', 0), ''); assert.strictEqual(strings.lcut('foo bar', 1), 'bar'); @@ -404,61 +395,6 @@ suite('Strings', () => { assert.equal(strings.getNLines('foo', 0), ''); }); - test('removeAccents', function () { - assert.equal(strings.removeAccents('joào'), 'joao'); - assert.equal(strings.removeAccents('joáo'), 'joao'); - assert.equal(strings.removeAccents('joâo'), 'joao'); - assert.equal(strings.removeAccents('joäo'), 'joao'); - // assert.equal(strings.removeAccents('joæo'), 'joao'); // not an accent - assert.equal(strings.removeAccents('joão'), 'joao'); - assert.equal(strings.removeAccents('joåo'), 'joao'); - assert.equal(strings.removeAccents('joåo'), 'joao'); - assert.equal(strings.removeAccents('joāo'), 'joao'); - - assert.equal(strings.removeAccents('fôo'), 'foo'); - assert.equal(strings.removeAccents('föo'), 'foo'); - assert.equal(strings.removeAccents('fòo'), 'foo'); - assert.equal(strings.removeAccents('fóo'), 'foo'); - // assert.equal(strings.removeAccents('fœo'), 'foo'); - // assert.equal(strings.removeAccents('føo'), 'foo'); - assert.equal(strings.removeAccents('fōo'), 'foo'); - assert.equal(strings.removeAccents('fõo'), 'foo'); - - assert.equal(strings.removeAccents('andrè'), 'andre'); - assert.equal(strings.removeAccents('andré'), 'andre'); - assert.equal(strings.removeAccents('andrê'), 'andre'); - assert.equal(strings.removeAccents('andrë'), 'andre'); - assert.equal(strings.removeAccents('andrē'), 'andre'); - assert.equal(strings.removeAccents('andrė'), 'andre'); - assert.equal(strings.removeAccents('andrę'), 'andre'); - - assert.equal(strings.removeAccents('hvîc'), 'hvic'); - assert.equal(strings.removeAccents('hvïc'), 'hvic'); - assert.equal(strings.removeAccents('hvíc'), 'hvic'); - assert.equal(strings.removeAccents('hvīc'), 'hvic'); - assert.equal(strings.removeAccents('hvįc'), 'hvic'); - assert.equal(strings.removeAccents('hvìc'), 'hvic'); - - assert.equal(strings.removeAccents('ûdo'), 'udo'); - assert.equal(strings.removeAccents('üdo'), 'udo'); - assert.equal(strings.removeAccents('ùdo'), 'udo'); - assert.equal(strings.removeAccents('údo'), 'udo'); - assert.equal(strings.removeAccents('ūdo'), 'udo'); - - assert.equal(strings.removeAccents('heÿ'), 'hey'); - - // assert.equal(strings.removeAccents('gruß'), 'grus'); - assert.equal(strings.removeAccents('gruś'), 'grus'); - assert.equal(strings.removeAccents('gruš'), 'grus'); - - assert.equal(strings.removeAccents('çool'), 'cool'); - assert.equal(strings.removeAccents('ćool'), 'cool'); - assert.equal(strings.removeAccents('čool'), 'cool'); - - assert.equal(strings.removeAccents('ñice'), 'nice'); - assert.equal(strings.removeAccents('ńice'), 'nice'); - }); - test('encodeUTF8', function () { function assertEncodeUTF8(str: string, expected: number[]): void { const actual = strings.encodeUTF8(str); diff --git a/src/vs/base/test/common/uri.test.ts b/src/vs/base/test/common/uri.test.ts index 495a1ddf636..c42e022215b 100644 --- a/src/vs/base/test/common/uri.test.ts +++ b/src/vs/base/test/common/uri.test.ts @@ -503,4 +503,68 @@ suite('URI', () => { // } // console.profileEnd(); }); + function assertJoined(base: string, fragment: string, expected: string, checkWithUrl: boolean = true) { + const baseUri = URI.parse(base); + const newUri = URI.joinPath(baseUri, fragment); + const actual = newUri.toString(true); + assert.equal(actual, expected); + + if (checkWithUrl) { + const actualUrl = new URL(fragment, base).href; + assert.equal(actualUrl, expected, 'DIFFERENT from URL'); + } + } + test('URI#joinPath', function () { + + assertJoined(('file:///foo/'), '../../bazz', 'file:///bazz'); + assertJoined(('file:///foo'), '../../bazz', 'file:///bazz'); + assertJoined(('file:///foo'), '../../bazz', 'file:///bazz'); + assertJoined(('file:///foo/bar/'), './bazz', 'file:///foo/bar/bazz'); + assertJoined(('file:///foo/bar'), './bazz', 'file:///foo/bar/bazz', false); + assertJoined(('file:///foo/bar'), 'bazz', 'file:///foo/bar/bazz', false); + + // "auto-path" scheme + assertJoined(('file:'), 'bazz', 'file:///bazz'); + assertJoined(('http://domain'), 'bazz', 'http://domain/bazz'); + assertJoined(('https://domain'), 'bazz', 'https://domain/bazz'); + assertJoined(('http:'), 'bazz', 'http:/bazz', false); + assertJoined(('https:'), 'bazz', 'https:/bazz', false); + + // no "auto-path" scheme with and w/o paths + assertJoined(('foo:/'), 'bazz', 'foo:/bazz'); + assertJoined(('foo://bar/'), 'bazz', 'foo://bar/bazz'); + + // no "auto-path" + no path -> error + assert.throws(() => assertJoined(('foo:'), 'bazz', '')); + assert.throws(() => new URL('bazz', 'foo:')); + assert.throws(() => assertJoined(('foo://bar'), 'bazz', '')); + // assert.throws(() => new URL('bazz', 'foo://bar')); Edge,Chrome => throw, Safari => foo://bar/bazz, Firefox ?? + }); + + test('URI#joinPath (posix)', function () { + if (isWindows) { + this.skip(); + } + assertJoined(('file:///c:/foo/'), '../../bazz', 'file:///bazz', false); + assertJoined(('file://server/share/c:/'), '../../bazz', 'file://server/bazz', false); + assertJoined(('file://server/share/c:'), '../../bazz', 'file://server/bazz', false); + + assertJoined(('file://ser/foo/'), '../../bazz', 'file://ser/bazz'); + assertJoined(('file://ser/foo'), '../../bazz', 'file://ser/bazz'); + }); + + test('URI#joinPath (windows)', function () { + if (!isWindows) { + this.skip(); + } + assertJoined(('file:///c:/foo/'), '../../bazz', 'file:///c:/bazz', false); + assertJoined(('file://server/share/c:/'), '../../bazz', 'file://server/share/bazz', false); + assertJoined(('file://server/share/c:'), '../../bazz', 'file://server/share/bazz', false); + + assertJoined(('file://ser/foo/'), '../../bazz', 'file://ser/foo/bazz', false); + assertJoined(('file://ser/foo'), '../../bazz', 'file://ser/foo/bazz', false); + + //https://github.com/microsoft/vscode/issues/93831 + assertJoined('file:///c:/foo/bar', './other/foo.img', 'file:///c:/foo/bar/other/foo.img', false); + }); }); diff --git a/src/vs/base/test/common/uuid.test.ts b/src/vs/base/test/common/uuid.test.ts index ef2cd78be28..ce07ab9cb19 100644 --- a/src/vs/base/test/common/uuid.test.ts +++ b/src/vs/base/test/common/uuid.test.ts @@ -7,16 +7,17 @@ import * as uuid from 'vs/base/common/uuid'; suite('UUID', () => { test('generation', () => { - const asHex = uuid.v4().asHex(); + const asHex = uuid.generateUuid(); assert.equal(asHex.length, 36); assert.equal(asHex[14], '4'); assert.ok(asHex[19] === '8' || asHex[19] === '9' || asHex[19] === 'a' || asHex[19] === 'b'); }); - test('parse', () => { - const id = uuid.v4(); - const asHext = id.asHex(); - const id2 = uuid.parse(asHext); - assert.equal(id.asHex(), id2.asHex()); + test('self-check', function () { + const t1 = Date.now(); + while (Date.now() - t1 < 50) { + const value = uuid.generateUuid(); + assert.ok(uuid.isUUID(value)); + } }); }); diff --git a/src/vs/base/test/node/glob.test.ts b/src/vs/base/test/node/glob.test.ts index f7e8dfd1356..2c0288e3897 100644 --- a/src/vs/base/test/node/glob.test.ts +++ b/src/vs/base/test/node/glob.test.ts @@ -239,10 +239,7 @@ suite('Glob', () => { assertGlobMatch(p, 'some/folder/project.json'); assertNoGlobMatch(p, 'some/folder/file_project.json'); assertNoGlobMatch(p, 'some/folder/fileproject.json'); - // assertNoGlobMatch(p, '/rrproject.json'); TODO@ben this still fails if T1-3 are disabled assertNoGlobMatch(p, 'some/rrproject.json'); - // assertNoGlobMatch(p, 'rrproject.json'); - // assertNoGlobMatch(p, '\\rrproject.json'); assertNoGlobMatch(p, 'some\\rrproject.json'); p = 'test/**'; diff --git a/src/vs/base/test/node/path.test.ts b/src/vs/base/test/node/path.test.ts index 688c184a99e..14ef9c92df1 100644 --- a/src/vs/base/test/node/path.test.ts +++ b/src/vs/base/test/node/path.test.ts @@ -401,9 +401,9 @@ suite('Paths (Node Implementation)', () => { ]; resolveTests.forEach((test) => { const resolve = test[0]; - //@ts-ignore + //@ts-expect-error test[1].forEach((test) => { - //@ts-ignore + //@ts-expect-error const actual = resolve.apply(null, test[0]); let actualAlt; const os = resolve === path.win32.resolve ? 'win32' : 'posix'; @@ -579,9 +579,9 @@ suite('Paths (Node Implementation)', () => { ]; relativeTests.forEach((test) => { const relative = test[0]; - //@ts-ignore + //@ts-expect-error test[1].forEach((test) => { - //@ts-ignore + //@ts-expect-error const actual = relative(test[0], test[1]); const expected = test[2]; const os = relative === path.win32.relative ? 'win32' : 'posix'; diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts index 45f6f17ce06..ef926bf4fa2 100644 --- a/src/vs/code/browser/workbench/workbench.ts +++ b/src/vs/code/browser/workbench/workbench.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkbenchConstructionOptions, create, URI, Event, Emitter, UriComponents, ICredentialsProvider, IURLCallbackProvider, IWorkspaceProvider, IWorkspace, IApplicationLink } from 'vs/workbench/workbench.web.api'; +import { IWorkbenchConstructionOptions, create, URI, Emitter, UriComponents, ICredentialsProvider, IURLCallbackProvider, IWorkspaceProvider, IWorkspace } from 'vs/workbench/workbench.web.api'; import { generateUuid } from 'vs/base/common/uuid'; import { CancellationToken } from 'vs/base/common/cancellation'; import { streamToBuffer } from 'vs/base/common/buffer'; @@ -12,10 +12,6 @@ import { request } from 'vs/base/parts/request/browser/request'; import { isFolderToOpen, isWorkspaceToOpen } from 'vs/platform/windows/common/windows'; import { isEqual } from 'vs/base/common/resources'; import { isStandalone } from 'vs/base/browser/browser'; -import product from 'vs/platform/product/common/product'; -import { Schemas } from 'vs/base/common/network'; -import { posix } from 'vs/base/common/path'; -import { localize } from 'vs/nls'; interface ICredential { service: string; @@ -120,8 +116,8 @@ class PollingURLCallbackProvider extends Disposable implements IURLCallbackProvi FRAGMENT: 'vscode-fragment' }; - private readonly _onCallback: Emitter = this._register(new Emitter()); - readonly onCallback: Event = this._onCallback.event; + private readonly _onCallback = this._register(new Emitter()); + readonly onCallback = this._onCallback.event; create(options?: Partial): URI { const queryValues: Map = new Map(); @@ -342,30 +338,11 @@ class WorkspaceProvider implements IWorkspaceProvider { } } - // Application links ("Open in Desktop") - let applicationLinks: IApplicationLink[] | undefined = undefined; - if (workspace) { - const workspaceUri = isWorkspaceToOpen(workspace) ? workspace.workspaceUri : isFolderToOpen(workspace) ? workspace.folderUri : undefined; - if (workspaceUri) { - applicationLinks = [{ - uri: URI.from({ - scheme: product.quality === 'stable' ? 'vscode' : 'vscode-insiders', - authority: Schemas.vscodeRemote, - path: posix.join(posix.sep, workspaceUri.authority, workspaceUri.path), - query: workspaceUri.query, - fragment: workspaceUri.fragment, - }), - label: localize('openInDesktop', "Open in Desktop") - }]; - } - } - // Finally create workbench create(document.body, { ...config, workspaceProvider: new WorkspaceProvider(workspace, payload), urlCallbackProvider: new PollingURLCallbackProvider(), - credentialsProvider: new LocalStorageCredentialsProvider(), - applicationLinks: applicationLinks + credentialsProvider: new LocalStorageCredentialsProvider() }); })(); diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index 169dbf0c326..2d32cf27fde 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -39,7 +39,7 @@ import { ITelemetryServiceConfig, TelemetryService } from 'vs/platform/telemetry import { combinedAppender, LogAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties'; import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc'; -import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; +import { INativeWindowConfiguration } from 'vs/platform/windows/node/window'; const MAX_URL_LENGTH = 2045; @@ -49,7 +49,7 @@ interface SearchResult { state?: string; } -export interface IssueReporterConfiguration extends IWindowConfiguration { +export interface IssueReporterConfiguration extends INativeWindowConfiguration { data: IssueReporterData; features: IssueReporterFeatures; } @@ -81,6 +81,8 @@ export class IssueReporter extends Disposable { this.initServices(configuration); const isSnap = process.platform === 'linux' && process.env.SNAP && process.env.SNAP_REVISION; + + const targetExtension = configuration.data.extensionId ? configuration.data.enabledExtensions.find(extension => extension.id === configuration.data.extensionId) : undefined; this.issueReporterModel = new IssueReporterModel({ issueType: configuration.data.issueType || IssueType.Bug, versionInfo: { @@ -88,8 +90,8 @@ export class IssueReporter extends Disposable { os: `${os.type()} ${os.arch()} ${os.release()}${isSnap ? ' snap' : ''}` }, extensionsDisabled: !!this.environmentService.disableExtensions, - fileOnExtension: configuration.data.extensionId ? true : undefined, - selectedExtension: configuration.data.extensionId ? configuration.data.enabledExtensions.filter(extension => extension.id === configuration.data.extensionId)[0] : undefined + fileOnExtension: configuration.data.extensionId ? !targetExtension?.isBuiltin : undefined, + selectedExtension: targetExtension, }); const issueReporterElement = this.getElementById('issue-reporter'); @@ -260,19 +262,20 @@ export class IssueReporter extends Disposable { } private handleExtensionData(extensions: IssueReporterExtensionData[]) { - const { nonThemes, themes } = collections.groupBy(extensions, ext => { + const installedExtensions = extensions.filter(x => !x.isBuiltin); + const { nonThemes, themes } = collections.groupBy(installedExtensions, ext => { return ext.isTheme ? 'themes' : 'nonThemes'; }); const numberOfThemeExtesions = themes && themes.length; - this.issueReporterModel.update({ numberOfThemeExtesions, enabledNonThemeExtesions: nonThemes, allExtensions: extensions }); + this.issueReporterModel.update({ numberOfThemeExtesions, enabledNonThemeExtesions: nonThemes, allExtensions: installedExtensions }); this.updateExtensionTable(nonThemes, numberOfThemeExtesions); - if (this.environmentService.disableExtensions || extensions.length === 0) { + if (this.environmentService.disableExtensions || installedExtensions.length === 0) { (this.getElementById('disableExtensions')).disabled = true; } - this.updateExtensionSelector(extensions); + this.updateExtensionSelector(installedExtensions); } private handleSettingsSearchData(data: ISettingsSearchIssueReporterData): void { @@ -316,7 +319,7 @@ export class IssueReporter extends Disposable { } } - private initServices(configuration: IWindowConfiguration): void { + private initServices(configuration: INativeWindowConfiguration): void { const serviceCollection = new ServiceCollection(); const mainProcessService = new MainProcessService(configuration.windowId); serviceCollection.set(IMainProcessService, mainProcessService); @@ -748,10 +751,14 @@ export class IssueReporter extends Disposable { private setSourceOptions(): void { const sourceSelect = this.getElementById('issue-source')! as HTMLSelectElement; - const { issueType, fileOnExtension } = this.issueReporterModel.getData(); + const { issueType, fileOnExtension, selectedExtension } = this.issueReporterModel.getData(); let selected = sourceSelect.selectedIndex; - if (selected === -1 && fileOnExtension !== undefined) { - selected = fileOnExtension ? 2 : 1; + if (selected === -1) { + if (fileOnExtension !== undefined) { + selected = fileOnExtension ? 2 : 1; + } else if (selectedExtension?.isBuiltin) { + selected = 1; + } } sourceSelect.innerHTML = ''; diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 2e35f7c47a1..b9e0b97fe02 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -49,17 +49,16 @@ import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider'; import { Schemas } from 'vs/base/common/network'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; -import { UserDataSyncChannel, UserDataSyncUtilServiceClient, SettingsSyncChannel, UserDataAutoSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; +import { UserDataSyncChannel, UserDataSyncUtilServiceClient, UserDataAutoSyncChannel, StorageKeysSyncRegistryChannelClient } from 'vs/platform/userDataSync/common/userDataSyncIpc'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { LoggerService } from 'vs/platform/log/node/loggerService'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService'; import { UserDataAutoSyncService } from 'vs/platform/userDataSync/electron-browser/userDataAutoSyncService'; -import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; import { NativeStorageService } from 'vs/platform/storage/node/storageService'; import { GlobalStorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -67,6 +66,8 @@ import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagemen import { UserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSyncEnablementService'; import { IAuthenticationTokenService, AuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; import { AuthenticationTokenServiceChannel } from 'vs/platform/authentication/common/authenticationIpc'; +import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; export interface ISharedProcessConfiguration { readonly machineId: string; @@ -140,6 +141,8 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat services.set(IStorageService, storageService); disposables.add(toDisposable(() => storageService.flush())); + services.set(IStorageKeysSyncRegistryService, new StorageKeysSyncRegistryChannelClient(mainProcessService.getChannel('storageKeysSyncRegistryService'))); + services.set(IEnvironmentService, environmentService); services.set(IProductService, { _serviceBrand: undefined, ...product }); services.set(ILogService, logService); @@ -194,8 +197,8 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', client => client.ctx !== 'main'))); services.set(IGlobalExtensionEnablementService, new SyncDescriptor(GlobalExtensionEnablementService)); services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService)); + services.set(IUserDataSyncBackupStoreService, new SyncDescriptor(UserDataSyncBackupStoreService)); services.set(IUserDataSyncEnablementService, new SyncDescriptor(UserDataSyncEnablementService)); - services.set(ISettingsSyncService, new SyncDescriptor(SettingsSynchroniser)); services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService)); registerConfiguration(); @@ -219,10 +222,6 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat const authTokenChannel = new AuthenticationTokenServiceChannel(authTokenService); server.registerChannel('authToken', authTokenChannel); - const settingsSyncService = accessor.get(ISettingsSyncService); - const settingsSyncChannel = new SettingsSyncChannel(settingsSyncService); - server.registerChannel('settingsSync', settingsSyncChannel); - const userDataSyncService = accessor.get(IUserDataSyncService); const userDataSyncChannel = new UserDataSyncChannel(userDataSyncService); server.registerChannel('userDataSync', userDataSyncChannel); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 4ba8c87cb98..80e7fa14a02 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -26,7 +26,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IURLService } from 'vs/platform/url/common/url'; import { URLHandlerChannelClient, URLHandlerRouter } from 'vs/platform/url/common/urlIpc'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService, machineIdKey, trueMachineIdKey } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService, combinedAppender, LogAppender } from 'vs/platform/telemetry/common/telemetryUtils'; import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc'; import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; @@ -61,7 +61,6 @@ import { Schemas } from 'vs/base/common/network'; import { SnapUpdateService } from 'vs/platform/update/electron-main/updateService.snap'; import { IStorageMainService, StorageMainService } from 'vs/platform/storage/node/storageMainService'; import { GlobalStorageDatabaseChannel } from 'vs/platform/storage/node/storageIpc'; -import { startsWith } from 'vs/base/common/strings'; import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService'; import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; import { WorkspacesHistoryMainService, IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService'; @@ -77,12 +76,10 @@ import { IDialogMainService, DialogMainService } from 'vs/platform/dialogs/elect import { withNullAsUndefined } from 'vs/base/common/types'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { coalesce } from 'vs/base/common/arrays'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; +import { StorageKeysSyncRegistryChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; export class CodeApplication extends Disposable { - - private static readonly MACHINE_ID_KEY = 'telemetry.machineId'; - private static readonly TRUE_MACHINE_ID_KEY = 'telemetry.trueMachineId'; - private windowsMainService: IWindowsMainService | undefined; private dialogMainService: IDialogMainService | undefined; @@ -133,7 +130,7 @@ export class CodeApplication extends Disposable { // // !!! DO NOT CHANGE without consulting the documentation !!! // - // app.on('remote-get-guest-web-contents', event => event.preventDefault()); // TODO@Ben TODO@Matt revisit this need for + // app.on('remote-get-guest-web-contents', event => event.preventDefault()); // TODO@Matt revisit this need for app.on('remote-require', (event, sender, module) => { this.logService.trace('App#on(remote-require): prevented'); @@ -173,14 +170,14 @@ export class CodeApplication extends Disposable { return false; } - if (source === 'data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%20lang%3D%22en%22%20style%3D%22width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3Chead%3E%0D%0A%09%3Ctitle%3EVirtual%20Document%3C%2Ftitle%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%20style%3D%22margin%3A%200%3B%20overflow%3A%20hidden%3B%20width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3C%2Fbody%3E%0D%0A%3C%2Fhtml%3E') { + if (source === 'data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%20lang%3D%22en%22%20style%3D%22width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3Chead%3E%0D%0A%3Ctitle%3EVirtual%20Document%3C%2Ftitle%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%20style%3D%22margin%3A%200%3B%20overflow%3A%20hidden%3B%20width%3A%20100%25%3B%20height%3A%20100%25%22%20role%3D%22document%22%3E%0D%0A%3C%2Fbody%3E%0D%0A%3C%2Fhtml%3E') { return true; } const srcUri = URI.parse(source).fsPath.toLowerCase(); const rootUri = URI.file(this.environmentService.appRoot).fsPath.toLowerCase(); - return startsWith(srcUri, rootUri + sep); + return srcUri.startsWith(rootUri + sep); }; // Ensure defaults @@ -407,21 +404,21 @@ export class CodeApplication extends Disposable { // We cache the machineId for faster lookups on startup // and resolve it only once initially if not cached - let machineId = this.stateService.getItem(CodeApplication.MACHINE_ID_KEY); + let machineId = this.stateService.getItem(machineIdKey); if (!machineId) { machineId = await getMachineId(); - this.stateService.setItem(CodeApplication.MACHINE_ID_KEY, machineId); + this.stateService.setItem(machineIdKey, machineId); } // Check if machineId is hashed iBridge Device let trueMachineId: string | undefined; if (isMacintosh && machineId === '6c9d2bc8f91b89624add29c0abeae7fb42bf539fa1cdb2e3e57cd668fa9bcead') { - trueMachineId = this.stateService.getItem(CodeApplication.TRUE_MACHINE_ID_KEY); + trueMachineId = this.stateService.getItem(trueMachineIdKey); if (!trueMachineId) { trueMachineId = await getMachineId(); - this.stateService.setItem(CodeApplication.TRUE_MACHINE_ID_KEY, trueMachineId); + this.stateService.setItem(trueMachineIdKey, trueMachineId); } } @@ -511,7 +508,7 @@ export class CodeApplication extends Disposable { type: 'info', message: localize('trace.message', "Successfully created trace."), detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path), - buttons: [localize('trace.ok', "Ok")] + buttons: [localize('trace.ok', "OK")] }, withNullAsUndefined(BrowserWindow.getFocusedWindow())); } } else { @@ -571,6 +568,11 @@ export class CodeApplication extends Disposable { electronIpcServer.registerChannel('storage', storageChannel); sharedProcessClient.then(client => client.registerChannel('storage', storageChannel)); + const storageKeysSyncRegistryService = accessor.get(IStorageKeysSyncRegistryService); + const storageKeysSyncChannel = new StorageKeysSyncRegistryChannel(storageKeysSyncRegistryService); + electronIpcServer.registerChannel('storageKeysSyncRegistryService', storageKeysSyncChannel); + sharedProcessClient.then(client => client.registerChannel('storageKeysSyncRegistryService', storageKeysSyncChannel)); + const loggerChannel = new LoggerChannel(accessor.get(ILogService)); electronIpcServer.registerChannel('logger', loggerChannel); sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel)); diff --git a/src/vs/code/electron-main/auth.ts b/src/vs/code/electron-main/auth.ts index 45229b3bdd8..b74e1c4e9c1 100644 --- a/src/vs/code/electron-main/auth.ts +++ b/src/vs/code/electron-main/auth.ts @@ -6,7 +6,7 @@ import { localize } from 'vs/nls'; import { Disposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; -import { BrowserWindow, app, AuthInfo, WebContents, Event as ElectronEvent } from 'electron'; +import { BrowserWindow, BrowserWindowConstructorOptions, app, AuthInfo, WebContents, Event as ElectronEvent } from 'electron'; type LoginEvent = { event: ElectronEvent; @@ -49,7 +49,7 @@ export class ProxyAuthHandler extends Disposable { event.preventDefault(); - const opts: any = { + const opts: BrowserWindowConstructorOptions = { alwaysOnTop: true, skipTaskbar: true, resizable: false, diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 7871c40dc59..527d3e29f1c 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -46,6 +46,7 @@ import { FileService } from 'vs/platform/files/common/fileService'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { Schemas } from 'vs/base/common/network'; import { IFileService } from 'vs/platform/files/common/files'; +import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; class ExpectedError extends Error { readonly isExpected = true; @@ -162,6 +163,7 @@ class CodeMain { services.set(IRequestService, new SyncDescriptor(RequestMainService)); services.set(IThemeMainService, new SyncDescriptor(ThemeMainService)); services.set(ISignService, new SyncDescriptor(SignService)); + services.set(IStorageKeysSyncRegistryService, new SyncDescriptor(StorageKeysSyncRegistryService)); return [new InstantiationService(services, true), instanceEnvironment]; } @@ -276,7 +278,7 @@ class CodeMain { // Skip this if we are running with --wait where it is expected that we wait for a while. // Also skip when gathering diagnostics (--status) which can take a longer time. let startupWarningDialogHandle: NodeJS.Timeout | undefined = undefined; - if (!environmentService.wait && !environmentService.status) { + if (!environmentService.args.wait && !environmentService.args.status) { startupWarningDialogHandle = setTimeout(() => { this.showStartupWarningDialog( localize('secondInstanceNoResponse', "Another instance of {0} is running but not responding", product.nameShort), diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 55600018311..91cb9023a43 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -6,7 +6,7 @@ import * as path from 'vs/base/common/path'; import * as objects from 'vs/base/common/objects'; import * as nls from 'vs/nls'; -import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment, nativeTheme } from 'electron'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; @@ -14,10 +14,11 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import product from 'vs/platform/product/common/product'; -import { IWindowSettings, MenuBarVisibility, IWindowConfiguration, ReadyState, getTitleBarStyle, getMenuBarVisibility } from 'vs/platform/windows/common/windows'; +import { IWindowSettings, MenuBarVisibility, ReadyState, getTitleBarStyle, getMenuBarVisibility } from 'vs/platform/windows/common/windows'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { ICodeWindow, IWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows'; +import { INativeWindowConfiguration } from 'vs/platform/windows/node/window'; import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; @@ -25,14 +26,14 @@ import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import * as perf from 'vs/base/common/performance'; import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; -import { endsWith } from 'vs/base/common/strings'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { IFileService } from 'vs/platform/files/common/files'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; +import { IStorageMainService } from 'vs/platform/storage/node/storageMainService'; +import { IFileService } from 'vs/platform/files/common/files'; const RUN_TEXTMATE_IN_WORKER = false; @@ -67,13 +68,13 @@ export class CodeWindow extends Disposable implements ICodeWindow { private static readonly MAX_URL_LENGTH = 2 * 1024 * 1024; // https://cs.chromium.org/chromium/src/url/url_constants.cc?l=32 private readonly _onClose = this._register(new Emitter()); - readonly onClose: CommonEvent = this._onClose.event; + readonly onClose = this._onClose.event; private readonly _onDestroy = this._register(new Emitter()); - readonly onDestroy: CommonEvent = this._onDestroy.event; + readonly onDestroy = this._onDestroy.event; private readonly _onLoad = this._register(new Emitter()); - readonly onLoad: CommonEvent = this._onLoad.event; + readonly onLoad = this._onLoad.event; private hiddenTitleBarStyle: boolean | undefined; private showTimeoutHandle: NodeJS.Timeout | undefined; @@ -81,11 +82,13 @@ export class CodeWindow extends Disposable implements ICodeWindow { private _readyState: ReadyState; private windowState: IWindowState; private currentMenuBarVisibility: MenuBarVisibility | undefined; + private representedFilename: string | undefined; + private documentEdited: boolean | undefined; private readonly whenReadyCallbacks: { (window: ICodeWindow): void }[]; - private pendingLoadConfig?: IWindowConfiguration; + private pendingLoadConfig?: INativeWindowConfiguration; private marketplaceHeadersPromise: Promise; @@ -96,6 +99,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { @ILogService private readonly logService: ILogService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IFileService private readonly fileService: IFileService, + @IStorageMainService private readonly storageService: IStorageMainService, @IConfigurationService private readonly configurationService: IConfigurationService, @IThemeMainService private readonly themeMainService: IThemeMainService, @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, @@ -225,14 +229,18 @@ export class CodeWindow extends Disposable implements ICodeWindow { this.createTouchBar(); // Request handling - this.marketplaceHeadersPromise = resolveMarketplaceHeaders(product.version, this.environmentService, this.fileService); + const that = this; + this.marketplaceHeadersPromise = resolveMarketplaceHeaders(product.version, this.environmentService, this.fileService, { + get(key) { return that.storageService.get(key); }, + store(key, value) { that.storageService.store(key, value); } + }); // Eventing this.registerListeners(); } - private currentConfig: IWindowConfiguration | undefined; - get config(): IWindowConfiguration | undefined { return this.currentConfig; } + private currentConfig: INativeWindowConfiguration | undefined; + get config(): INativeWindowConfiguration | undefined { return this.currentConfig; } private _id: number; get id(): number { return this._id; } @@ -264,6 +272,22 @@ export class CodeWindow extends Disposable implements ICodeWindow { return this.representedFilename; } + setDocumentEdited(edited: boolean): void { + if (isMacintosh) { + this._win.setDocumentEdited(edited); + } + + this.documentEdited = edited; + } + + isDocumentEdited(): boolean { + if (isMacintosh) { + return this._win.isDocumentEdited(); + } + + return !!this.documentEdited; + } + focus(): void { if (!this._win) { return; @@ -342,7 +366,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { this._win.webContents.session.webRequest.onBeforeRequest(null!, (details, callback) => { if (details.url.indexOf('.svg') > 0) { const uri = URI.parse(details.url); - if (uri && !uri.scheme.match(/file/i) && endsWith(uri.path, '.svg')) { + if (uri && !uri.scheme.match(/file/i) && uri.path.endsWith('.svg')) { return callback({ cancel: true }); } } @@ -391,6 +415,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { this.setFullScreen(false); this.setFullScreen(true); } + + this.sendWhenReady('vscode:displayChanged'); }, 100)); const displayChangedListener = () => simpleFullScreenScheduler.schedule(); @@ -552,7 +578,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } } - load(config: IWindowConfiguration, isReload?: boolean, disableExtensions?: boolean): void { + load(config: INativeWindowConfiguration, isReload?: boolean, disableExtensions?: boolean): void { // If this is the first time the window is loaded, we associate the paths // directly with the window because we assume the loading will just work @@ -577,9 +603,9 @@ export class CodeWindow extends Disposable implements ICodeWindow { } // Clear Document Edited if needed - if (isMacintosh && this._win.isDocumentEdited()) { + if (this.isDocumentEdited()) { if (!isReload || !this.backupMainService.isHotExitEnabled()) { - this._win.setDocumentEdited(false); + this.setDocumentEdited(false); } } @@ -612,7 +638,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { this._onLoad.fire(); } - reload(configurationIn?: IWindowConfiguration, cli?: ParsedArgs): void { + reload(configurationIn?: INativeWindowConfiguration, cli?: ParsedArgs): void { // If config is not provided, copy our current one const configuration = configurationIn ? configurationIn : objects.mixin({}, this.currentConfig); @@ -639,7 +665,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { this.load(configuration, true, disableExtensions); } - private getUrl(windowConfiguration: IWindowConfiguration): string { + private getUrl(windowConfiguration: INativeWindowConfiguration): string { // Set window ID windowConfiguration.windowId = this._win.id; diff --git a/src/vs/css.build.js b/src/vs/css.build.js index 69c6240891d..74f0f161ddf 100644 --- a/src/vs/css.build.js +++ b/src/vs/css.build.js @@ -53,7 +53,7 @@ var CSSBuildLoaderPlugin; BrowserCSSLoader.prototype._insertLinkNode = function (linkNode) { this._pendingLoads++; var head = document.head || document.getElementsByTagName('head')[0]; - var other = head.getElementsByTagName('link') || document.head.getElementsByTagName('script'); + var other = head.getElementsByTagName('link') || head.getElementsByTagName('script'); if (other.length > 0) { head.insertBefore(linkNode, other[other.length - 1]); } diff --git a/src/vs/css.js b/src/vs/css.js index 4a2d5a4d299..129fd29abd9 100644 --- a/src/vs/css.js +++ b/src/vs/css.js @@ -51,7 +51,7 @@ var CSSLoaderPlugin; BrowserCSSLoader.prototype._insertLinkNode = function (linkNode) { this._pendingLoads++; var head = document.head || document.getElementsByTagName('head')[0]; - var other = head.getElementsByTagName('link') || document.head.getElementsByTagName('script'); + var other = head.getElementsByTagName('link') || head.getElementsByTagName('script'); if (other.length > 0) { head.insertBefore(linkNode, other[other.length - 1]); } diff --git a/src/vs/editor/browser/config/elementSizeObserver.ts b/src/vs/editor/browser/config/elementSizeObserver.ts index b78ede941f4..06b42c6f9c6 100644 --- a/src/vs/editor/browser/config/elementSizeObserver.ts +++ b/src/vs/editor/browser/config/elementSizeObserver.ts @@ -3,9 +3,34 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { IDimension } from 'vs/editor/common/editorCommon'; -import * as dom from 'vs/base/browser/dom'; + +interface ResizeObserver { + observe(target: Element): void; + unobserve(target: Element): void; + disconnect(): void; +} + +interface ResizeObserverSize { + inlineSize: number; + blockSize: number; +} + +interface ResizeObserverEntry { + readonly target: Element; + readonly contentRect: DOMRectReadOnly; + readonly borderBoxSize: ResizeObserverSize; + readonly contentBoxSize: ResizeObserverSize; +} + +type ResizeObserverCallback = (entries: ReadonlyArray, observer: ResizeObserver) => void; + +declare const ResizeObserver: { + prototype: ResizeObserver; + new(callback: ResizeObserverCallback): ResizeObserver; +}; + export class ElementSizeObserver extends Disposable { @@ -13,8 +38,8 @@ export class ElementSizeObserver extends Disposable { private readonly changeCallback: () => void; private width: number; private height: number; - private mutationObserver: MutationObserver | null; - private windowSizeListener: IDisposable | null; + private resizeObserver: ResizeObserver | null; + private measureReferenceDomElementToken: number; constructor(referenceDomElement: HTMLElement | null, dimension: IDimension | undefined, changeCallback: () => void) { super(); @@ -22,8 +47,8 @@ export class ElementSizeObserver extends Disposable { this.changeCallback = changeCallback; this.width = -1; this.height = -1; - this.mutationObserver = null; - this.windowSizeListener = null; + this.resizeObserver = null; + this.measureReferenceDomElementToken = -1; this.measureReferenceDomElement(false, dimension); } @@ -41,25 +66,33 @@ export class ElementSizeObserver extends Disposable { } public startObserving(): void { - if (!this.mutationObserver && this.referenceDomElement) { - this.mutationObserver = new MutationObserver(() => this._onDidMutate()); - this.mutationObserver.observe(this.referenceDomElement, { - attributes: true, - }); - } - if (!this.windowSizeListener) { - this.windowSizeListener = dom.addDisposableListener(window, 'resize', () => this._onDidResizeWindow()); + if (typeof ResizeObserver !== 'undefined') { + if (!this.resizeObserver && this.referenceDomElement) { + this.resizeObserver = new ResizeObserver((entries) => { + if (entries && entries[0] && entries[0].contentRect) { + this.observe({ width: entries[0].contentRect.width, height: entries[0].contentRect.height }); + } else { + this.observe(); + } + }); + this.resizeObserver.observe(this.referenceDomElement); + } + } else { + if (this.measureReferenceDomElementToken === -1) { + // setInterval type defaults to NodeJS.Timeout instead of number, so specify it as a number + this.measureReferenceDomElementToken = setInterval(() => this.observe(), 100); + } } } public stopObserving(): void { - if (this.mutationObserver) { - this.mutationObserver.disconnect(); - this.mutationObserver = null; + if (this.resizeObserver) { + this.resizeObserver.disconnect(); + this.resizeObserver = null; } - if (this.windowSizeListener) { - this.windowSizeListener.dispose(); - this.windowSizeListener = null; + if (this.measureReferenceDomElementToken !== -1) { + clearInterval(this.measureReferenceDomElementToken); + this.measureReferenceDomElementToken = -1; } } @@ -67,14 +100,6 @@ export class ElementSizeObserver extends Disposable { this.measureReferenceDomElement(true, dimension); } - private _onDidMutate(): void { - this.measureReferenceDomElement(true); - } - - private _onDidResizeWindow(): void { - this.measureReferenceDomElement(true); - } - private measureReferenceDomElement(callChangeCallback: boolean, dimension?: IDimension): void { let observedWidth = 0; let observedHeight = 0; diff --git a/src/vs/editor/browser/controller/coreCommands.ts b/src/vs/editor/browser/controller/coreCommands.ts index ca00039a592..b2a8e0c96e2 100644 --- a/src/vs/editor/browser/controller/coreCommands.ts +++ b/src/vs/editor/browser/controller/coreCommands.ts @@ -856,18 +856,13 @@ export namespace CoreNavigationCommands { } })); - export const CursorLineStart: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand { - constructor() { - super({ - id: 'cursorLineStart', - precondition: undefined, - kbOpts: { - weight: CORE_WEIGHT, - kbExpr: EditorContextKeys.textInputFocus, - primary: 0, - mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_A } - } - }); + class LineStartCommand extends CoreEditorCommand { + + private readonly _inSelectionMode: boolean; + + constructor(opts: ICommandOptions & { inSelectionMode: boolean; }) { + super(opts); + this._inSelectionMode = opts.inSelectionMode; } public runCoreEditorCommand(cursors: ICursors, args: any): void { @@ -885,11 +880,35 @@ export namespace CoreNavigationCommands { for (let i = 0, len = cursors.length; i < len; i++) { const cursor = cursors[i]; const lineNumber = cursor.modelState.position.lineNumber; - result[i] = CursorState.fromModelState(cursor.modelState.move(false, lineNumber, 1, 0)); + result[i] = CursorState.fromModelState(cursor.modelState.move(this._inSelectionMode, lineNumber, 1, 0)); } return result; } - }); + } + + export const CursorLineStart: CoreEditorCommand = registerEditorCommand(new LineStartCommand({ + inSelectionMode: false, + id: 'cursorLineStart', + precondition: undefined, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: EditorContextKeys.textInputFocus, + primary: 0, + mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_A } + } + })); + + export const CursorLineStartSelect: CoreEditorCommand = registerEditorCommand(new LineStartCommand({ + inSelectionMode: true, + id: 'cursorLineStartSelect', + precondition: undefined, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: EditorContextKeys.textInputFocus, + primary: 0, + mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KEY_A } + } + })); class EndCommand extends CoreEditorCommand { @@ -935,18 +954,13 @@ export namespace CoreNavigationCommands { } })); - export const CursorLineEnd: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand { - constructor() { - super({ - id: 'cursorLineEnd', - precondition: undefined, - kbOpts: { - weight: CORE_WEIGHT, - kbExpr: EditorContextKeys.textInputFocus, - primary: 0, - mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_E } - } - }); + class LineEndCommand extends CoreEditorCommand { + + private readonly _inSelectionMode: boolean; + + constructor(opts: ICommandOptions & { inSelectionMode: boolean; }) { + super(opts); + this._inSelectionMode = opts.inSelectionMode; } public runCoreEditorCommand(cursors: ICursors, args: any): void { @@ -965,11 +979,35 @@ export namespace CoreNavigationCommands { const cursor = cursors[i]; const lineNumber = cursor.modelState.position.lineNumber; const maxColumn = context.model.getLineMaxColumn(lineNumber); - result[i] = CursorState.fromModelState(cursor.modelState.move(false, lineNumber, maxColumn, 0)); + result[i] = CursorState.fromModelState(cursor.modelState.move(this._inSelectionMode, lineNumber, maxColumn, 0)); } return result; } - }); + } + + export const CursorLineEnd: CoreEditorCommand = registerEditorCommand(new LineEndCommand({ + inSelectionMode: false, + id: 'cursorLineEnd', + precondition: undefined, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: EditorContextKeys.textInputFocus, + primary: 0, + mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_E } + } + })); + + export const CursorLineEndSelect: CoreEditorCommand = registerEditorCommand(new LineEndCommand({ + inSelectionMode: true, + id: 'cursorLineEndSelect', + precondition: undefined, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: EditorContextKeys.textInputFocus, + primary: 0, + mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KEY_E } + } + })); class TopCommand extends CoreEditorCommand { diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index 5bab90e5eb1..479fa090c6e 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -6,7 +6,7 @@ import * as browser from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; import { StandardWheelEvent, IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; -import { RunOnceScheduler, TimeoutTimer } from 'vs/base/common/async'; +import { TimeoutTimer } from 'vs/base/common/async'; import { Disposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import { HitTestContext, IViewZoneData, MouseTarget, MouseTargetFactory, PointerHandlerLastRenderData } from 'vs/editor/browser/controller/mouseTarget'; @@ -69,7 +69,6 @@ export class MouseHandler extends ViewEventHandler { protected viewController: ViewController; protected viewHelper: IPointerHandlerHelper; protected mouseTargetFactory: MouseTargetFactory; - private readonly _asyncFocus: RunOnceScheduler; protected readonly _mouseDownOperation: MouseDownOperation; private lastMouseLeaveTime: number; @@ -89,8 +88,6 @@ export class MouseHandler extends ViewEventHandler { (e) => this._getMouseColumn(e) )); - this._asyncFocus = this._register(new RunOnceScheduler(() => this.viewHelper.focusTextArea(), 0)); - this.lastMouseLeaveTime = -1; const mouseEvents = new EditorMouseEventFactory(this.viewHelper.viewDomNode); @@ -122,7 +119,7 @@ export class MouseHandler extends ViewEventHandler { e.stopPropagation(); } }; - this._register(dom.addDisposableListener(this.viewHelper.viewDomNode, browser.isEdgeOrIE ? 'mousewheel' : 'wheel', onMouseWheel, { capture: true, passive: false })); + this._register(dom.addDisposableListener(this.viewHelper.viewDomNode, browser.isEdge ? 'mousewheel' : 'wheel', onMouseWheel, { capture: true, passive: false })); this._context.addEventHandler(this); } @@ -137,9 +134,7 @@ export class MouseHandler extends ViewEventHandler { this._mouseDownOperation.onCursorStateChanged(e); return false; } - private _isFocused = false; public onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean { - this._isFocused = e.isFocused; return false; } public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { @@ -223,15 +218,8 @@ export class MouseHandler extends ViewEventHandler { } const focus = () => { - // In IE11, if the focus is in the browser's address bar and - // then you click in the editor, calling preventDefault() - // will not move focus properly (focus remains the address bar) - if (browser.isIE && !this._isFocused) { - this._asyncFocus.schedule(); - } else { - e.preventDefault(); - this.viewHelper.focusTextArea(); - } + e.preventDefault(); + this.viewHelper.focusTextArea(); }; if (shouldHandle && (targetIsContent || (targetIsLineNumbers && selectOnLineNumbers))) { diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index f83ff9bfd94..f0ad0cf79dc 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -925,6 +925,23 @@ export class MouseTargetFactory { } } + // For inline decorations, Gecko returns the `` of the line and the offset is the `` with the inline decoration + if (hitResult.offsetNode.nodeType === hitResult.offsetNode.ELEMENT_NODE) { + const parent1 = hitResult.offsetNode.parentNode; // expected to be the view line div + const parent1ClassName = parent1 && parent1.nodeType === parent1.ELEMENT_NODE ? (parent1).className : null; + + if (parent1ClassName === ViewLine.CLASS_NAME) { + const tokenSpan = hitResult.offsetNode.childNodes[Math.min(hitResult.offset, hitResult.offsetNode.childNodes.length - 1)]; + if (tokenSpan) { + const p = ctx.getPositionFromDOMInfo(tokenSpan, 0); + return { + position: p, + hitTarget: null + }; + } + } + } + return { position: null, hitTarget: hitResult.offsetNode diff --git a/src/vs/editor/browser/controller/pointerHandler.ts b/src/vs/editor/browser/controller/pointerHandler.ts index 46894f1be15..ecef6d8bf39 100644 --- a/src/vs/editor/browser/controller/pointerHandler.ts +++ b/src/vs/editor/browser/controller/pointerHandler.ts @@ -42,8 +42,8 @@ class MsPointerHandler extends MouseHandler implements IDisposable { constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) { super(context, viewController, viewHelper); - this.viewHelper.linesContentDomNode.style.msTouchAction = 'none'; - this.viewHelper.linesContentDomNode.style.msContentZooming = 'none'; + (this.viewHelper.linesContentDomNode.style as any).msTouchAction = 'none'; + (this.viewHelper.linesContentDomNode.style as any).msContentZooming = 'none'; // TODO@Alex -> this expects that the view is added in 100 ms, might not be the case // This handler should be added when the dom node is in the dom tree diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index 7a22ab779d3..01259b88fd0 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -53,7 +53,7 @@ class VisibleTextAreaData { } } -const canUseZeroSizeTextarea = (browser.isEdgeOrIE || browser.isFirefox); +const canUseZeroSizeTextarea = (browser.isEdge || browser.isFirefox); export class TextAreaHandler extends ViewPart { @@ -252,13 +252,14 @@ export class TextAreaHandler extends ViewPart { this._viewController.setSelection('keyboard', modelSelection); })); - this._register(this._textAreaInput.onCompositionStart(() => { + this._register(this._textAreaInput.onCompositionStart((e) => { const lineNumber = this._selections[0].startLineNumber; - const column = this._selections[0].startColumn; + const column = this._selections[0].startColumn - (e.moveOneCharacterLeft ? 1 : 0); this._context.privateViewEventBus.emit(new viewEvents.ViewRevealRangeRequestEvent( 'keyboard', new Range(lineNumber, column, lineNumber, column), + null, viewEvents.VerticalRevealType.Simple, true, ScrollType.Immediate @@ -283,7 +284,7 @@ export class TextAreaHandler extends ViewPart { })); this._register(this._textAreaInput.onCompositionUpdate((e: ICompositionData) => { - if (browser.isEdgeOrIE) { + if (browser.isEdge) { // Due to isEdgeOrIE (where the textarea was not cleared initially) // we cannot assume the text consists only of the composited text this._visibleTextArea = this._visibleTextArea!.setWidth(0); @@ -348,7 +349,7 @@ export class TextAreaHandler extends ViewPart { private _getAriaLabel(options: IComputedEditorOptions): string { const accessibilitySupport = options.get(EditorOption.accessibilitySupport); if (accessibilitySupport === AccessibilitySupport.Disabled) { - return nls.localize('accessibilityOffAriaLabel', "The editor is not accessible at this time. Press Alt+F1 for options."); + return nls.localize('accessibilityOffAriaLabel', "The editor is not accessible at this time. Press {0} for options.", platform.isLinux ? 'Shift+Alt+F1' : 'Alt+F1'); } return options.get(EditorOption.ariaLabel); } @@ -357,9 +358,9 @@ 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 160 for a better experience - // If we put more than 160 lines the nvda can not handle this https://github.com/microsoft/vscode/issues/89717 - this._accessibilityPageSize = 160; + // If a screen reader is attached and the default value is not set we shuold automatically increase the page size to 100 for a better experience + // If we put more than 100 lines the nvda can not handle this https://github.com/microsoft/vscode/issues/89717 + this._accessibilityPageSize = 100; } else { this._accessibilityPageSize = accessibilityPageSize; } @@ -456,6 +457,9 @@ export class TextAreaHandler extends ViewPart { this.textArea.setAttribute('aria-autocomplete', 'both'); this.textArea.removeAttribute('aria-activedescendant'); } + if (options.role) { + this.textArea.setAttribute('role', options.role); + } } // --- end view API diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index 8d0f1962ab2..225d9e73e8a 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -95,6 +95,10 @@ class InMemoryClipboardMetadataManager { } } +export interface ICompositionStartEvent { + moveOneCharacterLeft: boolean; +} + /** * Writes screen reader content to the textarea and is able to analyze its input events to generate: * - onCut @@ -126,8 +130,8 @@ export class TextAreaInput extends Disposable { private _onType = this._register(new Emitter()); public readonly onType: Event = this._onType.event; - private _onCompositionStart = this._register(new Emitter()); - public readonly onCompositionStart: Event = this._onCompositionStart.event; + private _onCompositionStart = this._register(new Emitter()); + public readonly onCompositionStart: Event = this._onCompositionStart.event; private _onCompositionUpdate = this._register(new Emitter()); public readonly onCompositionUpdate: Event = this._onCompositionUpdate.event; @@ -165,9 +169,11 @@ export class TextAreaInput extends Disposable { this._isDoingComposition = false; this._nextCommand = ReadFromTextArea.Type; + let lastKeyDown: IKeyboardEvent | null = null; + this._register(dom.addStandardDisposableListener(textArea.domNode, 'keydown', (e: IKeyboardEvent) => { - if (this._isDoingComposition && - (e.keyCode === KeyCode.KEY_IN_COMPOSITION || e.keyCode === KeyCode.Backspace)) { + if (e.keyCode === KeyCode.KEY_IN_COMPOSITION + || (this._isDoingComposition && e.keyCode === KeyCode.Backspace)) { // Stop propagation for keyDown events if the IME is processing key input e.stopPropagation(); } @@ -177,6 +183,8 @@ export class TextAreaInput extends Disposable { // See https://msdn.microsoft.com/en-us/library/ie/ms536939(v=vs.85).aspx e.preventDefault(); } + + lastKeyDown = e; this._onKeyDown.fire(e); })); @@ -190,12 +198,35 @@ export class TextAreaInput extends Disposable { } this._isDoingComposition = true; - // In IE we cannot set .value when handling 'compositionstart' because the entire composition will get canceled. - if (!browser.isEdgeOrIE) { + let moveOneCharacterLeft = false; + if ( + platform.isMacintosh + && lastKeyDown + && lastKeyDown.equals(KeyCode.KEY_IN_COMPOSITION) + && this._textAreaState.selectionStart === this._textAreaState.selectionEnd + && this._textAreaState.selectionStart > 0 + && this._textAreaState.value.substr(this._textAreaState.selectionStart - 1, 1) === e.data + ) { + // Handling long press case on macOS + arrow key => pretend the character was selected + if (lastKeyDown.code === 'ArrowRight' || lastKeyDown.code === 'ArrowLeft') { + moveOneCharacterLeft = true; + } + } + + if (moveOneCharacterLeft) { + this._textAreaState = new TextAreaState( + this._textAreaState.value, + this._textAreaState.selectionStart - 1, + this._textAreaState.selectionEnd, + this._textAreaState.selectionStartPosition ? new Position(this._textAreaState.selectionStartPosition.lineNumber, this._textAreaState.selectionStartPosition.column - 1) : null, + this._textAreaState.selectionEndPosition + ); + } else if (!browser.isEdge) { + // In IE we cannot set .value when handling 'compositionstart' because the entire composition will get canceled. this._setAndWriteTextAreaState('compositionstart', TextAreaState.EMPTY); } - this._onCompositionStart.fire(); + this._onCompositionStart.fire({ moveOneCharacterLeft }); })); /** @@ -225,15 +256,7 @@ export class TextAreaInput extends Disposable { // Multi-part Japanese compositions reset cursor in Edge/IE, Chinese and Korean IME don't have this issue. // The reason that we can't use this path for all CJK IME is IE and Edge behave differently when handling Korean IME, // which breaks this path of code. - if (browser.isEdgeOrIE && locale === 'ja') { - return true; - } - - // https://github.com/Microsoft/monaco-editor/issues/545 - // On IE11, we can't trust composition data when typing Chinese as IE11 doesn't emit correct - // events when users type numbers in IME. - // Chinese: zh-Hans-CN, zh-Hans-SG, zh-Hant-TW, zh-Hant-HK - if (browser.isIE && locale.indexOf('zh-Han') === 0) { + if (browser.isEdge && locale === 'ja') { return true; } @@ -274,7 +297,7 @@ export class TextAreaInput extends Disposable { // Due to isEdgeOrIE (where the textarea was not cleared initially) and isChrome (the textarea is not updated correctly when composition ends) // we cannot assume the text at the end consists only of the composited text - if (browser.isEdgeOrIE || browser.isChrome) { + if (browser.isEdge || browser.isChrome) { this._textAreaState = TextAreaState.readFromTextArea(this._textArea); } diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index 3b9f5d03e24..e8993db6133 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -333,6 +333,7 @@ export interface IOverviewRuler { */ export interface IEditorAriaOptions { activeDescendant: string | undefined; + role?: string; } /** @@ -703,6 +704,11 @@ export interface ICodeEditor extends editorCommon.IEditor { */ getVisibleRanges(): Range[]; + /** + * @internal + */ + getVisibleRangesPlusViewportAboveBelow(): Range[]; + /** * Get the view zones. * @internal @@ -1013,6 +1019,16 @@ export function isDiffEditor(thing: any): thing is IDiffEditor { } } +/** + *@internal + */ +export function isCompositeEditor(thing: any): thing is editorCommon.ICompositeCodeEditor { + return thing + && typeof thing === 'object' + && typeof (thing).onDidChangeActiveEditor === 'function'; + +} + /** *@internal */ diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index 149d562a3d4..1ddd9266efc 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -347,7 +347,7 @@ export function registerModelCommand(id: string, handler: (model: ITextModel, .. const model = accessor.get(IModelService).getModel(resource); if (model) { - return handler(model, args.slice(1)); + return handler(model, ...args.slice(1)); } return accessor.get(ITextModelService).createModelReference(resource).then(reference => { diff --git a/src/vs/editor/browser/services/abstractCodeEditorService.ts b/src/vs/editor/browser/services/abstractCodeEditorService.ts index bb96485db3e..2aeb0b21b42 100644 --- a/src/vs/editor/browser/services/abstractCodeEditorService.ts +++ b/src/vs/editor/browser/services/abstractCodeEditorService.ts @@ -9,7 +9,7 @@ import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IDecorationRenderOptions } from 'vs/editor/common/editorCommon'; import { IModelDecorationOptions, ITextModel } from 'vs/editor/common/model'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; export abstract class AbstractCodeEditorService extends Disposable implements ICodeEditorService { @@ -135,7 +135,7 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC } abstract getActiveCodeEditor(): ICodeEditor | null; - abstract openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; + abstract openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; } export class ModelTransientSettingWatcher { diff --git a/src/vs/editor/browser/services/codeEditorService.ts b/src/vs/editor/browser/services/codeEditorService.ts index 145cb9d2641..c38fccad1ff 100644 --- a/src/vs/editor/browser/services/codeEditorService.ts +++ b/src/vs/editor/browser/services/codeEditorService.ts @@ -7,7 +7,7 @@ import { Event } from 'vs/base/common/event'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IDecorationRenderOptions } from 'vs/editor/common/editorCommon'; import { IModelDecorationOptions, ITextModel } from 'vs/editor/common/model'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export const ICodeEditorService = createDecorator('codeEditorService'); @@ -46,5 +46,5 @@ export interface ICodeEditorService { getTransientModelProperties(model: ITextModel): [string, any][] | undefined; getActiveCodeEditor(): ICodeEditor | null; - openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; + openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; } diff --git a/src/vs/editor/browser/services/codeEditorServiceImpl.ts b/src/vs/editor/browser/services/codeEditorServiceImpl.ts index 944a6d0f20f..464c1a33d41 100644 --- a/src/vs/editor/browser/services/codeEditorServiceImpl.ts +++ b/src/vs/editor/browser/services/codeEditorServiceImpl.ts @@ -11,20 +11,20 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { AbstractCodeEditorService } from 'vs/editor/browser/services/abstractCodeEditorService'; import { IContentDecorationRenderOptions, IDecorationRenderOptions, IThemeDecorationRenderOptions, isThemeColor } from 'vs/editor/common/editorCommon'; import { IModelDecorationOptions, IModelDecorationOverviewRulerOptions, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; -import { ITheme, IThemeService, ThemeColor } from 'vs/platform/theme/common/themeService'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; +import { IColorTheme, IThemeService, ThemeColor } from 'vs/platform/theme/common/themeService'; -class RefCountedStyleSheet { +export class RefCountedStyleSheet { private readonly _parent: CodeEditorServiceImpl; private readonly _editorId: string; - public readonly styleSheet: HTMLStyleElement; + private readonly _styleSheet: HTMLStyleElement; private _refCount: number; constructor(parent: CodeEditorServiceImpl, editorId: string, styleSheet: HTMLStyleElement) { this._parent = parent; this._editorId = editorId; - this.styleSheet = styleSheet; + this._styleSheet = styleSheet; this._refCount = 0; } @@ -35,17 +35,26 @@ class RefCountedStyleSheet { public unref(): void { this._refCount--; if (this._refCount === 0) { - this.styleSheet.parentNode?.removeChild(this.styleSheet); + this._styleSheet.parentNode?.removeChild(this._styleSheet); this._parent._removeEditorStyleSheets(this._editorId); } } + + public insertRule(rule: string, index?: number): void { + const sheet = this._styleSheet.sheet; + sheet.insertRule(rule, index); + } + + public removeRulesContainingSelector(ruleName: string): void { + dom.removeCSSRulesContainingSelector(ruleName, this._styleSheet); + } } -class GlobalStyleSheet { - public readonly styleSheet: HTMLStyleElement; +export class GlobalStyleSheet { + private readonly _styleSheet: HTMLStyleElement; constructor(styleSheet: HTMLStyleElement) { - this.styleSheet = styleSheet; + this._styleSheet = styleSheet; } public ref(): void { @@ -53,6 +62,15 @@ class GlobalStyleSheet { public unref(): void { } + + public insertRule(rule: string, index?: number): void { + const sheet = this._styleSheet.sheet; + sheet.insertRule(rule, index); + } + + public removeRulesContainingSelector(ruleName: string): void { + dom.removeCSSRulesContainingSelector(ruleName, this._styleSheet); + } } export abstract class CodeEditorServiceImpl extends AbstractCodeEditorService { @@ -62,9 +80,9 @@ export abstract class CodeEditorServiceImpl extends AbstractCodeEditorService { private readonly _editorStyleSheets = new Map(); private readonly _themeService: IThemeService; - constructor(@IThemeService themeService: IThemeService, styleSheet: HTMLStyleElement | null = null) { + constructor(@IThemeService themeService: IThemeService, styleSheet: GlobalStyleSheet | null = null) { super(); - this._globalStyleSheet = styleSheet ? new GlobalStyleSheet(styleSheet) : null; + this._globalStyleSheet = styleSheet ? styleSheet : null; this._themeService = themeService; } @@ -100,7 +118,7 @@ export abstract class CodeEditorServiceImpl extends AbstractCodeEditorService { if (!provider) { const styleSheet = this._getOrCreateStyleSheet(editor); const providerArgs: ProviderArguments = { - styleSheet: styleSheet.styleSheet, + styleSheet: styleSheet, key: key, parentTypeKey: parentTypeKey, options: options || Object.create(null) @@ -136,7 +154,7 @@ export abstract class CodeEditorServiceImpl extends AbstractCodeEditorService { } abstract getActiveCodeEditor(): ICodeEditor | null; - abstract openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; + abstract openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; } interface IModelDecorationOptionsProvider extends IDisposable { @@ -188,7 +206,7 @@ class DecorationSubTypeOptionsProvider implements IModelDecorationOptionsProvide } interface ProviderArguments { - styleSheet: HTMLStyleElement; + styleSheet: GlobalStyleSheet | RefCountedStyleSheet; key: string; parentTypeKey?: string; options: IDecorationRenderOptions; @@ -320,7 +338,7 @@ const _CSS_MAP: { [prop: string]: string; } = { class DecorationCSSRules { - private _theme: ITheme; + private _theme: IColorTheme; private readonly _className: string; private readonly _unThemedSelector: string; private _hasContent: boolean; @@ -330,8 +348,8 @@ class DecorationCSSRules { private readonly _providerArgs: ProviderArguments; private _usesThemeColors: boolean; - public constructor(ruleType: ModelDecorationCSSRuleType, providerArgs: ProviderArguments, themeService: IThemeService) { - this._theme = themeService.getTheme(); + constructor(ruleType: ModelDecorationCSSRuleType, providerArgs: ProviderArguments, themeService: IThemeService) { + this._theme = themeService.getColorTheme(); this._ruleType = ruleType; this._providerArgs = providerArgs; this._usesThemeColors = false; @@ -349,8 +367,8 @@ class DecorationCSSRules { this._buildCSS(); if (this._usesThemeColors) { - this._themeListener = themeService.onThemeChange(theme => { - this._theme = themeService.getTheme(); + this._themeListener = themeService.onDidColorThemeChange(theme => { + this._theme = themeService.getColorTheme(); this._removeCSS(); this._buildCSS(); }); @@ -414,7 +432,7 @@ class DecorationCSSRules { default: throw new Error('Unknown rule type: ' + this._ruleType); } - const sheet = this._providerArgs.styleSheet.sheet; + const sheet = this._providerArgs.styleSheet; let hasContent = false; if (unthemedCSS.length > 0) { @@ -433,7 +451,7 @@ class DecorationCSSRules { } private _removeCSS(): void { - dom.removeCSSRulesContainingSelector(this._unThemedSelector, this._providerArgs.styleSheet); + this._providerArgs.styleSheet.removeRulesContainingSelector(this._unThemedSelector); } /** diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index 4dfb0035522..0390927a4c4 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -11,7 +11,7 @@ import { Schemas } from 'vs/base/common/network'; import { normalizePath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { IOpener, IOpenerService, IValidator, IExternalUriResolver, OpenOptions, ResolveExternalUriOptions, IResolvedExternalUri, IExternalOpener, matchesScheme } from 'vs/platform/opener/common/opener'; import { EditorOpenContext } from 'vs/platform/editor/common/editor'; @@ -28,9 +28,6 @@ class CommandOpener implements IOpener { if (typeof target === 'string') { target = URI.parse(target); } - if (!CommandsRegistry.getCommand(target.path)) { - throw new Error(`command '${target.path}' NOT known`); - } // execute as command let args: any = []; try { diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index 30971b52388..3530ec720fc 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -115,9 +115,9 @@ export class View extends ViewEventHandler { this.eventDispatcher.addEventHandler(this); // The view context is passed on to most classes (basically to reduce param. counts in ctors) - this._context = new ViewContext(configuration, themeService.getTheme(), model, this.eventDispatcher); + this._context = new ViewContext(configuration, themeService.getColorTheme(), model, this.eventDispatcher); - this._register(themeService.onThemeChange(theme => { + this._register(themeService.onDidColorThemeChange(theme => { this._context.theme.update(theme); this.eventDispatcher.emit(new viewEvents.ViewThemeChangedEvent()); this.render(true, false); diff --git a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts index 59fdaf045a9..098bc6c76fa 100644 --- a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts +++ b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts @@ -23,6 +23,8 @@ export abstract class AbstractLineHighlightOverlay extends DynamicViewOverlay { protected _contentLeft: number; protected _contentWidth: number; protected _selectionIsEmpty: boolean; + protected _renderLineHightlightOnlyWhenFocus: boolean; + protected _focused: boolean; private _cursorLineNumbers: number[]; private _selections: Selection[]; private _renderData: string[] | null; @@ -35,9 +37,11 @@ export abstract class AbstractLineHighlightOverlay extends DynamicViewOverlay { const layoutInfo = options.get(EditorOption.layoutInfo); this._lineHeight = options.get(EditorOption.lineHeight); this._renderLineHighlight = options.get(EditorOption.renderLineHighlight); + this._renderLineHightlightOnlyWhenFocus = options.get(EditorOption.renderLineHighlightOnlyWhenFocus); this._contentLeft = layoutInfo.contentLeft; this._contentWidth = layoutInfo.contentWidth; this._selectionIsEmpty = true; + this._focused = false; this._cursorLineNumbers = []; this._selections = []; this._renderData = null; @@ -81,6 +85,7 @@ export abstract class AbstractLineHighlightOverlay extends DynamicViewOverlay { const layoutInfo = options.get(EditorOption.layoutInfo); this._lineHeight = options.get(EditorOption.lineHeight); this._renderLineHighlight = options.get(EditorOption.renderLineHighlight); + this._renderLineHightlightOnlyWhenFocus = options.get(EditorOption.renderLineHighlightOnlyWhenFocus); this._contentLeft = layoutInfo.contentLeft; this._contentWidth = layoutInfo.contentWidth; return true; @@ -104,6 +109,14 @@ export abstract class AbstractLineHighlightOverlay extends DynamicViewOverlay { public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { return true; } + public onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean { + if (!this._renderLineHightlightOnlyWhenFocus) { + return false; + } + + this._focused = e.isFocused; + return true; + } // --- end event handlers public prepareRender(ctx: RenderingContext): void { @@ -157,11 +170,13 @@ export class CurrentLineHighlightOverlay extends AbstractLineHighlightOverlay { return ( (this._renderLineHighlight === 'line' || this._renderLineHighlight === 'all') && this._selectionIsEmpty + && (!this._renderLineHightlightOnlyWhenFocus || this._focused) ); } protected _shouldRenderOther(): boolean { return ( (this._renderLineHighlight === 'gutter' || this._renderLineHighlight === 'all') + && (!this._renderLineHightlightOnlyWhenFocus || this._focused) ); } } @@ -174,12 +189,14 @@ export class CurrentLineMarginHighlightOverlay extends AbstractLineHighlightOver protected _shouldRenderThis(): boolean { return ( (this._renderLineHighlight === 'gutter' || this._renderLineHighlight === 'all') + && (!this._renderLineHightlightOnlyWhenFocus || this._focused) ); } protected _shouldRenderOther(): boolean { return ( (this._renderLineHighlight === 'line' || this._renderLineHighlight === 'all') && this._selectionIsEmpty + && (!this._renderLineHightlightOnlyWhenFocus || this._focused) ); } } diff --git a/src/vs/editor/browser/viewParts/lines/viewLine.ts b/src/vs/editor/browser/viewParts/lines/viewLine.ts index 9c93f326a6f..46d36912654 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLine.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLine.ts @@ -42,7 +42,9 @@ const canUseFastRenderedViewLine = (function () { return true; })(); -const alwaysRenderInlineSelection = (browser.isEdgeOrIE); +let monospaceAssumptionsAreValid = true; + +const alwaysRenderInlineSelection = (browser.isEdge); export class DomReadingContext { @@ -248,7 +250,7 @@ export class ViewLine implements IVisibleLine { sb.appendASCIIString(''); let renderedViewLine: IRenderedViewLine | null = null; - if (canUseFastRenderedViewLine && lineData.isBasicASCII && options.useMonospaceOptimizations && output.containsForeignElements === ForeignElementType.None) { + if (monospaceAssumptionsAreValid && canUseFastRenderedViewLine && lineData.isBasicASCII && options.useMonospaceOptimizations && output.containsForeignElements === ForeignElementType.None) { if (lineData.content.length < 300 && renderLineInput.lineTokens.getCount() < 100) { // Browser rounding errors have been observed in Chrome and IE, so using the fast // view line only for short lines. Please test before removing the length check... @@ -304,6 +306,29 @@ export class ViewLine implements IVisibleLine { return this._renderedViewLine.getWidthIsFast(); } + public needsMonospaceFontCheck(): boolean { + if (!this._renderedViewLine) { + return false; + } + return (this._renderedViewLine instanceof FastRenderedViewLine); + } + + public monospaceAssumptionsAreValid(): boolean { + if (!this._renderedViewLine) { + return monospaceAssumptionsAreValid; + } + if (this._renderedViewLine instanceof FastRenderedViewLine) { + return this._renderedViewLine.monospaceAssumptionsAreValid(); + } + return monospaceAssumptionsAreValid; + } + + public onMonospaceAssumptionsInvalidated(): void { + if (this._renderedViewLine && this._renderedViewLine instanceof FastRenderedViewLine) { + this._renderedViewLine = this._renderedViewLine.toSlowRenderedLine(); + } + } + public getVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): VisibleRanges | null { if (!this._renderedViewLine) { return null; @@ -382,6 +407,24 @@ class FastRenderedViewLine implements IRenderedViewLine { return true; } + public monospaceAssumptionsAreValid(): boolean { + if (!this.domNode) { + return monospaceAssumptionsAreValid; + } + const expectedWidth = this.getWidth(); + const actualWidth = (this.domNode.domNode.firstChild).offsetWidth; + if (Math.abs(expectedWidth - actualWidth) >= 2) { + // more than 2px off + console.warn(`monospace assumptions have been violated, therefore disabling monospace optimizations!`); + monospaceAssumptionsAreValid = false; + } + return monospaceAssumptionsAreValid; + } + + public toSlowRenderedLine(): RenderedViewLine { + return createRenderedLine(this.domNode, this.input, this._characterMapping, false, ForeignElementType.None); + } + public getVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null { const startPosition = this._getCharPosition(startColumn); const endPosition = this._getCharPosition(endColumn); @@ -573,7 +616,15 @@ class RenderedViewLine implements IRenderedViewLine { if (!r || r.length === 0) { return -1; } - return r[0].left; + const result = r[0].left; + if (this.input.isBasicASCII) { + const charOffset = this._characterMapping.getAbsoluteOffsets(); + const expectedResult = Math.round(this.input.spaceWidth * charOffset[column - 1]); + if (Math.abs(expectedResult - result) <= 1) { + return expectedResult; + } + } + return result; } private _readRawVisibleRangesForRange(domNode: FastDomNode, startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null { diff --git a/src/vs/editor/browser/viewParts/lines/viewLines.ts b/src/vs/editor/browser/viewParts/lines/viewLines.ts index 5b2bd0ee591..09ee4fb5516 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLines.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLines.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./viewLines'; +import * as platform from 'vs/base/common/platform'; import { FastDomNode } from 'vs/base/browser/fastDomNode'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Configuration } from 'vs/editor/browser/config/configuration'; @@ -12,6 +13,7 @@ import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/v import { DomReadingContext, ViewLine, ViewLineOptions } from 'vs/editor/browser/viewParts/lines/viewLine'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { IViewLines, LineVisibleRanges, VisibleRanges, HorizontalPosition } from 'vs/editor/common/view/renderingContext'; import { ViewContext } from 'vs/editor/common/view/viewContext'; @@ -38,25 +40,49 @@ class LastRenderedData { } } -class HorizontalRevealRequest { +class HorizontalRevealRangeRequest { + public readonly type = 'range'; + public readonly minLineNumber: number; + public readonly maxLineNumber: number; - public readonly lineNumber: number; - public readonly startColumn: number; - public readonly endColumn: number; - public readonly startScrollTop: number; - public readonly stopScrollTop: number; - public readonly scrollType: ScrollType; - - constructor(lineNumber: number, startColumn: number, endColumn: number, startScrollTop: number, stopScrollTop: number, scrollType: ScrollType) { - this.lineNumber = lineNumber; - this.startColumn = startColumn; - this.endColumn = endColumn; - this.startScrollTop = startScrollTop; - this.stopScrollTop = stopScrollTop; - this.scrollType = scrollType; + constructor( + public readonly lineNumber: number, + public readonly startColumn: number, + public readonly endColumn: number, + public readonly startScrollTop: number, + public readonly stopScrollTop: number, + public readonly scrollType: ScrollType + ) { + this.minLineNumber = lineNumber; + this.maxLineNumber = lineNumber; } } +class HorizontalRevealSelectionsRequest { + public readonly type = 'selections'; + public readonly minLineNumber: number; + public readonly maxLineNumber: number; + + constructor( + public readonly selections: Selection[], + public readonly startScrollTop: number, + public readonly stopScrollTop: number, + public readonly scrollType: ScrollType + ) { + let minLineNumber = selections[0].startLineNumber; + let maxLineNumber = selections[0].endLineNumber; + for (let i = 1, len = selections.length; i < len; i++) { + const selection = selections[i]; + minLineNumber = Math.min(minLineNumber, selection.startLineNumber); + maxLineNumber = Math.max(maxLineNumber, selection.endLineNumber); + } + this.minLineNumber = minLineNumber; + this.maxLineNumber = maxLineNumber; + } +} + +type HorizontalRevealRequest = HorizontalRevealRangeRequest | HorizontalRevealSelectionsRequest; + export class ViewLines extends ViewPart implements IVisibleLinesHost, IViewLines { /** * Adds this amount of pixels to the right of lines (no-one wants to type near the edge of the viewport) @@ -81,6 +107,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, // --- width private _maxLineWidth: number; private readonly _asyncUpdateLineWidths: RunOnceScheduler; + private readonly _asyncCheckMonospaceFontAssumptions: RunOnceScheduler; private _horizontalRevealRequest: HorizontalRevealRequest | null; private readonly _lastRenderedData: LastRenderedData; @@ -115,6 +142,9 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, this._asyncUpdateLineWidths = new RunOnceScheduler(() => { this._updateLineWidthsSlow(); }, 200); + this._asyncCheckMonospaceFontAssumptions = new RunOnceScheduler(() => { + this._checkMonospaceFontAssumptions(); + }, 2000); this._lastRenderedData = new LastRenderedData(); @@ -123,6 +153,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, public dispose(): void { this._asyncUpdateLineWidths.dispose(); + this._asyncCheckMonospaceFontAssumptions.dispose(); super.dispose(); } @@ -221,21 +252,28 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, public onRevealRangeRequest(e: viewEvents.ViewRevealRangeRequestEvent): boolean { // Using the future viewport here in order to handle multiple // incoming reveal range requests that might all desire to be animated - const desiredScrollTop = this._computeScrollTopToRevealRange(this._context.viewLayout.getFutureViewport(), e.source, e.range, e.verticalType); + const desiredScrollTop = this._computeScrollTopToRevealRange(this._context.viewLayout.getFutureViewport(), e.source, e.range, e.selections, e.verticalType); + + if (desiredScrollTop === -1) { + // marker to abort the reveal range request + return false; + } // validate the new desired scroll top let newScrollPosition = this._context.viewLayout.validateScrollPosition({ scrollTop: desiredScrollTop }); if (e.revealHorizontal) { - if (e.range.startLineNumber !== e.range.endLineNumber) { + if (e.range && e.range.startLineNumber !== e.range.endLineNumber) { // Two or more lines? => scroll to base (That's how you see most of the two lines) newScrollPosition = { scrollTop: newScrollPosition.scrollTop, scrollLeft: 0 }; - } else { + } else if (e.range) { // We don't necessarily know the horizontal offset of this range since the line might not be in the view... - this._horizontalRevealRequest = new HorizontalRevealRequest(e.range.startLineNumber, e.range.startColumn, e.range.endColumn, this._context.viewLayout.getCurrentScrollTop(), newScrollPosition.scrollTop, e.scrollType); + this._horizontalRevealRequest = new HorizontalRevealRangeRequest(e.range.startLineNumber, e.range.startColumn, e.range.endColumn, this._context.viewLayout.getCurrentScrollTop(), newScrollPosition.scrollTop, e.scrollType); + } else if (e.selections && e.selections.length > 0) { + this._horizontalRevealRequest = new HorizontalRevealSelectionsRequest(e.selections, this._context.viewLayout.getCurrentScrollTop(), newScrollPosition.scrollTop, e.scrollType); } } else { this._horizontalRevealRequest = null; @@ -481,6 +519,37 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, return allWidthsComputed; } + private _checkMonospaceFontAssumptions(): void { + // Problems with monospace assumptions are more apparent for longer lines, + // as small rounding errors start to sum up, so we will select the longest + // line for a closer inspection + let longestLineNumber = -1; + let longestWidth = -1; + const rendStartLineNumber = this._visibleLines.getStartLineNumber(); + const rendEndLineNumber = this._visibleLines.getEndLineNumber(); + for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) { + const visibleLine = this._visibleLines.getVisibleLine(lineNumber); + if (visibleLine.needsMonospaceFontCheck()) { + const lineWidth = visibleLine.getWidth(); + if (lineWidth > longestWidth) { + longestWidth = lineWidth; + longestLineNumber = lineNumber; + } + } + } + + if (longestLineNumber === -1) { + return; + } + + if (!this._visibleLines.getVisibleLine(longestLineNumber).monospaceAssumptionsAreValid()) { + for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) { + const visibleLine = this._visibleLines.getVisibleLine(lineNumber); + visibleLine.onMonospaceAssumptionsInvalidated(); + } + } + } + public prepareRender(): void { throw new Error('Not supported'); } @@ -501,13 +570,10 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, // - it might change `scrollWidth` and `scrollLeft` if (this._horizontalRevealRequest) { - const revealLineNumber = this._horizontalRevealRequest.lineNumber; - const revealStartColumn = this._horizontalRevealRequest.startColumn; - const revealEndColumn = this._horizontalRevealRequest.endColumn; - const scrollType = this._horizontalRevealRequest.scrollType; + const horizontalRevealRequest = this._horizontalRevealRequest; // Check that we have the line that contains the horizontal range in the viewport - if (viewportData.startLineNumber <= revealLineNumber && revealLineNumber <= viewportData.endLineNumber) { + if (viewportData.startLineNumber <= horizontalRevealRequest.minLineNumber && horizontalRevealRequest.maxLineNumber <= viewportData.endLineNumber) { this._horizontalRevealRequest = null; @@ -515,23 +581,23 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, this.onDidRender(); // compute new scroll position - const newScrollLeft = this._computeScrollLeftToRevealRange(revealLineNumber, revealStartColumn, revealEndColumn); + const newScrollLeft = this._computeScrollLeftToReveal(horizontalRevealRequest); - const isViewportWrapping = this._isViewportWrapping; - if (!isViewportWrapping) { - // ensure `scrollWidth` is large enough - this._ensureMaxLineWidth(newScrollLeft.maxHorizontalOffset); - } - - // set `scrollLeft` - if (scrollType === ScrollType.Smooth) { - this._context.viewLayout.setScrollPositionSmooth({ - scrollLeft: newScrollLeft.scrollLeft - }); - } else { - this._context.viewLayout.setScrollPositionNow({ - scrollLeft: newScrollLeft.scrollLeft - }); + if (newScrollLeft) { + if (!this._isViewportWrapping) { + // ensure `scrollWidth` is large enough + this._ensureMaxLineWidth(newScrollLeft.maxHorizontalOffset); + } + // set `scrollLeft` + if (horizontalRevealRequest.scrollType === ScrollType.Smooth) { + this._context.viewLayout.setScrollPositionSmooth({ + scrollLeft: newScrollLeft.scrollLeft + }); + } else { + this._context.viewLayout.setScrollPositionNow({ + scrollLeft: newScrollLeft.scrollLeft + }); + } } } } @@ -542,6 +608,18 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, this._asyncUpdateLineWidths.schedule(); } + if (platform.isLinux && !this._asyncCheckMonospaceFontAssumptions.isScheduled()) { + const rendStartLineNumber = this._visibleLines.getStartLineNumber(); + const rendEndLineNumber = this._visibleLines.getEndLineNumber(); + for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) { + const visibleLine = this._visibleLines.getVisibleLine(lineNumber); + if (visibleLine.needsMonospaceFontCheck()) { + this._asyncCheckMonospaceFontAssumptions.schedule(); + break; + } + } + } + // (3) handle scrolling this._linesContent.setLayerHinting(this._canUseLayerHinting); this._linesContent.setContain('strict'); @@ -560,16 +638,33 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, } } - private _computeScrollTopToRevealRange(viewport: Viewport, source: string, range: Range, verticalType: viewEvents.VerticalRevealType): number { + private _computeScrollTopToRevealRange(viewport: Viewport, source: string, range: Range | null, selections: Selection[] | null, verticalType: viewEvents.VerticalRevealType): number { const viewportStartY = viewport.top; const viewportHeight = viewport.height; const viewportEndY = viewportStartY + viewportHeight; + let boxIsSingleRange: boolean; let boxStartY: number; let boxEndY: number; // Have a box that includes one extra line height (for the horizontal scrollbar) - boxStartY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.startLineNumber); - boxEndY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.endLineNumber) + this._lineHeight; + if (selections && selections.length > 0) { + let minLineNumber = selections[0].startLineNumber; + let maxLineNumber = selections[0].endLineNumber; + for (let i = 1, len = selections.length; i < len; i++) { + const selection = selections[i]; + minLineNumber = Math.min(minLineNumber, selection.startLineNumber); + maxLineNumber = Math.max(maxLineNumber, selection.endLineNumber); + } + boxIsSingleRange = false; + boxStartY = this._context.viewLayout.getVerticalOffsetForLineNumber(minLineNumber); + boxEndY = this._context.viewLayout.getVerticalOffsetForLineNumber(maxLineNumber) + this._lineHeight; + } else if (range) { + boxIsSingleRange = true; + boxStartY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.startLineNumber); + boxEndY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.endLineNumber) + this._lineHeight; + } else { + return -1; + } const shouldIgnoreScrollOff = source === 'mouse' && this._cursorSurroundingLinesStyle === 'default'; @@ -588,6 +683,10 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, if (boxEndY - boxStartY > viewportHeight) { // the box is larger than the viewport ... scroll to its top + if (!boxIsSingleRange) { + // do not reveal multiple cursors if there are more than fit the viewport + return -1; + } newScrollTop = boxStartY; } else if (verticalType === viewEvents.VerticalRevealType.NearTop || verticalType === viewEvents.VerticalRevealType.NearTopIfOutsideViewport) { if (verticalType === viewEvents.VerticalRevealType.NearTopIfOutsideViewport && viewportStartY <= boxStartY && boxEndY <= viewportEndY) { @@ -618,44 +717,50 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, return newScrollTop; } - private _computeScrollLeftToRevealRange(lineNumber: number, startColumn: number, endColumn: number): { scrollLeft: number; maxHorizontalOffset: number; } { - - let maxHorizontalOffset = 0; + private _computeScrollLeftToReveal(horizontalRevealRequest: HorizontalRevealRequest): { scrollLeft: number; maxHorizontalOffset: number; } | null { const viewport = this._context.viewLayout.getCurrentViewport(); const viewportStartX = viewport.left; const viewportEndX = viewportStartX + viewport.width; - const visibleRanges = this._visibleRangesForLineRange(lineNumber, startColumn, endColumn); let boxStartX = Constants.MAX_SAFE_SMALL_INTEGER; let boxEndX = 0; - - if (!visibleRanges) { - // Unknown - return { - scrollLeft: viewportStartX, - maxHorizontalOffset: maxHorizontalOffset - }; - } - - for (const visibleRange of visibleRanges.ranges) { - if (visibleRange.left < boxStartX) { - boxStartX = visibleRange.left; + if (horizontalRevealRequest.type === 'range') { + const visibleRanges = this._visibleRangesForLineRange(horizontalRevealRequest.lineNumber, horizontalRevealRequest.startColumn, horizontalRevealRequest.endColumn); + if (!visibleRanges) { + return null; } - if (visibleRange.left + visibleRange.width > boxEndX) { - boxEndX = visibleRange.left + visibleRange.width; + for (const visibleRange of visibleRanges.ranges) { + boxStartX = Math.min(boxStartX, visibleRange.left); + boxEndX = Math.max(boxEndX, visibleRange.left + visibleRange.width); + } + } else { + for (const selection of horizontalRevealRequest.selections) { + if (selection.startLineNumber !== selection.endLineNumber) { + return null; + } + const visibleRanges = this._visibleRangesForLineRange(selection.startLineNumber, selection.startColumn, selection.endColumn); + if (!visibleRanges) { + return null; + } + for (const visibleRange of visibleRanges.ranges) { + boxStartX = Math.min(boxStartX, visibleRange.left); + boxEndX = Math.max(boxEndX, visibleRange.left + visibleRange.width); + } } } - maxHorizontalOffset = boxEndX; - boxStartX = Math.max(0, boxStartX - ViewLines.HORIZONTAL_EXTRA_PX); boxEndX += this._revealHorizontalRightPadding; + if (horizontalRevealRequest.type === 'selections' && boxEndX - boxStartX > viewport.width) { + return null; + } + const newScrollLeft = this._computeMinimumScrolling(viewportStartX, viewportEndX, boxStartX, boxEndX); return { scrollLeft: newScrollLeft, - maxHorizontalOffset: maxHorizontalOffset + maxHorizontalOffset: boxEndX }; } diff --git a/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts b/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts index fc6a255eb83..6e40faf77b4 100644 --- a/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts +++ b/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts @@ -78,6 +78,10 @@ export class LinesDecorationsOverlay extends DedupOverlay { if (linesDecorationsClassName) { r[rLen++] = new DecorationToRender(d.range.startLineNumber, d.range.endLineNumber, linesDecorationsClassName); } + const firstLineDecorationClassName = d.options.firstLineDecorationClassName; + if (firstLineDecorationClassName) { + r[rLen++] = new DecorationToRender(d.range.startLineNumber, d.range.startLineNumber, firstLineDecorationClassName); + } } return r; } diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 4c1fe639300..40dd3bc3f8e 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -1012,6 +1012,7 @@ export class Minimap extends ViewPart implements IMinimapModel { this._context.privateViewEventBus.emit(new viewEvents.ViewRevealRangeRequestEvent( 'mouse', new Range(lineNumber, 1, lineNumber, 1), + null, viewEvents.VerticalRevealType.Center, false, ScrollType.Smooth diff --git a/src/vs/editor/browser/viewParts/selections/selections.ts b/src/vs/editor/browser/viewParts/selections/selections.ts index d50b0f5679b..bdedea154e3 100644 --- a/src/vs/editor/browser/viewParts/selections/selections.ts +++ b/src/vs/editor/browser/viewParts/selections/selections.ts @@ -60,7 +60,7 @@ function toStyled(item: LineVisibleRanges): LineVisibleRangesWithStyle { // TODO@Alex: Remove this once IE11 fixes Bug #524217 // The problem in IE11 is that it does some sort of auto-zooming to accomodate for displays with different pixel density. // Unfortunately, this auto-zooming is buggy around dealing with rounded borders -const isIEWithZoomingIssuesNearRoundedBorders = browser.isEdgeOrIE; +const isIEWithZoomingIssuesNearRoundedBorders = browser.isEdge; export class SelectionsOverlay extends DynamicViewOverlay { diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index dd790f97f10..cb0c34760f3 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -460,6 +460,13 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return this._modelData.viewModel.getVisibleRanges(); } + public getVisibleRangesPlusViewportAboveBelow(): Range[] { + if (!this._modelData) { + return []; + } + return this._modelData.viewModel.getVisibleRangesPlusViewportAboveBelow(); + } + public getWhitespaces(): IEditorWhitespace[] { if (!this._modelData) { return []; @@ -550,7 +557,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE const validatedModelRange = this._modelData.model.validateRange(modelRange); const viewRange = this._modelData.viewModel.coordinatesConverter.convertModelRangeToViewRange(validatedModelRange); - this._modelData.cursor.emitCursorRevealRange('api', viewRange, verticalType, revealHorizontal, scrollType); + this._modelData.cursor.emitCursorRevealRange('api', viewRange, null, verticalType, revealHorizontal, scrollType); } public revealLine(lineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index a843f7297a6..f70f91150ff 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -39,7 +39,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { defaultInsertColor, defaultRemoveColor, diffBorder, diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, scrollbarShadow, scrollbarSliderBackground, scrollbarSliderHoverBackground, scrollbarSliderActiveBackground } from 'vs/platform/theme/common/colorRegistry'; -import { ITheme, IThemeService, getThemeTypeSelector, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService, getThemeTypeSelector, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IDiffLinesChange, InlineDiffMargin } from 'vs/editor/browser/widget/inlineDiffMargin'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -71,7 +71,7 @@ interface IEditorsZones { interface IDiffEditorWidgetStyle { getEditorsDiffDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalWhitespaces: IEditorWhitespace[], modifiedWhitespaces: IEditorWhitespace[], originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorsDiffDecorationsWithZones; setEnableSplitViewResizing(enableSplitViewResizing: boolean): void; - applyColors(theme: ITheme): boolean; + applyColors(theme: IColorTheme): boolean; layout(): number; dispose(): void; } @@ -278,7 +278,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._updateDecorationsRunner = this._register(new RunOnceScheduler(() => this._updateDecorations(), 0)); this._containerDomElement = document.createElement('div'); - this._containerDomElement.className = DiffEditorWidget._getClassName(this._themeService.getTheme(), this._renderSideBySide); + this._containerDomElement.className = DiffEditorWidget._getClassName(this._themeService.getColorTheme(), this._renderSideBySide); this._containerDomElement.style.position = 'relative'; this._containerDomElement.style.height = '100%'; this._domElement.appendChild(this._containerDomElement); @@ -368,11 +368,11 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._setStrategy(new DiffEditorWidgetInline(this._createDataSource(), this._enableSplitViewResizing)); } - this._register(themeService.onThemeChange(t => { + this._register(themeService.onDidColorThemeChange(t => { if (this._strategy && this._strategy.applyColors(t)) { this._updateDecorationsRunner.schedule(); } - this._containerDomElement.className = DiffEditorWidget._getClassName(this._themeService.getTheme(), this._renderSideBySide); + this._containerDomElement.className = DiffEditorWidget._getClassName(this._themeService.getColorTheme(), this._renderSideBySide); })); const contributions: IDiffEditorContributionDescription[] = EditorExtensionsRegistry.getDiffEditorContributions(); @@ -431,7 +431,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._reviewPane.prev(); } - private static _getClassName(theme: ITheme, renderSideBySide: boolean): string { + private static _getClassName(theme: IColorTheme, renderSideBySide: boolean): string { let result = 'monaco-diff-editor monaco-editor-background '; if (renderSideBySide) { result += 'side-by-side '; @@ -674,7 +674,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._setStrategy(new DiffEditorWidgetInline(this._createDataSource(), this._enableSplitViewResizing)); } // Update class name - this._containerDomElement.className = DiffEditorWidget._getClassName(this._themeService.getTheme(), this._renderSideBySide); + this._containerDomElement.className = DiffEditorWidget._getClassName(this._themeService.getColorTheme(), this._renderSideBySide); } } @@ -1172,7 +1172,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } this._strategy = newStrategy; - newStrategy.applyColors(this._themeService.getTheme()); + newStrategy.applyColors(this._themeService.getColorTheme()); if (this._diffComputationResult) { this._updateDecorations(); @@ -1296,7 +1296,7 @@ abstract class DiffEditorWidgetStyle extends Disposable implements IDiffEditorWi this._removeColor = null; } - public applyColors(theme: ITheme): boolean { + public applyColors(theme: IColorTheme): boolean { let newInsertColor = (theme.getColor(diffInserted) || defaultInsertColor).transparent(2); let newRemoveColor = (theme.getColor(diffRemoved) || defaultRemoveColor).transparent(2); let hasChanges = !newInsertColor.equals(this._insertColor) || !newRemoveColor.equals(this._removeColor); diff --git a/src/vs/editor/browser/widget/diffReview.ts b/src/vs/editor/browser/widget/diffReview.ts index eb22ce3dba0..d9ee8f481eb 100644 --- a/src/vs/editor/browser/widget/diffReview.ts +++ b/src/vs/editor/browser/widget/diffReview.ts @@ -30,6 +30,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { scrollbarShadow } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { Constants } from 'vs/base/common/uint'; const DIFF_LINES_PADDING = 3; @@ -124,16 +125,6 @@ export class DiffReview extends Disposable { } this._render(); })); - this._register(diffEditor.getOriginalEditor().onDidFocusEditorWidget(() => { - if (this._isVisible) { - this.hide(); - } - })); - this._register(diffEditor.getModifiedEditor().onDidFocusEditorWidget(() => { - if (this._isVisible) { - this.hide(); - } - })); this._register(dom.addStandardDisposableListener(this.domNode.domNode, 'click', (e) => { e.preventDefault(); @@ -209,7 +200,9 @@ export class DiffReview extends Disposable { } index = index % this._diffs.length; - this._diffEditor.setPosition(new Position(this._diffs[index].entries[0].modifiedLineStart, 1)); + const entries = this._diffs[index].entries; + this._diffEditor.setPosition(new Position(entries[0].modifiedLineStart, 1)); + this._diffEditor.setSelection({ startColumn: 1, startLineNumber: entries[0].modifiedLineStart, endColumn: Constants.MAX_SAFE_SMALL_INTEGER, endLineNumber: entries[entries.length - 1].modifiedLineEnd }); this._isVisible = true; this._diffEditor.doLayout(); this._render(); @@ -242,7 +235,9 @@ export class DiffReview extends Disposable { } index = index % this._diffs.length; - this._diffEditor.setPosition(new Position(this._diffs[index].entries[0].modifiedLineStart, 1)); + const entries = this._diffs[index].entries; + this._diffEditor.setPosition(new Position(entries[0].modifiedLineStart, 1)); + this._diffEditor.setSelection({ startColumn: 1, startLineNumber: entries[0].modifiedLineStart, endColumn: Constants.MAX_SAFE_SMALL_INTEGER, endLineNumber: entries[entries.length - 1].modifiedLineEnd }); this._isVisible = true; this._diffEditor.doLayout(); this._render(); @@ -551,6 +546,7 @@ export class DiffReview extends Disposable { let container = document.createElement('div'); container.className = 'diff-review-table'; container.setAttribute('role', 'list'); + container.setAttribute('aria-label', 'Difference review. Use "Stage | Unstage | Revert Selected Ranges" commands'); Configuration.applyFontInfoSlow(container, modifiedOptions.get(EditorOption.fontInfo)); let minOriginalLine = 0; @@ -590,11 +586,11 @@ export class DiffReview extends Disposable { const getAriaLines = (lines: number) => { if (lines === 0) { - return nls.localize('no_lines', "no lines"); + return nls.localize('no_lines_changed', "no lines changed"); } else if (lines === 1) { - return nls.localize('one_line', "1 line"); + return nls.localize('one_line_changed', "1 line changed"); } else { - return nls.localize('more_lines', "{0} lines", lines); + return nls.localize('more_lines_changed', "{0} lines changed", lines); } }; @@ -608,9 +604,9 @@ export class DiffReview extends Disposable { 'That encodes that at original line 154 (which is now line 159), 12 lines were removed/changed with 39 lines.', 'Variables 0 and 1 refer to the diff index out of total number of diffs.', 'Variables 2 and 4 will be numbers (a line number).', - 'Variables 3 and 5 will be "no lines", "1 line" or "X lines", localized separately.' + 'Variables 3 and 5 will be "no lines changed", "1 line changed" or "X lines changed", localized separately.' ] - }, "Difference {0} of {1}: original {2}, {3}, modified {4}, {5}", (diffIndex + 1), this._diffs.length, minOriginalLine, originalChangedLinesCntAria, minModifiedLine, modifiedChangedLinesCntAria)); + }, "Difference {0} of {1}: original line {2}, {3}, modified line {4}, {5}", (diffIndex + 1), this._diffs.length, minOriginalLine, originalChangedLinesCntAria, minModifiedLine, modifiedChangedLinesCntAria)); header.appendChild(cell); // @@ -504,7 +517,7 @@ @@ -747,13 +743,13 @@ export class DiffReview extends Disposable { let ariaLabel: string = ''; switch (type) { case DiffEntryType.Equal: - ariaLabel = nls.localize('equalLine', "original {0}, modified {1}: {2}", originalLine, modifiedLine, lineContent); + ariaLabel = nls.localize('equalLine', "{0} original line {1} modified line {2}", lineContent, originalLine, modifiedLine); break; case DiffEntryType.Insert: - ariaLabel = nls.localize('insertLine', "+ modified {0}: {1}", modifiedLine, lineContent); + ariaLabel = nls.localize('insertLine', "+ {0} modified line {1}", lineContent, modifiedLine); break; case DiffEntryType.Delete: - ariaLabel = nls.localize('deleteLine', "- original {0}: {1}", originalLine, lineContent); + ariaLabel = nls.localize('deleteLine', "- {0} original line {1}", lineContent, originalLine); break; } row.setAttribute('aria-label', ariaLabel); @@ -869,9 +865,14 @@ class DiffReviewPrev extends EditorAction { function findFocusedDiffEditor(accessor: ServicesAccessor): DiffEditorWidget | null { const codeEditorService = accessor.get(ICodeEditorService); const diffEditors = codeEditorService.listDiffEditors(); + const activeCodeEditor = codeEditorService.getActiveCodeEditor(); + if (!activeCodeEditor) { + return null; + } + for (let i = 0, len = diffEditors.length; i < len; i++) { const diffEditor = diffEditors[i]; - if (diffEditor.hasWidgetFocus()) { + if (diffEditor.getModifiedEditor().getId() === activeCodeEditor.getId() || diffEditor.getOriginalEditor().getId() === activeCodeEditor.getId()) { return diffEditor; } } diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 284c6f904bb..4b817e35d9f 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -135,6 +135,11 @@ export interface IEditorOptions { * Defaults to false. */ readOnly?: boolean; + /** + * Rename matching regions on type. + * Defaults to false. + */ + renameOnType?: boolean; /** * Should the editor render validation decorations. * Defaults to editable. @@ -541,6 +546,11 @@ export interface IEditorOptions { * Defaults to all. */ renderLineHighlight?: 'none' | 'gutter' | 'line' | 'all'; + /** + * Control if the current line highlight should be rendered only the editor is focused. + * Defaults to false. + */ + renderLineHighlightOnlyWhenFocus?: boolean; /** * Inserting and deleting whitespace follows tab stops. */ @@ -1332,7 +1342,7 @@ export class EditorFontLigatures extends BaseEditorOption 1) { - // no revealing! + this.emitCursorRevealRange(source, null, this._cursors.getViewSelections(), verticalType, revealHorizontal, scrollType); return; } } const viewRange = new Range(viewPosition.lineNumber, viewPosition.column, viewPosition.lineNumber, viewPosition.column); - this.emitCursorRevealRange(source, viewRange, verticalType, revealHorizontal, scrollType); + this.emitCursorRevealRange(source, viewRange, null, verticalType, revealHorizontal, scrollType); } - public emitCursorRevealRange(source: string, viewRange: Range, verticalType: viewEvents.VerticalRevealType, revealHorizontal: boolean, scrollType: editorCommon.ScrollType) { + public emitCursorRevealRange(source: string, viewRange: Range | null, viewSelections: Selection[] | null, verticalType: viewEvents.VerticalRevealType, revealHorizontal: boolean, scrollType: editorCommon.ScrollType) { try { const eventsCollector = this._beginEmit(); - eventsCollector.emit(new viewEvents.ViewRevealRangeRequestEvent(source, viewRange, verticalType, revealHorizontal, scrollType)); + eventsCollector.emit(new viewEvents.ViewRevealRangeRequestEvent(source, viewRange, viewSelections, verticalType, revealHorizontal, scrollType)); } finally { this._endEmit(); } @@ -779,7 +779,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { } private _type(source: string, text: string): void { - if (!this._isDoingComposition && source === 'keyboard') { + if (source === 'keyboard') { // If this event is coming straight from the keyboard, look for electric characters and enter const len = text.length; @@ -790,7 +790,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { // Here we must interpret each typed character individually const autoClosedCharacters = AutoClosedAction.getAllAutoClosedCharacters(this._autoClosedActions); - this._executeEditOperation(TypeOperations.typeWithInterceptors(this._prevEditOperationType, this.context.config, this.context.model, this.getSelections(), autoClosedCharacters, chr)); + this._executeEditOperation(TypeOperations.typeWithInterceptors(this._isDoingComposition, this._prevEditOperationType, this.context.config, this.context.model, this.getSelections(), autoClosedCharacters, chr)); offset += charLength; } diff --git a/src/vs/editor/common/controller/cursorMoveCommands.ts b/src/vs/editor/common/controller/cursorMoveCommands.ts index 759ad0e19fd..8545cb2a485 100644 --- a/src/vs/editor/common/controller/cursorMoveCommands.ts +++ b/src/vs/editor/common/controller/cursorMoveCommands.ts @@ -411,7 +411,7 @@ export class CursorMoveCommands { let newViewState = MoveOperations.moveLeft(context.config, context.viewModel, cursor.viewState, inSelectionMode, noOfColumns); - if (noOfColumns === 1 && newViewState.position.lineNumber !== cursor.viewState.position.lineNumber) { + if (!cursor.viewState.hasSelection() && noOfColumns === 1 && newViewState.position.lineNumber !== cursor.viewState.position.lineNumber) { // moved over to the previous view line const newViewModelPosition = context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(newViewState.position); if (newViewModelPosition.lineNumber === cursor.modelState.position.lineNumber) { @@ -442,7 +442,7 @@ export class CursorMoveCommands { const cursor = cursors[i]; let newViewState = MoveOperations.moveRight(context.config, context.viewModel, cursor.viewState, inSelectionMode, noOfColumns); - if (noOfColumns === 1 && newViewState.position.lineNumber !== cursor.viewState.position.lineNumber) { + if (!cursor.viewState.hasSelection() && noOfColumns === 1 && newViewState.position.lineNumber !== cursor.viewState.position.lineNumber) { // moved over to the next view line const newViewModelPosition = context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(newViewState.position); if (newViewModelPosition.lineNumber === cursor.modelState.position.lineNumber) { diff --git a/src/vs/editor/common/controller/cursorMoveOperations.ts b/src/vs/editor/common/controller/cursorMoveOperations.ts index df45d78e9f9..0687dbb6426 100644 --- a/src/vs/editor/common/controller/cursorMoveOperations.ts +++ b/src/vs/editor/common/controller/cursorMoveOperations.ts @@ -91,9 +91,10 @@ export class MoveOperations { public static down(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnLastLine: boolean): CursorPosition { const currentVisibleColumn = CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize) + leftoverVisibleColumns; + const lineCount = model.getLineCount(); + const wasOnLastPosition = (lineNumber === lineCount && column === model.getLineMaxColumn(lineNumber)); lineNumber = lineNumber + count; - let lineCount = model.getLineCount(); if (lineNumber > lineCount) { lineNumber = lineCount; if (allowMoveOnLastLine) { @@ -105,7 +106,11 @@ export class MoveOperations { column = CursorColumns.columnFromVisibleColumn2(config, model, lineNumber, currentVisibleColumn); } - leftoverVisibleColumns = currentVisibleColumn - CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize); + if (wasOnLastPosition) { + leftoverVisibleColumns = 0; + } else { + leftoverVisibleColumns = currentVisibleColumn - CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize); + } return new CursorPosition(lineNumber, column, leftoverVisibleColumns); } @@ -144,6 +149,7 @@ export class MoveOperations { public static up(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnFirstLine: boolean): CursorPosition { const currentVisibleColumn = CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize) + leftoverVisibleColumns; + const wasOnFirstPosition = (lineNumber === 1 && column === 1); lineNumber = lineNumber - count; if (lineNumber < 1) { @@ -157,7 +163,11 @@ export class MoveOperations { column = CursorColumns.columnFromVisibleColumn2(config, model, lineNumber, currentVisibleColumn); } - leftoverVisibleColumns = currentVisibleColumn - CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize); + if (wasOnFirstPosition) { + leftoverVisibleColumns = 0; + } else { + leftoverVisibleColumns = currentVisibleColumn - CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize); + } return new CursorPosition(lineNumber, column, leftoverVisibleColumns); } diff --git a/src/vs/editor/common/controller/cursorTypeOperations.ts b/src/vs/editor/common/controller/cursorTypeOperations.ts index 64db6112da9..0155bf76b6e 100644 --- a/src/vs/editor/common/controller/cursorTypeOperations.ts +++ b/src/vs/editor/common/controller/cursorTypeOperations.ts @@ -269,9 +269,15 @@ export class TypeOperations { commands[i] = null; continue; } - let pos = selection.getPosition(); - let startColumn = Math.max(1, pos.column - replaceCharCnt); - let range = new Range(pos.lineNumber, startColumn, pos.lineNumber, pos.column); + const pos = selection.getPosition(); + const startColumn = Math.max(1, pos.column - replaceCharCnt); + const range = new Range(pos.lineNumber, startColumn, pos.lineNumber, pos.column); + const oldText = model.getValueInRange(range); + if (oldText === txt) { + // => ignore composition that doesn't do anything + commands[i] = null; + continue; + } commands[i] = new ReplaceCommand(range, txt); } return new EditOperationResult(EditOperationType.Typing, commands, { @@ -796,9 +802,9 @@ export class TypeOperations { return null; } - public static typeWithInterceptors(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], autoClosedCharacters: Range[], ch: string): EditOperationResult { + public static typeWithInterceptors(isDoingComposition: boolean, prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], autoClosedCharacters: Range[], ch: string): EditOperationResult { - if (ch === '\n') { + if (!isDoingComposition && ch === '\n') { let commands: ICommand[] = []; for (let i = 0, len = selections.length; i < len; i++) { commands[i] = TypeOperations._enter(config, model, false, selections[i]); @@ -809,7 +815,7 @@ export class TypeOperations { }); } - if (this._isAutoIndentType(config, model, selections)) { + if (!isDoingComposition && this._isAutoIndentType(config, model, selections)) { let commands: Array = []; let autoIndentFails = false; for (let i = 0, len = selections.length; i < len; i++) { @@ -827,13 +833,15 @@ export class TypeOperations { } } - if (this._isAutoClosingOvertype(config, model, selections, autoClosedCharacters, ch)) { + if (!isDoingComposition && this._isAutoClosingOvertype(config, model, selections, autoClosedCharacters, ch)) { return this._runAutoClosingOvertype(prevEditOperationType, config, model, selections, ch); } - const autoClosingPairOpenCharType = this._isAutoClosingOpenCharType(config, model, selections, ch, true); - if (autoClosingPairOpenCharType) { - return this._runAutoClosingOpenCharType(prevEditOperationType, config, model, selections, ch, true, autoClosingPairOpenCharType); + if (!isDoingComposition) { + const autoClosingPairOpenCharType = this._isAutoClosingOpenCharType(config, model, selections, ch, true); + if (autoClosingPairOpenCharType) { + return this._runAutoClosingOpenCharType(prevEditOperationType, config, model, selections, ch, true, autoClosingPairOpenCharType); + } } if (this._isSurroundSelectionType(config, model, selections, ch)) { @@ -842,7 +850,7 @@ export class TypeOperations { // Electric characters make sense only when dealing with a single cursor, // as multiple cursors typing brackets for example would interfer with bracket matching - if (this._isTypeInterceptorElectricChar(config, model, selections)) { + if (!isDoingComposition && this._isTypeInterceptorElectricChar(config, model, selections)) { const r = this._typeInterceptorElectricChar(prevEditOperationType, config, model, selections[0], ch); if (r) { return r; diff --git a/src/vs/editor/common/controller/cursorWordOperations.ts b/src/vs/editor/common/controller/cursorWordOperations.ts index ba9e5f557a2..13acacc4cbe 100644 --- a/src/vs/editor/common/controller/cursorWordOperations.ts +++ b/src/vs/editor/common/controller/cursorWordOperations.ts @@ -238,7 +238,7 @@ export class WordOperations { const left = lineContent.charCodeAt(column - 2); const right = lineContent.charCodeAt(column - 1); - if (left !== CharCode.Underline && right === CharCode.Underline) { + if (left === CharCode.Underline && right !== CharCode.Underline) { // snake_case_variables return new Position(lineNumber, column); } @@ -340,7 +340,7 @@ export class WordOperations { const left = lineContent.charCodeAt(column - 2); const right = lineContent.charCodeAt(column - 1); - if (left === CharCode.Underline && right !== CharCode.Underline) { + if (left !== CharCode.Underline && right === CharCode.Underline) { // snake_case_variables return new Position(lineNumber, column); } diff --git a/src/vs/editor/common/core/stringBuilder.ts b/src/vs/editor/common/core/stringBuilder.ts index 661fd6642e2..8a577ab8de2 100644 --- a/src/vs/editor/common/core/stringBuilder.ts +++ b/src/vs/editor/common/core/stringBuilder.ts @@ -4,8 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as strings from 'vs/base/common/strings'; +import * as platform from 'vs/base/common/platform'; +import * as buffer from 'vs/base/common/buffer'; -declare const TextDecoder: any; // TODO@TypeScript +declare const TextDecoder: { + prototype: TextDecoder; + new(label?: string): TextDecoder; +}; interface TextDecoder { decode(view: Uint16Array): string; } @@ -18,17 +23,43 @@ export interface IStringBuilder { appendASCIIString(str: string): void; } -export let createStringBuilder: (capacity: number) => IStringBuilder; +let _platformTextDecoder: TextDecoder | null; +export function getPlatformTextDecoder(): TextDecoder { + if (!_platformTextDecoder) { + _platformTextDecoder = new TextDecoder(platform.isLittleEndian() ? 'UTF-16LE' : 'UTF-16BE'); + } + return _platformTextDecoder; +} -if (typeof TextDecoder !== 'undefined') { +export const hasTextDecoder = (typeof TextDecoder !== 'undefined'); +export let createStringBuilder: (capacity: number) => IStringBuilder; +export let decodeUTF16LE: (source: Uint8Array, offset: number, len: number) => string; + +if (hasTextDecoder) { createStringBuilder = (capacity) => new StringBuilder(capacity); + decodeUTF16LE = standardDecodeUTF16LE; } else { createStringBuilder = (capacity) => new CompatStringBuilder(); + decodeUTF16LE = compatDecodeUTF16LE; +} + +function standardDecodeUTF16LE(source: Uint8Array, offset: number, len: number): string { + const view = new Uint16Array(source.buffer, offset, len); + return getPlatformTextDecoder().decode(view); +} + +function compatDecodeUTF16LE(source: Uint8Array, offset: number, len: number): string { + let result: string[] = []; + let resultLen = 0; + for (let i = 0; i < len; i++) { + const charCode = buffer.readUInt16LE(source, offset); offset += 2; + result[resultLen++] = String.fromCharCode(charCode); + } + return result.join(''); } class StringBuilder implements IStringBuilder { - private readonly _decoder: TextDecoder; private readonly _capacity: number; private readonly _buffer: Uint16Array; @@ -36,7 +67,6 @@ class StringBuilder implements IStringBuilder { private _bufferLength: number; constructor(capacity: number) { - this._decoder = new TextDecoder('UTF-16LE'); this._capacity = capacity | 0; this._buffer = new Uint16Array(this._capacity); @@ -63,7 +93,7 @@ class StringBuilder implements IStringBuilder { } const view = new Uint16Array(this._buffer.buffer, 0, this._bufferLength); - return this._decoder.decode(view); + return getPlatformTextDecoder().decode(view); } private _flushBuffer(): void { diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index e176e90ede3..b236bb7fe7e 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -5,6 +5,7 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ConfigurationChangedEvent, IComputedEditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IPosition, Position } from 'vs/editor/common/core/position'; @@ -529,6 +530,24 @@ export interface IDiffEditor extends IEditor { getModifiedEditor(): IEditor; } +/** + * @internal + */ +export interface ICompositeCodeEditor { + + /** + * An event that signals that the active editor has changed + */ + readonly onDidChangeActiveEditor: Event; + + /** + * The active code editor iff any + */ + readonly activeCodeEditor: IEditor | undefined; + // readonly editors: readonly ICodeEditor[] maybe supported with uris +} + + /** * An editor contribution that gets created every time a new editor gets created and gets disposed when the editor gets disposed. */ diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 2bf83cca827..1f24a1e45b3 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -15,6 +15,7 @@ import { SearchData } from 'vs/editor/common/model/textModelSearch'; import { LanguageId, LanguageIdentifier, FormattingOptions } from 'vs/editor/common/modes'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; import { MultilineTokens, MultilineTokens2 } from 'vs/editor/common/model/tokensStore'; +import { TextChange } from 'vs/editor/common/model/textChange'; /** * Vertical Lane in the overview ruler of the editor. @@ -123,6 +124,10 @@ export interface IModelDecorationOptions { * If set, the decoration will be rendered in the lines decorations with this CSS class name. */ linesDecorationsClassName?: string | null; + /** + * If set, the decoration will be rendered in the lines decorations with this CSS class name, but only for the first line in case of line wrapping. + */ + firstLineDecorationClassName?: string | null; /** * If set, the decoration will be rendered in the margin (covering its full width) with this CSS class name. */ @@ -369,21 +374,13 @@ export interface IValidEditOperation { */ range: Range; /** - * The text to replace with. This can be null to emulate a simple delete. + * The text to replace with. This can be empty to emulate a simple delete. */ - text: string | null; + text: string; /** - * This indicates that this operation has "insert" semantics. - * i.e. forceMoveMarkers = true => if `range` is collapsed, all markers at the position will be moved. + * @internal */ - forceMoveMarkers: boolean; -} - -/** - * @internal - */ -export interface IValidEditOperations { - operations: IValidEditOperation[]; + textChange: TextChange; } /** @@ -623,6 +620,12 @@ export interface ITextModel { */ equalsTextBuffer(other: ITextBuffer): boolean; + /** + * Get the underling text buffer. + * @internal + */ + getTextBuffer(): ITextBuffer; + /** * Get the text in a certain range. * @param range The range describing what text to get. @@ -824,7 +827,17 @@ export interface ITextModel { /** * @internal */ - setSemanticTokens(tokens: MultilineTokens2[] | null): void; + setSemanticTokens(tokens: MultilineTokens2[] | null, isComplete: boolean): void; + + /** + * @internal + */ + setPartialSemanticTokens(range: Range, tokens: MultilineTokens2[] | null): void; + + /** + * @internal + */ + hasSemanticTokens(): boolean; /** * Flush all tokenization state. @@ -1089,9 +1102,11 @@ export interface ITextModel { * Edit the model without adding the edits to the undo stack. * This can have dire consequences on the undo stack! See @pushEditOperations for the preferred way. * @param operations The edit operations. - * @return The inverse edit operations, that, when applied, will bring the model back to the previous state. + * @return If desired, the inverse edit operations, that, when applied, will bring the model back to the previous state. */ - applyEdits(operations: IIdentifiedSingleEditOperation[]): IValidEditOperation[]; + applyEdits(operations: IIdentifiedSingleEditOperation[]): void; + applyEdits(operations: IIdentifiedSingleEditOperation[], computeUndoEdits: false): void; + applyEdits(operations: IIdentifiedSingleEditOperation[], computeUndoEdits: true): IValidEditOperation[]; /** * Change the end of line sequence without recording in the undo stack. @@ -1102,7 +1117,12 @@ export interface ITextModel { /** * @internal */ - _applyUndoRedoEdits(edits: IValidEditOperations[], eol: EndOfLineSequence, isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): IValidEditOperations[]; + _applyUndo(changes: TextChange[], eol: EndOfLineSequence, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void; + + /** + * @internal + */ + _applyRedo(changes: TextChange[], eol: EndOfLineSequence, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void; /** * Undo edit operations until the first previous stop point created by `pushStackElement`. @@ -1281,7 +1301,7 @@ export interface ITextBuffer { getLineLastNonWhitespaceColumn(lineNumber: number): number; setEOL(newEOL: '\r\n' | '\n'): void; - applyEdits(rawOperations: ValidAnnotatedEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult; + applyEdits(rawOperations: ValidAnnotatedEditOperation[], recordTrimAutoWhitespace: boolean, computeUndoEdits: boolean): ApplyEditsResult; findMatchesLineByLine(searchRange: Range, searchData: SearchData, captureMatches: boolean, limitResultCount: number): FindMatch[]; } @@ -1291,7 +1311,7 @@ export interface ITextBuffer { export class ApplyEditsResult { constructor( - public readonly reverseEdits: IValidEditOperation[], + public readonly reverseEdits: IValidEditOperation[] | null, public readonly changes: IInternalModelContentChange[], public readonly trimAutoWhitespaceLineNumbers: number[] | null ) { } diff --git a/src/vs/editor/common/model/editStack.ts b/src/vs/editor/common/model/editStack.ts index c0aee2abe10..24c38f88141 100644 --- a/src/vs/editor/common/model/editStack.ts +++ b/src/vs/editor/common/model/editStack.ts @@ -6,69 +6,212 @@ import * as nls from 'vs/nls'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Selection } from 'vs/editor/common/core/selection'; -import { EndOfLineSequence, ICursorStateComputer, IIdentifiedSingleEditOperation, IValidEditOperation, ITextModel, IValidEditOperations } from 'vs/editor/common/model'; +import { EndOfLineSequence, ICursorStateComputer, IIdentifiedSingleEditOperation, IValidEditOperation, ITextModel } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; import { IUndoRedoService, IResourceUndoRedoElement, UndoRedoElementType, IWorkspaceUndoRedoElement } from 'vs/platform/undoRedo/common/undoRedo'; import { URI } from 'vs/base/common/uri'; -import { getComparisonKey as uriGetComparisonKey } from 'vs/base/common/resources'; +import { TextChange, compressConsecutiveTextChanges } from 'vs/editor/common/model/textChange'; +import * as buffer from 'vs/base/common/buffer'; -export class EditStackElement implements IResourceUndoRedoElement { +function uriGetComparisonKey(resource: URI): string { + return resource.toString(); +} - public readonly type = UndoRedoElementType.Resource; - public readonly label: string; - private _isOpen: boolean; - public readonly model: ITextModel; - private readonly _beforeVersionId: number; - private readonly _beforeEOL: EndOfLineSequence; - private readonly _beforeCursorState: Selection[] | null; - private _afterVersionId: number; - private _afterEOL: EndOfLineSequence; - private _afterCursorState: Selection[] | null; - private _edits: IValidEditOperations[]; +class SingleModelEditStackData { + + public static create(model: ITextModel, beforeCursorState: Selection[] | null): SingleModelEditStackData { + const alternativeVersionId = model.getAlternativeVersionId(); + const eol = getModelEOL(model); + return new SingleModelEditStackData( + alternativeVersionId, + alternativeVersionId, + eol, + eol, + beforeCursorState, + beforeCursorState, + [] + ); + } + + constructor( + public readonly beforeVersionId: number, + public afterVersionId: number, + public readonly beforeEOL: EndOfLineSequence, + public afterEOL: EndOfLineSequence, + public readonly beforeCursorState: Selection[] | null, + public afterCursorState: Selection[] | null, + public changes: TextChange[] + ) { } + + public append(model: ITextModel, textChanges: TextChange[], afterEOL: EndOfLineSequence, afterVersionId: number, afterCursorState: Selection[] | null): void { + if (textChanges.length > 0) { + this.changes = compressConsecutiveTextChanges(this.changes, textChanges); + } + this.afterEOL = afterEOL; + this.afterVersionId = afterVersionId; + this.afterCursorState = afterCursorState; + } + + private static _writeSelectionsSize(selections: Selection[] | null): number { + return 4 + 4 * 4 * (selections ? selections.length : 0); + } + + private static _writeSelections(b: Uint8Array, selections: Selection[] | null, offset: number): number { + buffer.writeUInt32BE(b, (selections ? selections.length : 0), offset); offset += 4; + if (selections) { + for (const selection of selections) { + buffer.writeUInt32BE(b, selection.selectionStartLineNumber, offset); offset += 4; + buffer.writeUInt32BE(b, selection.selectionStartColumn, offset); offset += 4; + buffer.writeUInt32BE(b, selection.positionLineNumber, offset); offset += 4; + buffer.writeUInt32BE(b, selection.positionColumn, offset); offset += 4; + } + } + return offset; + } + + private static _readSelections(b: Uint8Array, offset: number, dest: Selection[]): number { + const count = buffer.readUInt32BE(b, offset); offset += 4; + for (let i = 0; i < count; i++) { + const selectionStartLineNumber = buffer.readUInt32BE(b, offset); offset += 4; + const selectionStartColumn = buffer.readUInt32BE(b, offset); offset += 4; + const positionLineNumber = buffer.readUInt32BE(b, offset); offset += 4; + const positionColumn = buffer.readUInt32BE(b, offset); offset += 4; + dest.push(new Selection(selectionStartLineNumber, selectionStartColumn, positionLineNumber, positionColumn)); + } + return offset; + } + + public serialize(): ArrayBuffer { + let necessarySize = ( + + 4 // beforeVersionId + + 4 // afterVersionId + + 1 // beforeEOL + + 1 // afterEOL + + SingleModelEditStackData._writeSelectionsSize(this.beforeCursorState) + + SingleModelEditStackData._writeSelectionsSize(this.afterCursorState) + + 4 // change count + ); + for (const change of this.changes) { + necessarySize += change.writeSize(); + } + + const b = new Uint8Array(necessarySize); + let offset = 0; + buffer.writeUInt32BE(b, this.beforeVersionId, offset); offset += 4; + buffer.writeUInt32BE(b, this.afterVersionId, offset); offset += 4; + buffer.writeUInt8(b, this.beforeEOL, offset); offset += 1; + buffer.writeUInt8(b, this.afterEOL, offset); offset += 1; + offset = SingleModelEditStackData._writeSelections(b, this.beforeCursorState, offset); + offset = SingleModelEditStackData._writeSelections(b, this.afterCursorState, offset); + buffer.writeUInt32BE(b, this.changes.length, offset); offset += 4; + for (const change of this.changes) { + offset = change.write(b, offset); + } + return b.buffer; + } + + public static deserialize(source: ArrayBuffer): SingleModelEditStackData { + const b = new Uint8Array(source); + let offset = 0; + const beforeVersionId = buffer.readUInt32BE(b, offset); offset += 4; + const afterVersionId = buffer.readUInt32BE(b, offset); offset += 4; + const beforeEOL = buffer.readUInt8(b, offset); offset += 1; + const afterEOL = buffer.readUInt8(b, offset); offset += 1; + const beforeCursorState: Selection[] = []; + offset = SingleModelEditStackData._readSelections(b, offset, beforeCursorState); + const afterCursorState: Selection[] = []; + offset = SingleModelEditStackData._readSelections(b, offset, afterCursorState); + const changeCount = buffer.readUInt32BE(b, offset); offset += 4; + const changes: TextChange[] = []; + for (let i = 0; i < changeCount; i++) { + offset = TextChange.read(b, offset, changes); + } + return new SingleModelEditStackData( + beforeVersionId, + afterVersionId, + beforeEOL, + afterEOL, + beforeCursorState, + afterCursorState, + changes + ); + } +} + +export class SingleModelEditStackElement implements IResourceUndoRedoElement { + + public model: ITextModel | URI; + private _data: SingleModelEditStackData | ArrayBuffer; + + public get type(): UndoRedoElementType.Resource { + return UndoRedoElementType.Resource; + } public get resource(): URI { + if (URI.isUri(this.model)) { + return this.model; + } return this.model.uri; } + public get label(): string { + return nls.localize('edit', "Typing"); + } + constructor(model: ITextModel, beforeCursorState: Selection[] | null) { - this.label = nls.localize('edit', "Typing"); - this._isOpen = true; this.model = model; - this._beforeVersionId = this.model.getAlternativeVersionId(); - this._beforeEOL = getModelEOL(this.model); - this._beforeCursorState = beforeCursorState; - this._afterVersionId = this._beforeVersionId; - this._afterEOL = this._beforeEOL; - this._afterCursorState = this._beforeCursorState; - this._edits = []; + this._data = SingleModelEditStackData.create(model, beforeCursorState); + } + + public setModel(model: ITextModel | URI): void { + this.model = model; } public canAppend(model: ITextModel): boolean { - return (this._isOpen && this.model === model); + return (this.model === model && this._data instanceof SingleModelEditStackData); } - public append(model: ITextModel, operations: IValidEditOperation[], afterEOL: EndOfLineSequence, afterVersionId: number, afterCursorState: Selection[] | null): void { - if (operations.length > 0) { - this._edits.push({ operations: operations }); + public append(model: ITextModel, textChanges: TextChange[], afterEOL: EndOfLineSequence, afterVersionId: number, afterCursorState: Selection[] | null): void { + if (this._data instanceof SingleModelEditStackData) { + this._data.append(model, textChanges, afterEOL, afterVersionId, afterCursorState); } - this._afterEOL = afterEOL; - this._afterVersionId = afterVersionId; - this._afterCursorState = afterCursorState; } public close(): void { - this._isOpen = false; + if (this._data instanceof SingleModelEditStackData) { + this._data = this._data.serialize(); + } } public undo(): void { - this._isOpen = false; - this._edits.reverse(); - this._edits = this.model._applyUndoRedoEdits(this._edits, this._beforeEOL, true, false, this._beforeVersionId, this._beforeCursorState); + if (URI.isUri(this.model)) { + // don't have a model + throw new Error(`Invalid SingleModelEditStackElement`); + } + if (this._data instanceof SingleModelEditStackData) { + this._data = this._data.serialize(); + } + const data = SingleModelEditStackData.deserialize(this._data); + this.model._applyUndo(data.changes, data.beforeEOL, data.beforeVersionId, data.beforeCursorState); } public redo(): void { - this._edits.reverse(); - this._edits = this.model._applyUndoRedoEdits(this._edits, this._afterEOL, false, true, this._afterVersionId, this._afterCursorState); + if (URI.isUri(this.model)) { + // don't have a model + throw new Error(`Invalid SingleModelEditStackElement`); + } + if (this._data instanceof SingleModelEditStackData) { + this._data = this._data.serialize(); + } + const data = SingleModelEditStackData.deserialize(this._data); + this.model._applyRedo(data.changes, data.afterEOL, data.afterVersionId, data.afterCursorState); + } + + public heapSize(): number { + if (this._data instanceof SingleModelEditStackData) { + this._data = this._data.serialize(); + } + return this._data.byteLength + 168/*heap overhead*/; } } @@ -78,27 +221,34 @@ export class MultiModelEditStackElement implements IWorkspaceUndoRedoElement { public readonly label: string; private _isOpen: boolean; - private readonly _editStackElementsArr: EditStackElement[]; - private readonly _editStackElementsMap: Map; + private readonly _editStackElementsArr: SingleModelEditStackElement[]; + private readonly _editStackElementsMap: Map; public get resources(): readonly URI[] { - return this._editStackElementsArr.map(editStackElement => editStackElement.model.uri); + return this._editStackElementsArr.map(editStackElement => editStackElement.resource); } constructor( label: string, - editStackElements: EditStackElement[] + editStackElements: SingleModelEditStackElement[] ) { this.label = label; this._isOpen = true; this._editStackElementsArr = editStackElements.slice(0); - this._editStackElementsMap = new Map(); + this._editStackElementsMap = new Map(); for (const editStackElement of this._editStackElementsArr) { - const key = uriGetComparisonKey(editStackElement.model.uri); + const key = uriGetComparisonKey(editStackElement.resource); this._editStackElementsMap.set(key, editStackElement); } } + public setModel(model: ITextModel | URI): void { + const key = uriGetComparisonKey(URI.isUri(model) ? model : model.uri); + if (this._editStackElementsMap.has(key)) { + this._editStackElementsMap.get(key)!.setModel(model); + } + } + public canAppend(model: ITextModel): boolean { if (!this._isOpen) { return false; @@ -111,10 +261,10 @@ export class MultiModelEditStackElement implements IWorkspaceUndoRedoElement { return false; } - public append(model: ITextModel, operations: IValidEditOperation[], afterEOL: EndOfLineSequence, afterVersionId: number, afterCursorState: Selection[] | null): void { + public append(model: ITextModel, textChanges: TextChange[], afterEOL: EndOfLineSequence, afterVersionId: number, afterCursorState: Selection[] | null): void { const key = uriGetComparisonKey(model.uri); const editStackElement = this._editStackElementsMap.get(key)!; - editStackElement.append(model, operations, afterEOL, afterVersionId, afterCursorState); + editStackElement.append(model, textChanges, afterEOL, afterVersionId, afterCursorState); } public close(): void { @@ -135,11 +285,22 @@ export class MultiModelEditStackElement implements IWorkspaceUndoRedoElement { } } + public heapSize(resource: URI): number { + const key = uriGetComparisonKey(resource); + if (this._editStackElementsMap.has(key)) { + const editStackElement = this._editStackElementsMap.get(key)!; + return editStackElement.heapSize(); + } + return 0; + } + public split(): IResourceUndoRedoElement[] { return this._editStackElementsArr; } } +export type EditStackElement = SingleModelEditStackElement | MultiModelEditStackElement; + function getModelEOL(model: ITextModel): EndOfLineSequence { const eol = model.getEOL(); if (eol === '\n') { @@ -149,11 +310,11 @@ function getModelEOL(model: ITextModel): EndOfLineSequence { } } -function isKnownStackElement(element: IResourceUndoRedoElement | IWorkspaceUndoRedoElement | null): element is EditStackElement | MultiModelEditStackElement { +function isKnownStackElement(element: IResourceUndoRedoElement | IWorkspaceUndoRedoElement | null): element is EditStackElement { if (!element) { return false; } - return ((element instanceof EditStackElement) || (element instanceof MultiModelEditStackElement)); + return ((element instanceof SingleModelEditStackElement) || (element instanceof MultiModelEditStackElement)); } export class EditStack { @@ -177,12 +338,12 @@ export class EditStack { this._undoRedoService.removeElements(this._model.uri); } - private _getOrCreateEditStackElement(beforeCursorState: Selection[] | null): EditStackElement | MultiModelEditStackElement { + private _getOrCreateEditStackElement(beforeCursorState: Selection[] | null): EditStackElement { const lastElement = this._undoRedoService.getLastElement(this._model.uri); if (isKnownStackElement(lastElement) && lastElement.canAppend(this._model)) { return lastElement; } - const newElement = new EditStackElement(this._model, beforeCursorState); + const newElement = new SingleModelEditStackElement(this._model, beforeCursorState); this._undoRedoService.pushElement(newElement); return newElement; } @@ -195,9 +356,16 @@ export class EditStack { public pushEditOperation(beforeCursorState: Selection[] | null, editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer | null): Selection[] | null { const editStackElement = this._getOrCreateEditStackElement(beforeCursorState); - const inverseEditOperations = this._model.applyEdits(editOperations); + const inverseEditOperations = this._model.applyEdits(editOperations, true); const afterCursorState = EditStack._computeCursorState(cursorStateComputer, inverseEditOperations); - editStackElement.append(this._model, inverseEditOperations, getModelEOL(this._model), this._model.getAlternativeVersionId(), afterCursorState); + const textChanges = inverseEditOperations.map((op, index) => ({ index: index, textChange: op.textChange })); + textChanges.sort((a, b) => { + if (a.textChange.oldPosition === b.textChange.oldPosition) { + return a.index - b.index; + } + return a.textChange.oldPosition - b.textChange.oldPosition; + }); + editStackElement.append(this._model, textChanges.map(op => op.textChange), getModelEOL(this._model), this._model.getAlternativeVersionId(), afterCursorState); return afterCursorState; } diff --git a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts index 05d11060137..e8e737a23c3 100644 --- a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts +++ b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts @@ -269,7 +269,7 @@ export class PieceTreeBase { protected _buffers!: StringBuffer[]; // 0 is change buffer, others are readonly original buffer. protected _lineCnt!: number; protected _length!: number; - protected _EOL!: string; + protected _EOL!: '\r\n' | '\n'; protected _EOLLength!: number; protected _EOLNormalized!: boolean; private _lastChangeBufferPos!: BufferCursor; @@ -351,7 +351,7 @@ export class PieceTreeBase { } // #region Buffer API - public getEOL(): string { + public getEOL(): '\r\n' | '\n' { return this._EOL; } diff --git a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts index 0d9c539ff8a..3dd4acda9d6 100644 --- a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts +++ b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts @@ -9,6 +9,8 @@ import { Range } from 'vs/editor/common/core/range'; import { ApplyEditsResult, EndOfLinePreference, FindMatch, IInternalModelContentChange, ISingleEditOperationIdentifier, ITextBuffer, ITextSnapshot, ValidAnnotatedEditOperation, IValidEditOperation } from 'vs/editor/common/model'; import { PieceTreeBase, StringBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase'; import { SearchData } from 'vs/editor/common/model/textModelSearch'; +import { countEOL, StringEOL } from 'vs/editor/common/model/tokensStore'; +import { TextChange } from 'vs/editor/common/model/textChange'; export interface IValidatedEditOperation { sortIndex: number; @@ -16,7 +18,10 @@ export interface IValidatedEditOperation { range: Range; rangeOffset: number; rangeLength: number; - lines: string[] | null; + text: string; + eolCount: number; + firstLineLength: number; + lastLineLength: number; forceMoveMarkers: boolean; isAutoWhitespaceEdit: boolean; } @@ -60,7 +65,7 @@ export class PieceTreeTextBuffer implements ITextBuffer { public getBOM(): string { return this._BOM; } - public getEOL(): string { + public getEOL(): '\r\n' | '\n' { return this._pieceTree.getEOL(); } @@ -201,7 +206,7 @@ export class PieceTreeTextBuffer implements ITextBuffer { this._pieceTree.setEOL(newEOL); } - public applyEdits(rawOperations: ValidAnnotatedEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult { + public applyEdits(rawOperations: ValidAnnotatedEditOperation[], recordTrimAutoWhitespace: boolean, computeUndoEdits: boolean): ApplyEditsResult { let mightContainRTL = this._mightContainRTL; let mightContainNonBasicASCII = this._mightContainNonBasicASCII; let canReduceOperations = true; @@ -220,13 +225,34 @@ export class PieceTreeTextBuffer implements ITextBuffer { if (!mightContainNonBasicASCII && op.text) { mightContainNonBasicASCII = !strings.isBasicASCII(op.text); } + + let validText = ''; + let eolCount = 0; + let firstLineLength = 0; + let lastLineLength = 0; + if (op.text) { + let strEOL: StringEOL; + [eolCount, firstLineLength, lastLineLength, strEOL] = countEOL(op.text); + + const bufferEOL = this.getEOL(); + const expectedStrEOL = (bufferEOL === '\r\n' ? StringEOL.CRLF : StringEOL.LF); + if (strEOL === StringEOL.Unknown || strEOL === expectedStrEOL) { + validText = op.text; + } else { + validText = op.text.replace(/\r\n|\r|\n/g, bufferEOL); + } + } + operations[i] = { sortIndex: i, identifier: op.identifier || null, range: validatedRange, rangeOffset: this.getOffsetAt(validatedRange.startLineNumber, validatedRange.startColumn), rangeLength: this.getValueLengthInRange(validatedRange), - lines: op.text ? op.text.split(/\r\n|\r|\n/) : null, + text: validText, + eolCount: eolCount, + firstLineLength: firstLineLength, + lastLineLength: lastLineLength, forceMoveMarkers: Boolean(op.forceMoveMarkers), isAutoWhitespaceEdit: op.isAutoWhitespaceEdit || false }; @@ -254,46 +280,56 @@ export class PieceTreeTextBuffer implements ITextBuffer { } // Delta encode operations - let reverseRanges = PieceTreeTextBuffer._getInverseEditRanges(operations); + let reverseRanges = (computeUndoEdits || recordTrimAutoWhitespace ? PieceTreeTextBuffer._getInverseEditRanges(operations) : []); let newTrimAutoWhitespaceCandidates: { lineNumber: number, oldContent: string }[] = []; + if (recordTrimAutoWhitespace) { + for (let i = 0; i < operations.length; i++) { + let op = operations[i]; + let reverseRange = reverseRanges[i]; - for (let i = 0; i < operations.length; i++) { - let op = operations[i]; - let reverseRange = reverseRanges[i]; - - if (recordTrimAutoWhitespace && op.isAutoWhitespaceEdit && op.range.isEmpty()) { - // Record already the future line numbers that might be auto whitespace removal candidates on next edit - for (let lineNumber = reverseRange.startLineNumber; lineNumber <= reverseRange.endLineNumber; lineNumber++) { - let currentLineContent = ''; - if (lineNumber === reverseRange.startLineNumber) { - currentLineContent = this.getLineContent(op.range.startLineNumber); - if (strings.firstNonWhitespaceIndex(currentLineContent) !== -1) { - continue; + if (op.isAutoWhitespaceEdit && op.range.isEmpty()) { + // Record already the future line numbers that might be auto whitespace removal candidates on next edit + for (let lineNumber = reverseRange.startLineNumber; lineNumber <= reverseRange.endLineNumber; lineNumber++) { + let currentLineContent = ''; + if (lineNumber === reverseRange.startLineNumber) { + currentLineContent = this.getLineContent(op.range.startLineNumber); + if (strings.firstNonWhitespaceIndex(currentLineContent) !== -1) { + continue; + } } + newTrimAutoWhitespaceCandidates.push({ lineNumber: lineNumber, oldContent: currentLineContent }); } - newTrimAutoWhitespaceCandidates.push({ lineNumber: lineNumber, oldContent: currentLineContent }); } } } - let reverseOperations: IReverseSingleEditOperation[] = []; - for (let i = 0; i < operations.length; i++) { - let op = operations[i]; - let reverseRange = reverseRanges[i]; + let reverseOperations: IReverseSingleEditOperation[] | null = null; + if (computeUndoEdits) { - reverseOperations[i] = { - sortIndex: op.sortIndex, - identifier: op.identifier, - range: reverseRange, - text: this.getValueInRange(op.range), - forceMoveMarkers: op.forceMoveMarkers - }; + let reverseRangeDeltaOffset = 0; + reverseOperations = []; + for (let i = 0; i < operations.length; i++) { + const op = operations[i]; + const reverseRange = reverseRanges[i]; + const bufferText = this.getValueInRange(op.range); + const reverseRangeOffset = op.rangeOffset + reverseRangeDeltaOffset; + reverseRangeDeltaOffset += (op.text.length - bufferText.length); + + reverseOperations[i] = { + sortIndex: op.sortIndex, + identifier: op.identifier, + range: reverseRange, + text: bufferText, + textChange: new TextChange(op.rangeOffset, bufferText, reverseRangeOffset, op.text) + }; + } + + // Can only sort reverse operations when the order is not significant + if (!hasTouchingRanges) { + reverseOperations.sort((a, b) => a.sortIndex - b.sortIndex); + } } - // Can only sort reverse operations when the order is not significant - if (!hasTouchingRanges) { - reverseOperations.sort((a, b) => a.sortIndex - b.sortIndex); - } this._mightContainRTL = mightContainRTL; this._mightContainNonBasicASCII = mightContainNonBasicASCII; @@ -350,58 +386,45 @@ export class PieceTreeTextBuffer implements ITextBuffer { } _toSingleEditOperation(operations: IValidatedEditOperation[]): IValidatedEditOperation { - let forceMoveMarkers = false, - firstEditRange = operations[0].range, - lastEditRange = operations[operations.length - 1].range, - entireEditRange = new Range(firstEditRange.startLineNumber, firstEditRange.startColumn, lastEditRange.endLineNumber, lastEditRange.endColumn), - lastEndLineNumber = firstEditRange.startLineNumber, - lastEndColumn = firstEditRange.startColumn, - result: string[] = []; + let forceMoveMarkers = false; + const firstEditRange = operations[0].range; + const lastEditRange = operations[operations.length - 1].range; + const entireEditRange = new Range(firstEditRange.startLineNumber, firstEditRange.startColumn, lastEditRange.endLineNumber, lastEditRange.endColumn); + let lastEndLineNumber = firstEditRange.startLineNumber; + let lastEndColumn = firstEditRange.startColumn; + const result: string[] = []; for (let i = 0, len = operations.length; i < len; i++) { - let operation = operations[i], - range = operation.range; + const operation = operations[i]; + const range = operation.range; forceMoveMarkers = forceMoveMarkers || operation.forceMoveMarkers; // (1) -- Push old text - for (let lineNumber = lastEndLineNumber; lineNumber < range.startLineNumber; lineNumber++) { - if (lineNumber === lastEndLineNumber) { - result.push(this.getLineContent(lineNumber).substring(lastEndColumn - 1)); - } else { - result.push('\n'); - result.push(this.getLineContent(lineNumber)); - } - } - - if (range.startLineNumber === lastEndLineNumber) { - result.push(this.getLineContent(range.startLineNumber).substring(lastEndColumn - 1, range.startColumn - 1)); - } else { - result.push('\n'); - result.push(this.getLineContent(range.startLineNumber).substring(0, range.startColumn - 1)); - } + result.push(this.getValueInRange(new Range(lastEndLineNumber, lastEndColumn, range.startLineNumber, range.startColumn))); // (2) -- Push new text - if (operation.lines) { - for (let j = 0, lenJ = operation.lines.length; j < lenJ; j++) { - if (j !== 0) { - result.push('\n'); - } - result.push(operation.lines[j]); - } + if (operation.text.length > 0) { + result.push(operation.text); } - lastEndLineNumber = operation.range.endLineNumber; - lastEndColumn = operation.range.endColumn; + lastEndLineNumber = range.endLineNumber; + lastEndColumn = range.endColumn; } + const text = result.join(''); + const [eolCount, firstLineLength, lastLineLength] = countEOL(text); + return { sortIndex: 0, identifier: operations[0].identifier, range: entireEditRange, rangeOffset: this.getOffsetAt(entireEditRange.startLineNumber, entireEditRange.startColumn), rangeLength: this.getValueLengthInRange(entireEditRange, EndOfLinePreference.TextDefined), - lines: result.join('').split('\n'), + text: text, + eolCount: eolCount, + firstLineLength: firstLineLength, + lastLineLength: lastLineLength, forceMoveMarkers: forceMoveMarkers, isAutoWhitespaceEdit: false }; @@ -421,41 +444,26 @@ export class PieceTreeTextBuffer implements ITextBuffer { const endLineNumber = op.range.endLineNumber; const endColumn = op.range.endColumn; - if (startLineNumber === endLineNumber && startColumn === endColumn && (!op.lines || op.lines.length === 0)) { + if (startLineNumber === endLineNumber && startColumn === endColumn && op.text.length === 0) { // no-op continue; } - const deletingLinesCnt = endLineNumber - startLineNumber; - const insertingLinesCnt = (op.lines ? op.lines.length - 1 : 0); - const editingLinesCnt = Math.min(deletingLinesCnt, insertingLinesCnt); - - const text = (op.lines ? op.lines.join(this.getEOL()) : ''); - - if (text) { + if (op.text) { // replacement this._pieceTree.delete(op.rangeOffset, op.rangeLength); - this._pieceTree.insert(op.rangeOffset, text, true); + this._pieceTree.insert(op.rangeOffset, op.text, true); } else { // deletion this._pieceTree.delete(op.rangeOffset, op.rangeLength); } - if (editingLinesCnt < insertingLinesCnt) { - let newLinesContent: string[] = []; - for (let j = editingLinesCnt + 1; j <= insertingLinesCnt; j++) { - newLinesContent.push(op.lines![j]); - } - - newLinesContent[newLinesContent.length - 1] = this.getLineContent(startLineNumber + insertingLinesCnt - 1); - } - const contentChangeRange = new Range(startLineNumber, startColumn, endLineNumber, endColumn); contentChanges.push({ range: contentChangeRange, rangeLength: op.rangeLength, - text: text, + text: op.text, rangeOffset: op.rangeOffset, forceMoveMarkers: op.forceMoveMarkers }); @@ -504,18 +512,16 @@ export class PieceTreeTextBuffer implements ITextBuffer { let resultRange: Range; - if (op.lines && op.lines.length > 0) { + if (op.text.length > 0) { // the operation inserts something - let lineCount = op.lines.length; - let firstLine = op.lines[0]; - let lastLine = op.lines[lineCount - 1]; + const lineCount = op.eolCount + 1; if (lineCount === 1) { // single line insert - resultRange = new Range(startLineNumber, startColumn, startLineNumber, startColumn + firstLine.length); + resultRange = new Range(startLineNumber, startColumn, startLineNumber, startColumn + op.firstLineLength); } else { // multi line insert - resultRange = new Range(startLineNumber, startColumn, startLineNumber + lineCount - 1, lastLine.length + 1); + resultRange = new Range(startLineNumber, startColumn, startLineNumber + lineCount - 1, op.lastLineLength + 1); } } else { // There is nothing to insert diff --git a/src/vs/editor/common/model/textChange.ts b/src/vs/editor/common/model/textChange.ts new file mode 100644 index 00000000000..5f4024cfbe6 --- /dev/null +++ b/src/vs/editor/common/model/textChange.ts @@ -0,0 +1,326 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as buffer from 'vs/base/common/buffer'; +import { decodeUTF16LE } from 'vs/editor/common/core/stringBuilder'; + +export class TextChange { + + public get oldLength(): number { + return this.oldText.length; + } + + public get oldEnd(): number { + return this.oldPosition + this.oldText.length; + } + + public get newLength(): number { + return this.newText.length; + } + + public get newEnd(): number { + return this.newPosition + this.newText.length; + } + + constructor( + public readonly oldPosition: number, + public readonly oldText: string, + public readonly newPosition: number, + public readonly newText: string + ) { } + + private static _writeStringSize(str: string): number { + return ( + 4 + 2 * str.length + ); + } + + private static _writeString(b: Uint8Array, str: string, offset: number): number { + const len = str.length; + buffer.writeUInt32BE(b, len, offset); offset += 4; + for (let i = 0; i < len; i++) { + buffer.writeUInt16LE(b, str.charCodeAt(i), offset); offset += 2; + } + return offset; + } + + private static _readString(b: Uint8Array, offset: number): string { + const len = buffer.readUInt32BE(b, offset); offset += 4; + return decodeUTF16LE(b, offset, len); + } + + public writeSize(): number { + return ( + + 4 // oldPosition + + 4 // newPosition + + TextChange._writeStringSize(this.oldText) + + TextChange._writeStringSize(this.newText) + ); + } + + public write(b: Uint8Array, offset: number): number { + buffer.writeUInt32BE(b, this.oldPosition, offset); offset += 4; + buffer.writeUInt32BE(b, this.newPosition, offset); offset += 4; + offset = TextChange._writeString(b, this.oldText, offset); + offset = TextChange._writeString(b, this.newText, offset); + return offset; + } + + public static read(b: Uint8Array, offset: number, dest: TextChange[]): number { + const oldPosition = buffer.readUInt32BE(b, offset); offset += 4; + const newPosition = buffer.readUInt32BE(b, offset); offset += 4; + const oldText = TextChange._readString(b, offset); offset += TextChange._writeStringSize(oldText); + const newText = TextChange._readString(b, offset); offset += TextChange._writeStringSize(newText); + dest.push(new TextChange(oldPosition, oldText, newPosition, newText)); + return offset; + } +} + +export function compressConsecutiveTextChanges(prevEdits: TextChange[] | null, currEdits: TextChange[]): TextChange[] { + if (prevEdits === null || prevEdits.length === 0) { + return currEdits; + } + const compressor = new TextChangeCompressor(prevEdits, currEdits); + return compressor.compress(); +} + +class TextChangeCompressor { + + private _prevEdits: TextChange[]; + private _currEdits: TextChange[]; + + private _result: TextChange[]; + private _resultLen: number; + + private _prevLen: number; + private _prevDeltaOffset: number; + + private _currLen: number; + private _currDeltaOffset: number; + + constructor(prevEdits: TextChange[], currEdits: TextChange[]) { + this._prevEdits = prevEdits; + this._currEdits = currEdits; + + this._result = []; + this._resultLen = 0; + + this._prevLen = this._prevEdits.length; + this._prevDeltaOffset = 0; + + this._currLen = this._currEdits.length; + this._currDeltaOffset = 0; + } + + public compress(): TextChange[] { + let prevIndex = 0; + let currIndex = 0; + + let prevEdit = this._getPrev(prevIndex); + let currEdit = this._getCurr(currIndex); + + while (prevIndex < this._prevLen || currIndex < this._currLen) { + + if (prevEdit === null) { + this._acceptCurr(currEdit!); + currEdit = this._getCurr(++currIndex); + continue; + } + + if (currEdit === null) { + this._acceptPrev(prevEdit); + prevEdit = this._getPrev(++prevIndex); + continue; + } + + if (currEdit.oldEnd <= prevEdit.newPosition) { + this._acceptCurr(currEdit); + currEdit = this._getCurr(++currIndex); + continue; + } + + if (prevEdit.newEnd <= currEdit.oldPosition) { + this._acceptPrev(prevEdit); + prevEdit = this._getPrev(++prevIndex); + continue; + } + + if (currEdit.oldPosition < prevEdit.newPosition) { + const [e1, e2] = TextChangeCompressor._splitCurr(currEdit, prevEdit.newPosition - currEdit.oldPosition); + this._acceptCurr(e1); + currEdit = e2; + continue; + } + + if (prevEdit.newPosition < currEdit.oldPosition) { + const [e1, e2] = TextChangeCompressor._splitPrev(prevEdit, currEdit.oldPosition - prevEdit.newPosition); + this._acceptPrev(e1); + prevEdit = e2; + continue; + } + + // At this point, currEdit.oldPosition === prevEdit.newPosition + + let mergePrev: TextChange; + let mergeCurr: TextChange; + + if (currEdit.oldEnd === prevEdit.newEnd) { + mergePrev = prevEdit; + mergeCurr = currEdit; + prevEdit = this._getPrev(++prevIndex); + currEdit = this._getCurr(++currIndex); + } else if (currEdit.oldEnd < prevEdit.newEnd) { + const [e1, e2] = TextChangeCompressor._splitPrev(prevEdit, currEdit.oldLength); + mergePrev = e1; + mergeCurr = currEdit; + prevEdit = e2; + currEdit = this._getCurr(++currIndex); + } else { + const [e1, e2] = TextChangeCompressor._splitCurr(currEdit, prevEdit.newLength); + mergePrev = prevEdit; + mergeCurr = e1; + prevEdit = this._getPrev(++prevIndex); + currEdit = e2; + } + + this._result[this._resultLen++] = new TextChange( + mergePrev.oldPosition, + mergePrev.oldText, + mergeCurr.newPosition, + mergeCurr.newText + ); + this._prevDeltaOffset += mergePrev.newLength - mergePrev.oldLength; + this._currDeltaOffset += mergeCurr.newLength - mergeCurr.oldLength; + } + + const merged = TextChangeCompressor._merge(this._result); + const cleaned = TextChangeCompressor._removeNoOps(merged); + return cleaned; + } + + private _acceptCurr(currEdit: TextChange): void { + this._result[this._resultLen++] = TextChangeCompressor._rebaseCurr(this._prevDeltaOffset, currEdit); + this._currDeltaOffset += currEdit.newLength - currEdit.oldLength; + } + + private _getCurr(currIndex: number): TextChange | null { + return (currIndex < this._currLen ? this._currEdits[currIndex] : null); + } + + private _acceptPrev(prevEdit: TextChange): void { + this._result[this._resultLen++] = TextChangeCompressor._rebasePrev(this._currDeltaOffset, prevEdit); + this._prevDeltaOffset += prevEdit.newLength - prevEdit.oldLength; + } + + private _getPrev(prevIndex: number): TextChange | null { + return (prevIndex < this._prevLen ? this._prevEdits[prevIndex] : null); + } + + private static _rebaseCurr(prevDeltaOffset: number, currEdit: TextChange): TextChange { + return new TextChange( + currEdit.oldPosition - prevDeltaOffset, + currEdit.oldText, + currEdit.newPosition, + currEdit.newText + ); + } + + private static _rebasePrev(currDeltaOffset: number, prevEdit: TextChange): TextChange { + return new TextChange( + prevEdit.oldPosition, + prevEdit.oldText, + prevEdit.newPosition + currDeltaOffset, + prevEdit.newText + ); + } + + private static _splitPrev(edit: TextChange, offset: number): [TextChange, TextChange] { + const preText = edit.newText.substr(0, offset); + const postText = edit.newText.substr(offset); + + return [ + new TextChange( + edit.oldPosition, + edit.oldText, + edit.newPosition, + preText + ), + new TextChange( + edit.oldEnd, + '', + edit.newPosition + offset, + postText + ) + ]; + } + + private static _splitCurr(edit: TextChange, offset: number): [TextChange, TextChange] { + const preText = edit.oldText.substr(0, offset); + const postText = edit.oldText.substr(offset); + + return [ + new TextChange( + edit.oldPosition, + preText, + edit.newPosition, + edit.newText + ), + new TextChange( + edit.oldPosition + offset, + postText, + edit.newEnd, + '' + ) + ]; + } + + private static _merge(edits: TextChange[]): TextChange[] { + if (edits.length === 0) { + return edits; + } + + let result: TextChange[] = [], resultLen = 0; + + let prev = edits[0]; + for (let i = 1; i < edits.length; i++) { + const curr = edits[i]; + + if (prev.oldEnd === curr.oldPosition) { + // Merge into `prev` + prev = new TextChange( + prev.oldPosition, + prev.oldText + curr.oldText, + prev.newPosition, + prev.newText + curr.newText + ); + } else { + result[resultLen++] = prev; + prev = curr; + } + } + result[resultLen++] = prev; + + return result; + } + + private static _removeNoOps(edits: TextChange[]): TextChange[] { + if (edits.length === 0) { + return edits; + } + + let result: TextChange[] = [], resultLen = 0; + + for (let i = 0; i < edits.length; i++) { + const edit = edits[i]; + + if (edit.oldText === edit.newText) { + continue; + } + result[resultLen++] = edit; + } + + return result; + } +} diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 89050a1e6de..81a7be60be8 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -37,6 +37,7 @@ import { Color } from 'vs/base/common/color'; import { Constants } from 'vs/base/common/uint'; import { EditorTheme } from 'vs/editor/common/view/viewContext'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; +import { TextChange } from 'vs/editor/common/model/textChange'; function createTextBufferBuilder() { return new PieceTreeTextBufferBuilder(); @@ -367,7 +368,6 @@ export class TextModel extends Disposable implements model.ITextModel { this._onWillDispose.fire(); this._languageRegistryListener.dispose(); this._tokenization.dispose(); - this._undoRedoService.removeElements(this.uri); this._isDisposed = true; super.dispose(); this._isDisposing = false; @@ -384,6 +384,11 @@ export class TextModel extends Disposable implements model.ITextModel { return this._buffer.equals(other); } + public getTextBuffer(): model.ITextBuffer { + this._assertNotDisposed(); + return this._buffer; + } + private _emitContentChangedEvent(rawChange: ModelRawContentChangedEvent, change: IModelContentChangedEvent): void { if (this._isDisposing) { // Do not confuse listeners by emitting any event after disposing @@ -706,7 +711,11 @@ export class TextModel extends Disposable implements model.ITextModel { this._alternativeVersionId = this._versionId; } - private _overwriteAlternativeVersionId(newAlternativeVersionId: number): void { + public _overwriteVersionId(versionId: number): void { + this._versionId = versionId; + } + + public _overwriteAlternativeVersionId(newAlternativeVersionId: number): void { this._alternativeVersionId = newAlternativeVersionId; } @@ -1280,19 +1289,39 @@ export class TextModel extends Disposable implements model.ITextModel { return this._commandManager.pushEditOperation(beforeCursorState, editOperations, cursorStateComputer); } - _applyUndoRedoEdits(edits: model.IValidEditOperations[], eol: model.EndOfLineSequence, isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): model.IValidEditOperations[] { + _applyUndo(changes: TextChange[], eol: model.EndOfLineSequence, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void { + const edits = changes.map((change) => { + const rangeStart = this.getPositionAt(change.newPosition); + const rangeEnd = this.getPositionAt(change.newEnd); + return { + range: new Range(rangeStart.lineNumber, rangeStart.column, rangeEnd.lineNumber, rangeEnd.column), + text: change.oldText + }; + }); + this._applyUndoRedoEdits(edits, eol, true, false, resultingAlternativeVersionId, resultingSelection); + } + + _applyRedo(changes: TextChange[], eol: model.EndOfLineSequence, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void { + const edits = changes.map((change) => { + const rangeStart = this.getPositionAt(change.oldPosition); + const rangeEnd = this.getPositionAt(change.oldEnd); + return { + range: new Range(rangeStart.lineNumber, rangeStart.column, rangeEnd.lineNumber, rangeEnd.column), + text: change.newText + }; + }); + this._applyUndoRedoEdits(edits, eol, false, true, resultingAlternativeVersionId, resultingSelection); + } + + private _applyUndoRedoEdits(edits: model.IIdentifiedSingleEditOperation[], eol: model.EndOfLineSequence, isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void { try { this._onDidChangeDecorations.beginDeferredEmit(); this._eventEmitter.beginDeferredEmit(); this._isUndoing = isUndoing; this._isRedoing = isRedoing; - let reverseEdits: model.IValidEditOperations[] = []; - for (let i = 0, len = edits.length; i < len; i++) { - reverseEdits[i] = { operations: this.applyEdits(edits[i].operations) }; - } + this.applyEdits(edits, false); this.setEOL(eol); this._overwriteAlternativeVersionId(resultingAlternativeVersionId); - return reverseEdits; } finally { this._isUndoing = false; this._isRedoing = false; @@ -1301,21 +1330,25 @@ export class TextModel extends Disposable implements model.ITextModel { } } - public applyEdits(rawOperations: model.IIdentifiedSingleEditOperation[]): model.IValidEditOperation[] { + public applyEdits(operations: model.IIdentifiedSingleEditOperation[]): void; + public applyEdits(operations: model.IIdentifiedSingleEditOperation[], computeUndoEdits: false): void; + public applyEdits(operations: model.IIdentifiedSingleEditOperation[], computeUndoEdits: true): model.IValidEditOperation[]; + public applyEdits(rawOperations: model.IIdentifiedSingleEditOperation[], computeUndoEdits: boolean = false): void | model.IValidEditOperation[] { try { this._onDidChangeDecorations.beginDeferredEmit(); this._eventEmitter.beginDeferredEmit(); - return this._doApplyEdits(this._validateEditOperations(rawOperations)); + const operations = this._validateEditOperations(rawOperations); + return this._doApplyEdits(operations, computeUndoEdits); } finally { this._eventEmitter.endDeferredEmit(); this._onDidChangeDecorations.endDeferredEmit(); } } - private _doApplyEdits(rawOperations: model.ValidAnnotatedEditOperation[]): model.IValidEditOperation[] { + private _doApplyEdits(rawOperations: model.ValidAnnotatedEditOperation[], computeUndoEdits: boolean): void | model.IValidEditOperation[] { const oldLineCount = this._buffer.getLineCount(); - const result = this._buffer.applyEdits(rawOperations, this._options.trimAutoWhitespace); + const result = this._buffer.applyEdits(rawOperations, this._options.trimAutoWhitespace, computeUndoEdits); const newLineCount = this._buffer.getLineCount(); const contentChanges = result.changes; @@ -1390,7 +1423,7 @@ export class TextModel extends Disposable implements model.ITextModel { ); } - return result.reverseEdits; + return (result.reverseEdits === null ? undefined : result.reverseEdits); } public undo(): void { @@ -1754,20 +1787,39 @@ export class TextModel extends Disposable implements model.ITextModel { if (ranges.length > 0) { this._emitModelTokensChangedEvent({ tokenizationSupportChanged: false, + semanticTokensApplied: false, ranges: ranges }); } } - public setSemanticTokens(tokens: MultilineTokens2[] | null): void { - this._tokens2.set(tokens); + public setSemanticTokens(tokens: MultilineTokens2[] | null, isComplete: boolean): void { + this._tokens2.set(tokens, isComplete); this._emitModelTokensChangedEvent({ tokenizationSupportChanged: false, + semanticTokensApplied: tokens !== null, ranges: [{ fromLineNumber: 1, toLineNumber: this.getLineCount() }] }); } + public hasSemanticTokens(): boolean { + return this._tokens2.isComplete(); + } + + public setPartialSemanticTokens(range: Range, tokens: MultilineTokens2[]): void { + if (this.hasSemanticTokens()) { + return; + } + const changedRange = this._tokens2.setPartial(range, tokens); + + this._emitModelTokensChangedEvent({ + tokenizationSupportChanged: false, + semanticTokensApplied: true, + ranges: [{ fromLineNumber: changedRange.startLineNumber, toLineNumber: changedRange.endLineNumber }] + }); + } + public tokenizeViewport(startLineNumber: number, endLineNumber: number): void { startLineNumber = Math.max(1, startLineNumber); endLineNumber = Math.min(this._buffer.getLineCount(), endLineNumber); @@ -1778,6 +1830,7 @@ export class TextModel extends Disposable implements model.ITextModel { this._tokens.flush(); this._emitModelTokensChangedEvent({ tokenizationSupportChanged: true, + semanticTokensApplied: false, ranges: [{ fromLineNumber: 1, toLineNumber: this._buffer.getLineCount() @@ -1790,6 +1843,7 @@ export class TextModel extends Disposable implements model.ITextModel { this._emitModelTokensChangedEvent({ tokenizationSupportChanged: false, + semanticTokensApplied: false, ranges: [{ fromLineNumber: 1, toLineNumber: this.getLineCount() }] }); } @@ -3053,6 +3107,7 @@ export class ModelDecorationOptions implements model.IModelDecorationOptions { readonly minimap: ModelDecorationMinimapOptions | null; readonly glyphMarginClassName: string | null; readonly linesDecorationsClassName: string | null; + readonly firstLineDecorationClassName: string | null; readonly marginClassName: string | null; readonly inlineClassName: string | null; readonly inlineClassNameAffectsLetterSpacing: boolean; @@ -3072,6 +3127,7 @@ export class ModelDecorationOptions implements model.IModelDecorationOptions { this.minimap = options.minimap ? new ModelDecorationMinimapOptions(options.minimap) : null; this.glyphMarginClassName = options.glyphMarginClassName ? cleanClassName(options.glyphMarginClassName) : null; this.linesDecorationsClassName = options.linesDecorationsClassName ? cleanClassName(options.linesDecorationsClassName) : null; + this.firstLineDecorationClassName = options.firstLineDecorationClassName ? cleanClassName(options.firstLineDecorationClassName) : null; this.marginClassName = options.marginClassName ? cleanClassName(options.marginClassName) : null; this.inlineClassName = options.inlineClassName ? cleanClassName(options.inlineClassName) : null; this.inlineClassNameAffectsLetterSpacing = options.inlineClassNameAffectsLetterSpacing || false; diff --git a/src/vs/editor/common/model/textModelEvents.ts b/src/vs/editor/common/model/textModelEvents.ts index 17f45db65ad..6511d02173f 100644 --- a/src/vs/editor/common/model/textModelEvents.ts +++ b/src/vs/editor/common/model/textModelEvents.ts @@ -87,6 +87,7 @@ export interface IModelDecorationsChangedEvent { */ export interface IModelTokensChangedEvent { readonly tokenizationSupportChanged: boolean; + readonly semanticTokensApplied: boolean; readonly ranges: { /** * The start of the range (inclusive) diff --git a/src/vs/editor/common/model/textModelSearch.ts b/src/vs/editor/common/model/textModelSearch.ts index d41d1a2a1ba..ce5b545c6f9 100644 --- a/src/vs/editor/common/model/textModelSearch.ts +++ b/src/vs/editor/common/model/textModelSearch.ts @@ -515,7 +515,7 @@ export class Searcher { private _prevMatchStartIndex: number; private _prevMatchLength: number; - constructor(wordSeparators: WordCharacterClassifier | null, searchRegex: RegExp, ) { + constructor(wordSeparators: WordCharacterClassifier | null, searchRegex: RegExp,) { this._wordSeparators = wordSeparators; this._searchRegex = searchRegex; this._prevMatchStartIndex = -1; diff --git a/src/vs/editor/common/model/tokensStore.ts b/src/vs/editor/common/model/tokensStore.ts index c662f820c9c..45977539fbb 100644 --- a/src/vs/editor/common/model/tokensStore.ts +++ b/src/vs/editor/common/model/tokensStore.ts @@ -6,15 +6,23 @@ import * as arrays from 'vs/base/common/arrays'; import { LineTokens } from 'vs/editor/common/core/lineTokens'; import { Position } from 'vs/editor/common/core/position'; -import { IRange } from 'vs/editor/common/core/range'; +import { IRange, Range } from 'vs/editor/common/core/range'; import { ColorId, FontStyle, LanguageId, MetadataConsts, StandardTokenType, TokenMetadata } from 'vs/editor/common/modes'; import { writeUInt32BE, readUInt32BE } from 'vs/base/common/buffer'; import { CharCode } from 'vs/base/common/charCode'; -export function countEOL(text: string): [number, number, number] { +export const enum StringEOL { + Unknown = 0, + Invalid = 3, + LF = 1, + CRLF = 2 +} + +export function countEOL(text: string): [number, number, number, StringEOL] { let eolCount = 0; let firstLineLength = 0; let lastLineStart = 0; + let eol: StringEOL = StringEOL.Unknown; for (let i = 0, len = text.length; i < len; i++) { const chr = text.charCodeAt(i); @@ -25,12 +33,16 @@ export function countEOL(text: string): [number, number, number] { eolCount++; if (i + 1 < len && text.charCodeAt(i + 1) === CharCode.LineFeed) { // \r\n... case + eol |= StringEOL.CRLF; i++; // skip \n } else { // \r... case + eol |= StringEOL.Invalid; } lastLineStart = i + 1; } else if (chr === CharCode.LineFeed) { + // \n... case + eol |= StringEOL.LF; if (eolCount === 0) { firstLineLength = i; } @@ -41,7 +53,7 @@ export function countEOL(text: string): [number, number, number] { if (eolCount === 0) { firstLineLength = text.length; } - return [eolCount, firstLineLength, text.length - lastLineStart]; + return [eolCount, firstLineLength, text.length - lastLineStart, eol]; } function getDefaultMetadata(topLevelLanguageId: LanguageId): number { @@ -112,20 +124,7 @@ export class MultilineTokensBuilder { } } -export interface IEncodedTokens { - getTokenCount(): number; - getDeltaLine(tokenIndex: number): number; - getMaxDeltaLine(): number; - getStartCharacter(tokenIndex: number): number; - getEndCharacter(tokenIndex: number): number; - getMetadata(tokenIndex: number): number; - - clear(): void; - acceptDeleteRange(horizontalShiftForFirstLineTokens: number, startDeltaLine: number, startCharacter: number, endDeltaLine: number, endCharacter: number): void; - acceptInsertText(deltaLine: number, character: number, eolCount: number, firstLineLength: number, lastLineLength: number, firstCharCode: number): void; -} - -export class SparseEncodedTokens implements IEncodedTokens { +export class SparseEncodedTokens { /** * The encoding of tokens is: * 4*i deltaLine (from `startLineNumber`) @@ -133,7 +132,7 @@ export class SparseEncodedTokens implements IEncodedTokens { * 4*i+2 endCharacter (from the line start) * 4*i+3 metadata */ - private _tokens: Uint32Array; + private readonly _tokens: Uint32Array; private _tokenCount: number; constructor(tokens: Uint32Array) { @@ -141,38 +140,167 @@ export class SparseEncodedTokens implements IEncodedTokens { this._tokenCount = tokens.length / 4; } + public toString(startLineNumber: number): string { + let pieces: string[] = []; + for (let i = 0; i < this._tokenCount; i++) { + pieces.push(`(${this._getDeltaLine(i) + startLineNumber},${this._getStartCharacter(i)}-${this._getEndCharacter(i)})`); + } + return `[${pieces.join(',')}]`; + } + public getMaxDeltaLine(): number { - const tokenCount = this.getTokenCount(); + const tokenCount = this._getTokenCount(); if (tokenCount === 0) { return -1; } - return this.getDeltaLine(tokenCount - 1); + return this._getDeltaLine(tokenCount - 1); } - public getTokenCount(): number { + public getRange(): Range | null { + const tokenCount = this._getTokenCount(); + if (tokenCount === 0) { + return null; + } + const startChar = this._getStartCharacter(0); + const maxDeltaLine = this._getDeltaLine(tokenCount - 1); + const endChar = this._getEndCharacter(tokenCount - 1); + return new Range(0, startChar + 1, maxDeltaLine, endChar + 1); + } + + private _getTokenCount(): number { return this._tokenCount; } - public getDeltaLine(tokenIndex: number): number { + private _getDeltaLine(tokenIndex: number): number { return this._tokens[4 * tokenIndex]; } - public getStartCharacter(tokenIndex: number): number { + private _getStartCharacter(tokenIndex: number): number { return this._tokens[4 * tokenIndex + 1]; } - public getEndCharacter(tokenIndex: number): number { + private _getEndCharacter(tokenIndex: number): number { return this._tokens[4 * tokenIndex + 2]; } - public getMetadata(tokenIndex: number): number { - return this._tokens[4 * tokenIndex + 3]; + public isEmpty(): boolean { + return (this._getTokenCount() === 0); + } + + public getLineTokens(deltaLine: number): LineTokens2 | null { + let low = 0; + let high = this._getTokenCount() - 1; + + while (low < high) { + const mid = low + Math.floor((high - low) / 2); + const midDeltaLine = this._getDeltaLine(mid); + + if (midDeltaLine < deltaLine) { + low = mid + 1; + } else if (midDeltaLine > deltaLine) { + high = mid - 1; + } else { + let min = mid; + while (min > low && this._getDeltaLine(min - 1) === deltaLine) { + min--; + } + let max = mid; + while (max < high && this._getDeltaLine(max + 1) === deltaLine) { + max++; + } + return new LineTokens2(this._tokens.subarray(4 * min, 4 * max + 4)); + } + } + + if (this._getDeltaLine(low) === deltaLine) { + return new LineTokens2(this._tokens.subarray(4 * low, 4 * low + 4)); + } + + return null; } public clear(): void { this._tokenCount = 0; } + public removeTokens(startDeltaLine: number, startChar: number, endDeltaLine: number, endChar: number): number { + const tokens = this._tokens; + const tokenCount = this._tokenCount; + let newTokenCount = 0; + let hasDeletedTokens = false; + let firstDeltaLine = 0; + for (let i = 0; i < tokenCount; i++) { + const srcOffset = 4 * i; + const tokenDeltaLine = tokens[srcOffset]; + const tokenStartCharacter = tokens[srcOffset + 1]; + const tokenEndCharacter = tokens[srcOffset + 2]; + const tokenMetadata = tokens[srcOffset + 3]; + + if ( + (tokenDeltaLine > startDeltaLine || (tokenDeltaLine === startDeltaLine && tokenEndCharacter >= startChar)) + && (tokenDeltaLine < endDeltaLine || (tokenDeltaLine === endDeltaLine && tokenStartCharacter <= endChar)) + ) { + hasDeletedTokens = true; + } else { + if (newTokenCount === 0) { + firstDeltaLine = tokenDeltaLine; + } + if (hasDeletedTokens) { + // must move the token to the left + const destOffset = 4 * newTokenCount; + tokens[destOffset] = tokenDeltaLine - firstDeltaLine; + tokens[destOffset + 1] = tokenStartCharacter; + tokens[destOffset + 2] = tokenEndCharacter; + tokens[destOffset + 3] = tokenMetadata; + } + newTokenCount++; + } + } + + this._tokenCount = newTokenCount; + + return firstDeltaLine; + } + + public split(startDeltaLine: number, startChar: number, endDeltaLine: number, endChar: number): [SparseEncodedTokens, SparseEncodedTokens, number] { + const tokens = this._tokens; + const tokenCount = this._tokenCount; + let aTokens: number[] = []; + let bTokens: number[] = []; + let destTokens: number[] = aTokens; + let destOffset = 0; + let destFirstDeltaLine: number = 0; + for (let i = 0; i < tokenCount; i++) { + const srcOffset = 4 * i; + const tokenDeltaLine = tokens[srcOffset]; + const tokenStartCharacter = tokens[srcOffset + 1]; + const tokenEndCharacter = tokens[srcOffset + 2]; + const tokenMetadata = tokens[srcOffset + 3]; + + if ((tokenDeltaLine > startDeltaLine || (tokenDeltaLine === startDeltaLine && tokenEndCharacter >= startChar))) { + if ((tokenDeltaLine < endDeltaLine || (tokenDeltaLine === endDeltaLine && tokenStartCharacter <= endChar))) { + // this token is touching the range + continue; + } else { + // this token is after the range + if (destTokens !== bTokens) { + // this token is the first token after the range + destTokens = bTokens; + destOffset = 0; + destFirstDeltaLine = tokenDeltaLine; + } + } + } + + destTokens[destOffset++] = tokenDeltaLine - destFirstDeltaLine; + destTokens[destOffset++] = tokenStartCharacter; + destTokens[destOffset++] = tokenEndCharacter; + destTokens[destOffset++] = tokenMetadata; + } + + return [new SparseEncodedTokens(new Uint32Array(aTokens)), new SparseEncodedTokens(new Uint32Array(bTokens)), destFirstDeltaLine]; + } + public acceptDeleteRange(horizontalShiftForFirstLineTokens: number, startDeltaLine: number, startCharacter: number, endDeltaLine: number, endCharacter: number): void { // This is a bit complex, here are the cases I used to think about this: // @@ -402,30 +530,26 @@ export class SparseEncodedTokens implements IEncodedTokens { export class LineTokens2 { - private readonly _actual: IEncodedTokens; - private readonly _startTokenIndex: number; - private readonly _endTokenIndex: number; + private readonly _tokens: Uint32Array; - constructor(actual: IEncodedTokens, startTokenIndex: number, endTokenIndex: number) { - this._actual = actual; - this._startTokenIndex = startTokenIndex; - this._endTokenIndex = endTokenIndex; + constructor(tokens: Uint32Array) { + this._tokens = tokens; } public getCount(): number { - return this._endTokenIndex - this._startTokenIndex + 1; + return this._tokens.length / 4; } public getStartCharacter(tokenIndex: number): number { - return this._actual.getStartCharacter(this._startTokenIndex + tokenIndex); + return this._tokens[4 * tokenIndex + 1]; } public getEndCharacter(tokenIndex: number): number { - return this._actual.getEndCharacter(this._startTokenIndex + tokenIndex); + return this._tokens[4 * tokenIndex + 2]; } public getMetadata(tokenIndex: number): number { - return this._actual.getMetadata(this._startTokenIndex + tokenIndex); + return this._tokens[4 * tokenIndex + 3]; } } @@ -433,59 +557,58 @@ export class MultilineTokens2 { public startLineNumber: number; public endLineNumber: number; - public tokens: IEncodedTokens; + public tokens: SparseEncodedTokens; - constructor(startLineNumber: number, tokens: IEncodedTokens) { + constructor(startLineNumber: number, tokens: SparseEncodedTokens) { this.startLineNumber = startLineNumber; this.tokens = tokens; this.endLineNumber = this.startLineNumber + this.tokens.getMaxDeltaLine(); } + public toString(): string { + return this.tokens.toString(this.startLineNumber); + } + private _updateEndLineNumber(): void { this.endLineNumber = this.startLineNumber + this.tokens.getMaxDeltaLine(); } + public isEmpty(): boolean { + return this.tokens.isEmpty(); + } + public getLineTokens(lineNumber: number): LineTokens2 | null { if (this.startLineNumber <= lineNumber && lineNumber <= this.endLineNumber) { - const findResult = MultilineTokens2._findTokensWithLine(this.tokens, lineNumber - this.startLineNumber); - if (findResult) { - const [startTokenIndex, endTokenIndex] = findResult; - return new LineTokens2(this.tokens, startTokenIndex, endTokenIndex); - } + return this.tokens.getLineTokens(lineNumber - this.startLineNumber); } return null; } - private static _findTokensWithLine(tokens: IEncodedTokens, deltaLine: number): [number, number] | null { - let low = 0; - let high = tokens.getTokenCount() - 1; - - while (low < high) { - const mid = low + Math.floor((high - low) / 2); - const midDeltaLine = tokens.getDeltaLine(mid); - - if (midDeltaLine < deltaLine) { - low = mid + 1; - } else if (midDeltaLine > deltaLine) { - high = mid - 1; - } else { - let min = mid; - while (min > low && tokens.getDeltaLine(min - 1) === deltaLine) { - min--; - } - let max = mid; - while (max < high && tokens.getDeltaLine(max + 1) === deltaLine) { - max++; - } - return [min, max]; - } + public getRange(): Range | null { + const deltaRange = this.tokens.getRange(); + if (!deltaRange) { + return deltaRange; } + return new Range(this.startLineNumber + deltaRange.startLineNumber, deltaRange.startColumn, this.startLineNumber + deltaRange.endLineNumber, deltaRange.endColumn); + } - if (tokens.getDeltaLine(low) === deltaLine) { - return [low, low]; - } + public removeTokens(range: Range): void { + const startLineIndex = range.startLineNumber - this.startLineNumber; + const endLineIndex = range.endLineNumber - this.startLineNumber; - return null; + this.startLineNumber += this.tokens.removeTokens(startLineIndex, range.startColumn - 1, endLineIndex, range.endColumn - 1); + this._updateEndLineNumber(); + } + + public split(range: Range): [MultilineTokens2, MultilineTokens2] { + // split tokens to two: + // a) all the tokens before `range` + // b) all the tokens after `range` + const startLineIndex = range.startLineNumber - this.startLineNumber; + const endLineIndex = range.endLineNumber - this.startLineNumber; + + const [a, b, bDeltaLine] = this.tokens.split(startLineIndex, range.startColumn - 1, endLineIndex, range.endColumn - 1); + return [new MultilineTokens2(this.startLineNumber, a), new MultilineTokens2(this.startLineNumber + bDeltaLine, b)]; } public applyEdit(range: IRange, text: string): void { @@ -749,17 +872,105 @@ function toUint32Array(arr: Uint32Array | ArrayBuffer): Uint32Array { export class TokensStore2 { private _pieces: MultilineTokens2[]; + private _isComplete: boolean; constructor() { this._pieces = []; + this._isComplete = false; } public flush(): void { this._pieces = []; + this._isComplete = false; } - public set(pieces: MultilineTokens2[] | null) { + public set(pieces: MultilineTokens2[] | null, isComplete: boolean): void { this._pieces = pieces || []; + this._isComplete = isComplete; + } + + public setPartial(_range: Range, pieces: MultilineTokens2[]): Range { + // console.log(`setPartial ${_range} ${pieces.map(p => p.toString()).join(', ')}`); + + let range = _range; + if (pieces.length > 0) { + const _firstRange = pieces[0].getRange(); + const _lastRange = pieces[pieces.length - 1].getRange(); + if (!_firstRange || !_lastRange) { + return _range; + } + range = _range.plusRange(_firstRange).plusRange(_lastRange); + } + + let insertPosition: { index: number; } | null = null; + for (let i = 0, len = this._pieces.length; i < len; i++) { + const piece = this._pieces[i]; + if (piece.endLineNumber < range.startLineNumber) { + // this piece is before the range + continue; + } + + if (piece.startLineNumber > range.endLineNumber) { + // this piece is after the range, so mark the spot before this piece + // as a good insertion position and stop looping + insertPosition = insertPosition || { index: i }; + break; + } + + // this piece might intersect with the range + piece.removeTokens(range); + + if (piece.isEmpty()) { + // remove the piece if it became empty + this._pieces.splice(i, 1); + i--; + len--; + continue; + } + + if (piece.endLineNumber < range.startLineNumber) { + // after removal, this piece is before the range + continue; + } + + if (piece.startLineNumber > range.endLineNumber) { + // after removal, this piece is after the range + insertPosition = insertPosition || { index: i }; + continue; + } + + // after removal, this piece contains the range + const [a, b] = piece.split(range); + if (a.isEmpty()) { + // this piece is actually after the range + insertPosition = insertPosition || { index: i }; + continue; + } + if (b.isEmpty()) { + // this piece is actually before the range + continue; + } + this._pieces.splice(i, 1, a, b); + i++; + len++; + + insertPosition = insertPosition || { index: i }; + } + + insertPosition = insertPosition || { index: this._pieces.length }; + + if (pieces.length > 0) { + this._pieces = arrays.arrayInsert(this._pieces, insertPosition.index, pieces); + } + + // console.log(`I HAVE ${this._pieces.length} pieces`); + // console.log(`${this._pieces.map(p => p.toString()).join('\n')}`); + + return range; + } + + public isComplete(): boolean { + return this._isComplete; } public addSemanticTokens(lineNumber: number, aTokens: LineTokens): LineTokens { @@ -770,7 +981,7 @@ export class TokensStore2 { } const pieceIndex = TokensStore2._findFirstPieceWithLine(pieces, lineNumber); - const bTokens = this._pieces[pieceIndex].getLineTokens(lineNumber); + const bTokens = pieces[pieceIndex].getLineTokens(lineNumber); if (!bTokens) { return aTokens; @@ -781,6 +992,17 @@ export class TokensStore2 { let aIndex = 0; let result: number[] = [], resultLen = 0; + let lastEndOffset = 0; + + const emitToken = (endOffset: number, metadata: number) => { + if (endOffset === lastEndOffset) { + return; + } + lastEndOffset = endOffset; + result[resultLen++] = endOffset; + result[resultLen++] = metadata; + }; + for (let bIndex = 0; bIndex < bLen; bIndex++) { const bStartCharacter = bTokens.getStartCharacter(bIndex); const bEndCharacter = bTokens.getEndCharacter(bIndex); @@ -797,42 +1019,36 @@ export class TokensStore2 { // push any token from `a` that is before `b` while (aIndex < aLen && aTokens.getEndOffset(aIndex) <= bStartCharacter) { - result[resultLen++] = aTokens.getEndOffset(aIndex); - result[resultLen++] = aTokens.getMetadata(aIndex); + emitToken(aTokens.getEndOffset(aIndex), aTokens.getMetadata(aIndex)); aIndex++; } // push the token from `a` if it intersects the token from `b` if (aIndex < aLen && aTokens.getStartOffset(aIndex) < bStartCharacter) { - result[resultLen++] = bStartCharacter; - result[resultLen++] = aTokens.getMetadata(aIndex); + emitToken(bStartCharacter, aTokens.getMetadata(aIndex)); } // skip any tokens from `a` that are contained inside `b` while (aIndex < aLen && aTokens.getEndOffset(aIndex) < bEndCharacter) { - result[resultLen++] = aTokens.getEndOffset(aIndex); - result[resultLen++] = (aTokens.getMetadata(aIndex) & aMask) | (bMetadata & bMask); + emitToken(aTokens.getEndOffset(aIndex), (aTokens.getMetadata(aIndex) & aMask) | (bMetadata & bMask)); aIndex++; } if (aIndex < aLen && aTokens.getEndOffset(aIndex) === bEndCharacter) { // `a` ends exactly at the same spot as `b`! - result[resultLen++] = aTokens.getEndOffset(aIndex); - result[resultLen++] = (aTokens.getMetadata(aIndex) & aMask) | (bMetadata & bMask); + emitToken(aTokens.getEndOffset(aIndex), (aTokens.getMetadata(aIndex) & aMask) | (bMetadata & bMask)); aIndex++; } else { const aMergeIndex = Math.min(Math.max(0, aIndex - 1), aLen - 1); // push the token from `b` - result[resultLen++] = bEndCharacter; - result[resultLen++] = (aTokens.getMetadata(aMergeIndex) & aMask) | (bMetadata & bMask); + emitToken(bEndCharacter, (aTokens.getMetadata(aMergeIndex) & aMask) | (bMetadata & bMask)); } } // push the remaining tokens from `a` while (aIndex < aLen) { - result[resultLen++] = aTokens.getEndOffset(aIndex); - result[resultLen++] = aTokens.getMetadata(aIndex); + emitToken(aTokens.getEndOffset(aIndex), aTokens.getMetadata(aIndex)); aIndex++; } diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index b93301c609d..e767a6f3d1d 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -319,6 +319,8 @@ export const enum CompletionItemKind { Customcolor, Folder, TypeParameter, + User, + Issue, Snippet, // <- highest value (used for compare!) } @@ -327,32 +329,34 @@ export const enum CompletionItemKind { */ export const completionKindToCssClass = (function () { let data = Object.create(null); - data[CompletionItemKind.Method] = 'method'; - data[CompletionItemKind.Function] = 'function'; - data[CompletionItemKind.Constructor] = 'constructor'; - data[CompletionItemKind.Field] = 'field'; - data[CompletionItemKind.Variable] = 'variable'; - data[CompletionItemKind.Class] = 'class'; - data[CompletionItemKind.Struct] = 'struct'; - data[CompletionItemKind.Interface] = 'interface'; - data[CompletionItemKind.Module] = 'module'; - data[CompletionItemKind.Property] = 'property'; - data[CompletionItemKind.Event] = 'event'; - data[CompletionItemKind.Operator] = 'operator'; - data[CompletionItemKind.Unit] = 'unit'; - data[CompletionItemKind.Value] = 'value'; - data[CompletionItemKind.Constant] = 'constant'; - data[CompletionItemKind.Enum] = 'enum'; - data[CompletionItemKind.EnumMember] = 'enum-member'; - data[CompletionItemKind.Keyword] = 'keyword'; - data[CompletionItemKind.Snippet] = 'snippet'; - data[CompletionItemKind.Text] = 'text'; - data[CompletionItemKind.Color] = 'color'; - data[CompletionItemKind.File] = 'file'; - data[CompletionItemKind.Reference] = 'reference'; - data[CompletionItemKind.Customcolor] = 'customcolor'; - data[CompletionItemKind.Folder] = 'folder'; - data[CompletionItemKind.TypeParameter] = 'type-parameter'; + data[CompletionItemKind.Method] = 'symbol-method'; + data[CompletionItemKind.Function] = 'symbol-function'; + data[CompletionItemKind.Constructor] = 'symbol-constructor'; + data[CompletionItemKind.Field] = 'symbol-field'; + data[CompletionItemKind.Variable] = 'symbol-variable'; + data[CompletionItemKind.Class] = 'symbol-class'; + data[CompletionItemKind.Struct] = 'symbol-struct'; + data[CompletionItemKind.Interface] = 'symbol-interface'; + data[CompletionItemKind.Module] = 'symbol-module'; + data[CompletionItemKind.Property] = 'symbol-property'; + data[CompletionItemKind.Event] = 'symbol-event'; + data[CompletionItemKind.Operator] = 'symbol-operator'; + data[CompletionItemKind.Unit] = 'symbol-unit'; + data[CompletionItemKind.Value] = 'symbol-value'; + data[CompletionItemKind.Constant] = 'symbol-constant'; + data[CompletionItemKind.Enum] = 'symbol-enum'; + data[CompletionItemKind.EnumMember] = 'symbol-enum-member'; + data[CompletionItemKind.Keyword] = 'symbol-keyword'; + data[CompletionItemKind.Snippet] = 'symbol-snippet'; + data[CompletionItemKind.Text] = 'symbol-text'; + data[CompletionItemKind.Color] = 'symbol-color'; + data[CompletionItemKind.File] = 'symbol-file'; + data[CompletionItemKind.Reference] = 'symbol-reference'; + data[CompletionItemKind.Customcolor] = 'symbol-customcolor'; + data[CompletionItemKind.Folder] = 'symbol-folder'; + data[CompletionItemKind.TypeParameter] = 'symbol-type-parameter'; + data[CompletionItemKind.User] = 'account'; + data[CompletionItemKind.Issue] = 'issues'; return function (kind: CompletionItemKind) { return data[kind] || 'property'; @@ -395,7 +399,8 @@ export let completionKindFromString: { data['folder'] = CompletionItemKind.Folder; data['type-parameter'] = CompletionItemKind.TypeParameter; data['typeParameter'] = CompletionItemKind.TypeParameter; - + data['account'] = CompletionItemKind.User; + data['issue'] = CompletionItemKind.Issue; return function (value: string, strict?: true) { let res = data[value]; if (typeof res === 'undefined' && !strict) { @@ -412,9 +417,9 @@ export interface CompletionItemLabel { name: string; /** - * The signature without the return type. Render after `name`. + * The parameters without the return type. Render after `name`. */ - signature?: string; + parameters?: string; /** * The fully qualified name, like package name or file path. Rendered after `signature`. @@ -789,6 +794,20 @@ export interface DocumentHighlightProvider { provideDocumentHighlights(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult; } +/** + * The rename provider interface defines the contract between extensions and + * the live-rename feature. + */ +export interface OnTypeRenameProvider { + + stopPattern?: RegExp; + + /** + * Provide a list of ranges that can be live-renamed together. + */ + provideOnTypeRenameRanges(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult; +} + /** * Value-object that contains additional information when * requesting references. @@ -1246,11 +1265,11 @@ export interface SelectionRangeProvider { export interface FoldingContext { } /** - * A provider of colors for editor models. + * A provider of folding ranges for editor models. */ export interface FoldingRangeProvider { /** - * Provides the color ranges for a specific model. + * Provides the folding ranges for a specific model. */ provideFoldingRanges(model: model.ITextModel, context: FoldingContext, token: CancellationToken): ProviderResult; } @@ -1377,6 +1396,15 @@ export interface AuthenticationSession { accountName: string; } +/** + * @internal + */ +export interface AuthenticationSessionsChangeEvent { + added: string[]; + removed: string[]; + changed: string[]; +} + export interface Command { id: string; title: string; @@ -1589,6 +1617,7 @@ export interface SemanticTokensEdits { } export interface DocumentSemanticTokensProvider { + onDidChange?: Event; getLegend(): SemanticTokensLegend; provideDocumentSemanticTokens(model: model.ITextModel, lastResultId: string | null, token: CancellationToken): ProviderResult; releaseDocumentSemanticTokens(resultId: string | undefined): void; @@ -1641,6 +1670,11 @@ export const DocumentSymbolProviderRegistry = new LanguageFeatureRegistry(); +/** + * @internal + */ +export const OnTypeRenameProviderRegistry = new LanguageFeatureRegistry(); + /** * @internal */ diff --git a/src/vs/editor/common/modes/supports/richEditBrackets.ts b/src/vs/editor/common/modes/supports/richEditBrackets.ts index ae10537c82e..9325b4814a0 100644 --- a/src/vs/editor/common/modes/supports/richEditBrackets.ts +++ b/src/vs/editor/common/modes/supports/richEditBrackets.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as strings from 'vs/base/common/strings'; +import * as stringBuilder from 'vs/editor/common/core/stringBuilder'; import { Range } from 'vs/editor/common/core/range'; import { LanguageIdentifier } from 'vs/editor/common/modes'; import { CharacterPair } from 'vs/editor/common/modes/languageConfiguration'; @@ -264,14 +265,24 @@ function createBracketOrRegExp(pieces: string[]): RegExp { return strings.createRegExp(regexStr, true); } -let toReversedString = (function () { +const toReversedString = (function () { function reverse(str: string): string { - let reversedStr = ''; - for (let i = str.length - 1; i >= 0; i--) { - reversedStr += str.charAt(i); + if (stringBuilder.hasTextDecoder) { + // create a Uint16Array and then use a TextDecoder to create a string + const arr = new Uint16Array(str.length); + let offset = 0; + for (let i = str.length - 1; i >= 0; i--) { + arr[offset++] = str.charCodeAt(i); + } + return stringBuilder.getPlatformTextDecoder().decode(arr); + } else { + let result: string[] = [], resultLen = 0; + for (let i = str.length - 1; i >= 0; i--) { + result[resultLen++] = str.charAt(i); + } + return result.join(''); } - return reversedStr; } let lastInput: string | null = null; diff --git a/src/vs/editor/common/services/editorSimpleWorker.ts b/src/vs/editor/common/services/editorSimpleWorker.ts index 3e5361ce764..5a8391e91af 100644 --- a/src/vs/editor/common/services/editorSimpleWorker.ts +++ b/src/vs/editor/common/services/editorSimpleWorker.ts @@ -5,7 +5,6 @@ import { mergeSort } from 'vs/base/common/arrays'; import { stringDiff } from 'vs/base/common/diff/diff'; -import { FIN, Iterator, IteratorResult } from 'vs/base/common/iterator'; import { IDisposable } from 'vs/base/common/lifecycle'; import { globals } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; @@ -65,7 +64,7 @@ export interface ICommonModel extends ILinkComputerTarget, IMirrorModel { getLineCount(): number; getLineContent(lineNumber: number): string; getLineWords(lineNumber: number, wordDefinition: RegExp): IWordAtPosition[]; - createWordIterator(wordDefinition: RegExp): Iterator; + words(wordDefinition: RegExp): Iterable; getWordUntilPosition(position: IPosition, wordDefinition: RegExp): IWordAtPosition; getValueInRange(range: IRange): string; getWordAtPosition(position: IPosition, wordDefinition: RegExp): Range | null; @@ -153,36 +152,37 @@ class MirrorModel extends BaseMirrorModel implements ICommonModel { }; } - public createWordIterator(wordDefinition: RegExp): Iterator { - let obj: { done: false; value: string; }; + + public words(wordDefinition: RegExp): Iterable { + + const lines = this._lines; + const wordenize = this._wordenize.bind(this); + let lineNumber = 0; - let lineText: string; + let lineText = ''; let wordRangesIdx = 0; let wordRanges: IWordRange[] = []; - let next = (): IteratorResult => { - if (wordRangesIdx < wordRanges.length) { - const value = lineText.substring(wordRanges[wordRangesIdx].start, wordRanges[wordRangesIdx].end); - wordRangesIdx += 1; - if (!obj) { - obj = { done: false, value: value }; - } else { - obj.value = value; + return { + *[Symbol.iterator]() { + while (true) { + if (wordRangesIdx < wordRanges.length) { + const value = lineText.substring(wordRanges[wordRangesIdx].start, wordRanges[wordRangesIdx].end); + wordRangesIdx += 1; + yield value; + } else { + if (lineNumber < lines.length) { + lineText = lines[lineNumber]; + wordRanges = wordenize(lineText, wordDefinition); + wordRangesIdx = 0; + lineNumber += 1; + } else { + break; + } + } } - return obj; - - } else if (lineNumber >= this._lines.length) { - return FIN; - - } else { - lineText = this._lines[lineNumber]; - wordRanges = this._wordenize(lineText, wordDefinition); - wordRangesIdx = 0; - lineNumber += 1; - return next(); } }; - return { next }; } public getLineWords(lineNumber: number, wordDefinition: RegExp): IWordAtPosition[] { @@ -545,12 +545,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { seen.add(model.getValueInRange(wordAt)); } - for ( - let iter = model.createWordIterator(wordDefRegExp), e = iter.next(); - !e.done && seen.size <= EditorSimpleWorker._suggestionsLimit; - e = iter.next() - ) { - const word = e.value; + for (let word of model.words(wordDefRegExp)) { if (seen.has(word)) { continue; } @@ -559,6 +554,9 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { continue; } words.push(word); + if (seen.size > EditorSimpleWorker._suggestionsLimit) { + break; + } } return words; } diff --git a/src/vs/editor/common/services/modeService.ts b/src/vs/editor/common/services/modeService.ts index a6d2a6bc9e2..678b15836af 100644 --- a/src/vs/editor/common/services/modeService.ts +++ b/src/vs/editor/common/services/modeService.ts @@ -31,6 +31,7 @@ export interface IModeService { _serviceBrand: undefined; onDidCreateMode: Event; + onLanguagesMaybeChanged: Event; // --- reading isRegisteredMode(mimetypeOrModeId: string): boolean; diff --git a/src/vs/editor/common/services/modeServiceImpl.ts b/src/vs/editor/common/services/modeServiceImpl.ts index 083d387118b..6b2fd6f80f8 100644 --- a/src/vs/editor/common/services/modeServiceImpl.ts +++ b/src/vs/editor/common/services/modeServiceImpl.ts @@ -50,7 +50,7 @@ export class ModeServiceImpl implements IModeService { public readonly onDidCreateMode: Event = this._onDidCreateMode.event; protected readonly _onLanguagesMaybeChanged = new Emitter(); - private readonly onLanguagesMaybeChanged: Event = this._onLanguagesMaybeChanged.event; + public readonly onLanguagesMaybeChanged: Event = this._onLanguagesMaybeChanged.event; constructor(warnOnOverwrite = false) { this._instantiatedModes = {}; diff --git a/src/vs/editor/common/services/modelService.ts b/src/vs/editor/common/services/modelService.ts index 1c4a4514404..60ce39358dc 100644 --- a/src/vs/editor/common/services/modelService.ts +++ b/src/vs/editor/common/services/modelService.ts @@ -8,9 +8,13 @@ import { URI } from 'vs/base/common/uri'; import { ITextBufferFactory, ITextModel, ITextModelCreationOptions } from 'vs/editor/common/model'; import { ILanguageSelection } from 'vs/editor/common/services/modeService'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { DocumentSemanticTokensProvider, DocumentRangeSemanticTokensProvider } from 'vs/editor/common/modes'; +import { SemanticTokensProviderStyling } from 'vs/editor/common/services/semanticTokensProviderStyling'; export const IModelService = createDecorator('modelService'); +export type DocumentTokensProvider = DocumentSemanticTokensProvider | DocumentRangeSemanticTokensProvider; + export interface IModelService { _serviceBrand: undefined; @@ -28,6 +32,8 @@ export interface IModelService { getModel(resource: URI): ITextModel | null; + getSemanticTokensProviderStyling(provider: DocumentTokensProvider): SemanticTokensProviderStyling; + onModelAdded: Event; onModelRemoved: Event; diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 20e32b9d2a7..42d2754445e 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -3,29 +3,37 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import * as errors from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/config/editorOptions'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; -import { DefaultEndOfLine, EndOfLinePreference, EndOfLineSequence, IIdentifiedSingleEditOperation, ITextBuffer, ITextBufferFactory, ITextModel, ITextModelCreationOptions, IValidEditOperation } from 'vs/editor/common/model'; +import { DefaultEndOfLine, EndOfLinePreference, EndOfLineSequence, IIdentifiedSingleEditOperation, ITextBuffer, ITextBufferFactory, ITextModel, ITextModelCreationOptions } from 'vs/editor/common/model'; import { TextModel, createTextBuffer } from 'vs/editor/common/model/textModel'; import { IModelLanguageChangedEvent, IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; -import { LanguageIdentifier, DocumentSemanticTokensProviderRegistry, DocumentSemanticTokensProvider, SemanticTokensLegend, SemanticTokens, SemanticTokensEdits, TokenMetadata, FontStyle, MetadataConsts } from 'vs/editor/common/modes'; +import { LanguageIdentifier, DocumentSemanticTokensProviderRegistry, DocumentSemanticTokensProvider, SemanticTokens, SemanticTokensEdits } from 'vs/editor/common/modes'; import { PLAINTEXT_LANGUAGE_IDENTIFIER } from 'vs/editor/common/modes/modesRegistry'; import { ILanguageSelection } from 'vs/editor/common/services/modeService'; -import { IModelService } from 'vs/editor/common/services/modelService'; +import { IModelService, DocumentTokensProvider } from 'vs/editor/common/services/modelService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { RunOnceScheduler } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { SparseEncodedTokens, MultilineTokens2 } from 'vs/editor/common/model/tokensStore'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ILogService, LogLevel } from 'vs/platform/log/common/log'; -import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IUndoRedoService, IUndoRedoElement, IPastFutureElements } from 'vs/platform/undoRedo/common/undoRedo'; +import { StringSHA1 } from 'vs/base/common/hash'; +import { SingleModelEditStackElement, MultiModelEditStackElement, EditStackElement } from 'vs/editor/common/model/editStack'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { Schemas } from 'vs/base/common/network'; +import Severity from 'vs/base/common/severity'; +import { SemanticTokensProviderStyling, toMultilineTokens2 } from 'vs/editor/common/services/semanticTokensProviderStyling'; + +export const MAINTAIN_UNDO_REDO_STACK = true; export interface IEditorSemanticHighlightingOptions { enabled?: boolean; @@ -35,6 +43,18 @@ function MODEL_ID(resource: URI): string { return resource.toString(); } +function computeModelSha1(model: ITextModel): string { + // compute the sha1 + const shaComputer = new StringSHA1(); + const snapshot = model.createSnapshot(); + let text: string | null; + while ((text = snapshot.read())) { + shaComputer.update(text); + } + return shaComputer.digest(); +} + + class ModelData implements IDisposable { public readonly model: ITextModel; @@ -98,13 +118,42 @@ interface IRawConfig { const DEFAULT_EOL = (platform.isLinux || platform.isMacintosh) ? DefaultEndOfLine.LF : DefaultEndOfLine.CRLF; -export class ModelServiceImpl extends Disposable implements IModelService { - public _serviceBrand: undefined; +interface EditStackPastFutureElements { + past: EditStackElement[]; + future: EditStackElement[]; +} - private readonly _configurationService: IConfigurationService; - private readonly _configurationServiceSubscription: IDisposable; - private readonly _resourcePropertiesService: ITextResourcePropertiesService; - private readonly _undoRedoService: IUndoRedoService; +function isEditStackPastFutureElements(undoElements: IPastFutureElements): undoElements is EditStackPastFutureElements { + return (isEditStackElements(undoElements.past) && isEditStackElements(undoElements.future)); +} + +function isEditStackElements(elements: IUndoRedoElement[]): elements is EditStackElement[] { + for (const element of elements) { + if (element instanceof SingleModelEditStackElement) { + continue; + } + if (element instanceof MultiModelEditStackElement) { + continue; + } + return false; + } + return true; +} + +class DisposedModelInfo { + constructor( + public readonly uri: URI, + public readonly sha1: string, + public readonly versionId: number, + public readonly alternativeVersionId: number, + ) { } +} + +export class ModelServiceImpl extends Disposable implements IModelService { + + private static _PROMPT_UNDO_REDO_SIZE_LIMIT = 10 * 1024 * 1024; // 10MB + + public _serviceBrand: undefined; private readonly _onModelAdded: Emitter = this._register(new Emitter()); public readonly onModelAdded: Event = this._onModelAdded.event; @@ -115,39 +164,39 @@ export class ModelServiceImpl extends Disposable implements IModelService { private readonly _onModelModeChanged: Emitter<{ model: ITextModel; oldModeId: string; }> = this._register(new Emitter<{ model: ITextModel; oldModeId: string; }>()); public readonly onModelModeChanged: Event<{ model: ITextModel; oldModeId: string; }> = this._onModelModeChanged.event; - private _modelCreationOptionsByLanguageAndResource: { - [languageAndResource: string]: ITextModelCreationOptions; - }; + private _modelCreationOptionsByLanguageAndResource: { [languageAndResource: string]: ITextModelCreationOptions; }; /** * All the models known in the system. */ private readonly _models: { [modelId: string]: ModelData; }; + private readonly _disposedModels: Map; + private readonly _semanticStyling: SemanticStyling; constructor( - @IConfigurationService configurationService: IConfigurationService, - @ITextResourcePropertiesService resourcePropertiesService: ITextResourcePropertiesService, - @IThemeService themeService: IThemeService, - @ILogService logService: ILogService, - @IUndoRedoService undoRedoService: IUndoRedoService + @IConfigurationService private readonly _configurationService: IConfigurationService, + @ITextResourcePropertiesService private readonly _resourcePropertiesService: ITextResourcePropertiesService, + @IThemeService private readonly _themeService: IThemeService, + @ILogService private readonly _logService: ILogService, + @IUndoRedoService private readonly _undoRedoService: IUndoRedoService, + @IDialogService private readonly _dialogService: IDialogService, ) { super(); - this._configurationService = configurationService; - this._resourcePropertiesService = resourcePropertiesService; - this._undoRedoService = undoRedoService; - this._models = {}; this._modelCreationOptionsByLanguageAndResource = Object.create(null); + this._models = {}; + this._disposedModels = new Map(); + this._semanticStyling = this._register(new SemanticStyling(this._themeService, this._logService)); - this._configurationServiceSubscription = this._configurationService.onDidChangeConfiguration(e => this._updateModelOptions()); + this._register(this._configurationService.onDidChangeConfiguration(() => this._updateModelOptions())); this._updateModelOptions(); - this._register(new SemanticColoringFeature(this, themeService, configurationService, logService)); + this._register(new SemanticColoringFeature(this, this._themeService, this._configurationService, this._semanticStyling)); } private static _readModelOptions(config: IRawConfig, isForSimpleWidget: boolean): ITextModelCreationOptions { let tabSize = EDITOR_MODEL_DEFAULTS.tabSize; if (config.editor && typeof config.editor.tabSize !== 'undefined') { - let parsedTabSize = parseInt(config.editor.tabSize, 10); + const parsedTabSize = parseInt(config.editor.tabSize, 10); if (!isNaN(parsedTabSize)) { tabSize = parsedTabSize; } @@ -158,7 +207,7 @@ export class ModelServiceImpl extends Disposable implements IModelService { let indentSize = tabSize; if (config.editor && typeof config.editor.indentSize !== 'undefined' && config.editor.indentSize !== 'tabSize') { - let parsedIndentSize = parseInt(config.editor.indentSize, 10); + const parsedIndentSize = parseInt(config.editor.indentSize, 10); if (!isNaN(parsedIndentSize)) { indentSize = parsedIndentSize; } @@ -207,11 +256,22 @@ export class ModelServiceImpl extends Disposable implements IModelService { }; } + private _getEOL(resource: URI | undefined, language: string): string { + if (resource) { + return this._resourcePropertiesService.getEOL(resource, language); + } + const eol = this._configurationService.getValue('files.eol', { overrideIdentifier: language }); + if (eol && eol !== 'auto') { + return eol; + } + return platform.OS === platform.OperatingSystem.Linux || platform.OS === platform.OperatingSystem.Macintosh ? '\n' : '\r\n'; + } + public getCreationOptions(language: string, resource: URI | undefined, isForSimpleWidget: boolean): ITextModelCreationOptions { let creationOptions = this._modelCreationOptionsByLanguageAndResource[language + resource]; if (!creationOptions) { const editor = this._configurationService.getValue('editor', { overrideIdentifier: language, resource }); - const eol = this._resourcePropertiesService.getEOL(resource, language); + const eol = this._getEOL(resource, language); creationOptions = ModelServiceImpl._readModelOptions({ editor, eol }, isForSimpleWidget); this._modelCreationOptionsByLanguageAndResource[language + resource] = creationOptions; } @@ -219,14 +279,14 @@ export class ModelServiceImpl extends Disposable implements IModelService { } private _updateModelOptions(): void { - let oldOptionsByLanguageAndResource = this._modelCreationOptionsByLanguageAndResource; + const oldOptionsByLanguageAndResource = this._modelCreationOptionsByLanguageAndResource; this._modelCreationOptionsByLanguageAndResource = Object.create(null); // Update options on all models - let keys = Object.keys(this._models); + const keys = Object.keys(this._models); for (let i = 0, len = keys.length; i < len; i++) { - let modelId = keys[i]; - let modelData = this._models[modelId]; + const modelId = keys[i]; + const modelData = this._models[modelId]; const language = modelData.model.getLanguageIdentifier().language; const uri = modelData.model.uri; const oldOptions = oldOptionsByLanguageAndResource[language + uri]; @@ -266,17 +326,30 @@ export class ModelServiceImpl extends Disposable implements IModelService { } } - public dispose(): void { - this._configurationServiceSubscription.dispose(); - super.dispose(); - } - // --- begin IModelService private _createModelData(value: string | ITextBufferFactory, languageIdentifier: LanguageIdentifier, resource: URI | undefined, isForSimpleWidget: boolean): ModelData { // create & save the model const options = this.getCreationOptions(languageIdentifier.language, resource, isForSimpleWidget); const model: TextModel = new TextModel(value, options, languageIdentifier, resource, this._undoRedoService); + if (resource && this._disposedModels.has(MODEL_ID(resource))) { + const disposedModelData = this._disposedModels.get(MODEL_ID(resource))!; + this._disposedModels.delete(MODEL_ID(resource)); + const elements = this._undoRedoService.getElements(resource); + if (computeModelSha1(model) === disposedModelData.sha1 && isEditStackPastFutureElements(elements)) { + for (const element of elements.past) { + element.setModel(model); + } + for (const element of elements.future) { + element.setModel(model); + } + this._undoRedoService.setElementsIsValid(resource, true); + model._overwriteVersionId(disposedModelData.versionId); + model._overwriteAlternativeVersionId(disposedModelData.alternativeVersionId); + } else { + this._undoRedoService.removeElements(resource); + } + } const modelId = MODEL_ID(model.uri); if (this._models[modelId]) { @@ -309,7 +382,7 @@ export class ModelServiceImpl extends Disposable implements IModelService { model.pushEditOperations( [], ModelServiceImpl._computeEdits(model, textBuffer), - (inverseEditOperations: IValidEditOperation[]) => [] + () => [] ); model.pushStackElement(); } @@ -349,7 +422,8 @@ export class ModelServiceImpl extends Disposable implements IModelService { const commonSuffix = this._commonSuffix(model, modelLineCount - commonPrefix, commonPrefix, textBuffer, textBufferLineCount - commonPrefix, commonPrefix); - let oldRange: Range, newRange: Range; + let oldRange: Range; + let newRange: Range; if (commonSuffix > 0) { oldRange = new Range(commonPrefix + 1, 1, modelLineCount - commonSuffix + 1, 1); newRange = new Range(commonPrefix + 1, 1, textBufferLineCount - commonSuffix + 1, 1); @@ -383,7 +457,7 @@ export class ModelServiceImpl extends Disposable implements IModelService { if (!languageSelection) { return; } - let modelData = this._models[MODEL_ID(model.uri)]; + const modelData = this._models[MODEL_ID(model.uri)]; if (!modelData) { return; } @@ -392,19 +466,69 @@ export class ModelServiceImpl extends Disposable implements IModelService { public destroyModel(resource: URI): void { // We need to support that not all models get disposed through this service (i.e. model.dispose() should work!) - let modelData = this._models[MODEL_ID(resource)]; + const modelData = this._models[MODEL_ID(resource)]; if (!modelData) { return; } + const model = modelData.model; + let maintainUndoRedoStack = false; + let heapSize = 0; + if (MAINTAIN_UNDO_REDO_STACK && (resource.scheme === Schemas.file || resource.scheme === Schemas.vscodeRemote || resource.scheme === Schemas.userData)) { + const elements = this._undoRedoService.getElements(resource); + if ((elements.past.length > 0 || elements.future.length > 0) && isEditStackPastFutureElements(elements)) { + maintainUndoRedoStack = true; + for (const element of elements.past) { + heapSize += element.heapSize(resource); + element.setModel(resource); // remove reference from text buffer instance + } + for (const element of elements.future) { + heapSize += element.heapSize(resource); + element.setModel(resource); // remove reference from text buffer instance + } + } else { + maintainUndoRedoStack = false; + } + } + + if (maintainUndoRedoStack) { + // We only invalidate the elements, but they remain in the undo-redo service. + this._undoRedoService.setElementsIsValid(resource, false); + this._disposedModels.set(MODEL_ID(resource), new DisposedModelInfo(resource, computeModelSha1(model), model.getVersionId(), model.getAlternativeVersionId())); + } else { + this._undoRedoService.removeElements(resource); + } + modelData.model.dispose(); + + // After disposing the model, prompt and ask if we should keep the undo-redo stack + if (maintainUndoRedoStack && heapSize > ModelServiceImpl._PROMPT_UNDO_REDO_SIZE_LIMIT) { + const mbSize = (heapSize / 1024 / 1024).toFixed(1); + this._dialogService.show( + Severity.Info, + nls.localize('undoRedoConfirm', "Keep the undo-redo stack for {0} in memory ({1} MB)?", (resource.scheme === Schemas.file ? resource.fsPath : resource.path), mbSize), + [ + nls.localize('nok', "Discard"), + nls.localize('ok', "Keep"), + ], + { + cancelId: 2 + } + ).then((result) => { + const discard = (result.choice === 2 || result.choice === 0); + if (discard) { + this._disposedModels.delete(MODEL_ID(resource)); + this._undoRedoService.removeElements(resource); + } + }); + } } public getModels(): ITextModel[] { - let ret: ITextModel[] = []; + const ret: ITextModel[] = []; - let keys = Object.keys(this._models); + const keys = Object.keys(this._models); for (let i = 0, len = keys.length; i < len; i++) { - let modelId = keys[i]; + const modelId = keys[i]; ret.push(this._models[modelId].model); } @@ -412,19 +536,23 @@ export class ModelServiceImpl extends Disposable implements IModelService { } public getModel(resource: URI): ITextModel | null { - let modelId = MODEL_ID(resource); - let modelData = this._models[modelId]; + const modelId = MODEL_ID(resource); + const modelData = this._models[modelId]; if (!modelData) { return null; } return modelData.model; } + public getSemanticTokensProviderStyling(provider: DocumentTokensProvider): SemanticTokensProviderStyling { + return this._semanticStyling.get(provider); + } + // --- end IModelService private _onWillDispose(model: ITextModel): void { - let modelId = MODEL_ID(model.uri); - let modelData = this._models[modelId]; + const modelId = MODEL_ID(model.uri); + const modelData = this._models[modelId]; delete this._models[modelId]; modelData.dispose(); @@ -449,24 +577,26 @@ export interface ILineSequence { getLineContent(lineNumber: number): string; } +export const SEMANTIC_HIGHLIGHTING_SETTING_ID = 'editor.semanticHighlighting'; + +export function isSemanticColoringEnabled(model: ITextModel, themeService: IThemeService, configurationService: IConfigurationService): boolean { + if (!themeService.getColorTheme().semanticHighlighting) { + return false; + } + const options = configurationService.getValue(SEMANTIC_HIGHLIGHTING_SETTING_ID, { overrideIdentifier: model.getLanguageIdentifier().language, resource: model.uri }); + return Boolean(options && options.enabled); +} + class SemanticColoringFeature extends Disposable { - private static readonly SETTING_ID = 'editor.semanticHighlighting'; + private readonly _watchers: Record; + private readonly _semanticStyling: SemanticStyling; - private _watchers: Record; - private _semanticStyling: SemanticStyling; - private _configurationService: IConfigurationService; - - constructor(modelService: IModelService, themeService: IThemeService, configurationService: IConfigurationService, logService: ILogService) { + constructor(modelService: IModelService, themeService: IThemeService, configurationService: IConfigurationService, semanticStyling: SemanticStyling) { super(); - this._configurationService = configurationService; this._watchers = Object.create(null); - this._semanticStyling = this._register(new SemanticStyling(themeService, logService)); + this._semanticStyling = semanticStyling; - const isSemanticColoringEnabled = (model: ITextModel) => { - const options = configurationService.getValue(SemanticColoringFeature.SETTING_ID, { overrideIdentifier: model.getLanguageIdentifier().language, resource: model.uri }); - return options && options.enabled; - }; const register = (model: ITextModel) => { this._watchers[model.uri.toString()] = new ModelSemanticColoring(model, themeService, this._semanticStyling); }; @@ -474,8 +604,22 @@ class SemanticColoringFeature extends Disposable { modelSemanticColoring.dispose(); delete this._watchers[model.uri.toString()]; }; + const handleSettingOrThemeChange = () => { + for (let model of modelService.getModels()) { + const curr = this._watchers[model.uri.toString()]; + if (isSemanticColoringEnabled(model, themeService, configurationService)) { + if (!curr) { + register(model); + } + } else { + if (curr) { + deregister(model, curr); + } + } + } + }; this._register(modelService.onModelAdded((model) => { - if (isSemanticColoringEnabled(model)) { + if (isSemanticColoringEnabled(model, themeService, configurationService)) { register(model); } })); @@ -485,225 +629,38 @@ class SemanticColoringFeature extends Disposable { deregister(model, curr); } })); - this._configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(SemanticColoringFeature.SETTING_ID)) { - for (let model of modelService.getModels()) { - const curr = this._watchers[model.uri.toString()]; - if (isSemanticColoringEnabled(model)) { - if (!curr) { - register(model); - } - } else { - if (curr) { - deregister(model, curr); - } - } - } + this._register(configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(SEMANTIC_HIGHLIGHTING_SETTING_ID)) { + handleSettingOrThemeChange(); } - }); + })); + this._register(themeService.onDidColorThemeChange(handleSettingOrThemeChange)); } } class SemanticStyling extends Disposable { - private _caches: WeakMap; + private _caches: WeakMap; constructor( private readonly _themeService: IThemeService, private readonly _logService: ILogService ) { super(); - this._caches = new WeakMap(); - if (this._themeService) { - // workaround for tests which use undefined... :/ - this._register(this._themeService.onThemeChange(() => { - this._caches = new WeakMap(); - })); - } + this._caches = new WeakMap(); + this._register(this._themeService.onDidColorThemeChange(() => { + this._caches = new WeakMap(); + })); } - public get(provider: DocumentSemanticTokensProvider): SemanticColoringProviderStyling { + public get(provider: DocumentTokensProvider): SemanticTokensProviderStyling { if (!this._caches.has(provider)) { - this._caches.set(provider, new SemanticColoringProviderStyling(provider.getLegend(), this._themeService, this._logService)); + this._caches.set(provider, new SemanticTokensProviderStyling(provider.getLegend(), this._themeService, this._logService)); } return this._caches.get(provider)!; } } -const enum Constants { - NO_STYLING = 0b01111111111111111111111111111111 -} - -class HashTableEntry { - public readonly tokenTypeIndex: number; - public readonly tokenModifierSet: number; - public readonly metadata: number; - public next: HashTableEntry | null; - - constructor(tokenTypeIndex: number, tokenModifierSet: number, metadata: number) { - this.tokenTypeIndex = tokenTypeIndex; - this.tokenModifierSet = tokenModifierSet; - this.metadata = metadata; - this.next = null; - } -} - -class HashTable { - - private static _SIZES = [3, 7, 13, 31, 61, 127, 251, 509, 1021, 2039, 4093, 8191, 16381, 32749, 65521, 131071, 262139, 524287, 1048573, 2097143]; - - private _elementsCount: number; - private _currentLengthIndex: number; - private _currentLength: number; - private _growCount: number; - private _elements: (HashTableEntry | null)[]; - - constructor() { - this._elementsCount = 0; - this._currentLengthIndex = 0; - this._currentLength = HashTable._SIZES[this._currentLengthIndex]; - this._growCount = Math.round(this._currentLengthIndex + 1 < HashTable._SIZES.length ? 2 / 3 * this._currentLength : 0); - this._elements = []; - HashTable._nullOutEntries(this._elements, this._currentLength); - } - - private static _nullOutEntries(entries: (HashTableEntry | null)[], length: number): void { - for (let i = 0; i < length; i++) { - entries[i] = null; - } - } - - private _hashFunc(tokenTypeIndex: number, tokenModifierSet: number): number { - return ((((tokenTypeIndex << 5) - tokenTypeIndex) + tokenModifierSet) | 0) % this._currentLength; // tokenTypeIndex * 31 + tokenModifierSet, keep as int32 - } - - public get(tokenTypeIndex: number, tokenModifierSet: number): HashTableEntry | null { - const hash = this._hashFunc(tokenTypeIndex, tokenModifierSet); - - let p = this._elements[hash]; - while (p) { - if (p.tokenTypeIndex === tokenTypeIndex && p.tokenModifierSet === tokenModifierSet) { - return p; - } - p = p.next; - } - - return null; - } - - public add(tokenTypeIndex: number, tokenModifierSet: number, metadata: number): void { - this._elementsCount++; - if (this._growCount !== 0 && this._elementsCount >= this._growCount) { - // expand! - const oldElements = this._elements; - - this._currentLengthIndex++; - this._currentLength = HashTable._SIZES[this._currentLengthIndex]; - this._growCount = Math.round(this._currentLengthIndex + 1 < HashTable._SIZES.length ? 2 / 3 * this._currentLength : 0); - this._elements = []; - HashTable._nullOutEntries(this._elements, this._currentLength); - - for (const first of oldElements) { - let p = first; - while (p) { - const oldNext = p.next; - p.next = null; - this._add(p); - p = oldNext; - } - } - } - this._add(new HashTableEntry(tokenTypeIndex, tokenModifierSet, metadata)); - } - - private _add(element: HashTableEntry): void { - const hash = this._hashFunc(element.tokenTypeIndex, element.tokenModifierSet); - element.next = this._elements[hash]; - this._elements[hash] = element; - } -} - -class SemanticColoringProviderStyling { - - private readonly _hashTable: HashTable; - - constructor( - private readonly _legend: SemanticTokensLegend, - private readonly _themeService: IThemeService, - private readonly _logService: ILogService - ) { - this._hashTable = new HashTable(); - } - - public getMetadata(tokenTypeIndex: number, tokenModifierSet: number): number { - const entry = this._hashTable.get(tokenTypeIndex, tokenModifierSet); - let metadata: number; - if (entry) { - metadata = entry.metadata; - } else { - const tokenType = this._legend.tokenTypes[tokenTypeIndex]; - const tokenModifiers: string[] = []; - let modifierSet = tokenModifierSet; - for (let modifierIndex = 0; modifierSet > 0 && modifierIndex < this._legend.tokenModifiers.length; modifierIndex++) { - if (modifierSet & 1) { - tokenModifiers.push(this._legend.tokenModifiers[modifierIndex]); - } - modifierSet = modifierSet >> 1; - } - - const tokenStyle = this._themeService.getTheme().getTokenStyleMetadata(tokenType, tokenModifiers); - if (typeof tokenStyle === 'undefined') { - metadata = Constants.NO_STYLING; - } else { - metadata = 0; - if (typeof tokenStyle.italic !== 'undefined') { - const italicBit = (tokenStyle.italic ? FontStyle.Italic : 0) << MetadataConsts.FONT_STYLE_OFFSET; - metadata |= italicBit | MetadataConsts.SEMANTIC_USE_ITALIC; - } - if (typeof tokenStyle.bold !== 'undefined') { - const boldBit = (tokenStyle.bold ? FontStyle.Bold : 0) << MetadataConsts.FONT_STYLE_OFFSET; - metadata |= boldBit | MetadataConsts.SEMANTIC_USE_BOLD; - } - if (typeof tokenStyle.underline !== 'undefined') { - const underlineBit = (tokenStyle.underline ? FontStyle.Underline : 0) << MetadataConsts.FONT_STYLE_OFFSET; - metadata |= underlineBit | MetadataConsts.SEMANTIC_USE_UNDERLINE; - } - if (tokenStyle.foreground) { - const foregroundBits = (tokenStyle.foreground) << MetadataConsts.FOREGROUND_OFFSET; - metadata |= foregroundBits | MetadataConsts.SEMANTIC_USE_FOREGROUND; - } - if (metadata === 0) { - // Nothing! - metadata = Constants.NO_STYLING; - } - } - this._hashTable.add(tokenTypeIndex, tokenModifierSet, metadata); - } - if (this._logService.getLevel() === LogLevel.Trace) { - const type = this._legend.tokenTypes[tokenTypeIndex]; - const modifiers = tokenModifierSet ? ' ' + this._legend.tokenModifiers.filter((_, i) => tokenModifierSet & (1 << i)).join(' ') : ''; - this._logService.trace(`tokenStyleMetadata ${entry ? '[CACHED] ' : ''}${type}${modifiers}: foreground ${TokenMetadata.getForeground(metadata)}, fontStyle ${TokenMetadata.getFontStyle(metadata).toString(2)}`); - } - return metadata; - } - - -} - -const enum SemanticColoringConstants { - /** - * Let's aim at having 8KB buffers if possible... - * So that would be 8192 / (5 * 4) = 409.6 tokens per area - */ - DesiredTokensPerArea = 400, - - /** - * Try to keep the total number of areas under 1024 if possible, - * simply compensate by having more tokens per area... - */ - DesiredMaxAreas = 1024, -} - class SemanticTokensResponse { constructor( private readonly _provider: DocumentSemanticTokensProvider, @@ -721,9 +678,10 @@ class ModelSemanticColoring extends Disposable { private _isDisposed: boolean; private readonly _model: ITextModel; private readonly _semanticStyling: SemanticStyling; - private readonly _fetchSemanticTokens: RunOnceScheduler; - private _currentResponse: SemanticTokensResponse | null; - private _currentRequestCancellationTokenSource: CancellationTokenSource | null; + private readonly _fetchDocumentSemanticTokens: RunOnceScheduler; + private _currentDocumentResponse: SemanticTokensResponse | null; + private _currentDocumentRequestCancellationTokenSource: CancellationTokenSource | null; + private _documentProvidersChangeListeners: IDisposable[]; constructor(model: ITextModel, themeService: IThemeService, stylingProvider: SemanticStyling) { super(); @@ -731,44 +689,57 @@ class ModelSemanticColoring extends Disposable { this._isDisposed = false; this._model = model; this._semanticStyling = stylingProvider; - this._fetchSemanticTokens = this._register(new RunOnceScheduler(() => this._fetchSemanticTokensNow(), 300)); - this._currentResponse = null; - this._currentRequestCancellationTokenSource = null; + this._fetchDocumentSemanticTokens = this._register(new RunOnceScheduler(() => this._fetchDocumentSemanticTokensNow(), 300)); + this._currentDocumentResponse = null; + this._currentDocumentRequestCancellationTokenSource = null; + this._documentProvidersChangeListeners = []; - this._register(this._model.onDidChangeContent(e => { - if (!this._fetchSemanticTokens.isScheduled()) { - this._fetchSemanticTokens.schedule(); + this._register(this._model.onDidChangeContent(() => { + if (!this._fetchDocumentSemanticTokens.isScheduled()) { + this._fetchDocumentSemanticTokens.schedule(); } })); - this._register(DocumentSemanticTokensProviderRegistry.onDidChange(e => this._fetchSemanticTokens.schedule())); - if (themeService) { - // workaround for tests which use undefined... :/ - this._register(themeService.onThemeChange(_ => { - // clear out existing tokens - this._setSemanticTokens(null, null, null, []); - this._fetchSemanticTokens.schedule(); - })); - } - this._fetchSemanticTokens.schedule(0); + const bindDocumentChangeListeners = () => { + dispose(this._documentProvidersChangeListeners); + this._documentProvidersChangeListeners = []; + for (const provider of DocumentSemanticTokensProviderRegistry.all(model)) { + if (typeof provider.onDidChange === 'function') { + this._documentProvidersChangeListeners.push(provider.onDidChange(() => this._fetchDocumentSemanticTokens.schedule(0))); + } + } + }; + bindDocumentChangeListeners(); + this._register(DocumentSemanticTokensProviderRegistry.onDidChange(() => { + bindDocumentChangeListeners(); + this._fetchDocumentSemanticTokens.schedule(); + })); + + this._register(themeService.onDidColorThemeChange(_ => { + // clear out existing tokens + this._setDocumentSemanticTokens(null, null, null, []); + this._fetchDocumentSemanticTokens.schedule(); + })); + + this._fetchDocumentSemanticTokens.schedule(0); } public dispose(): void { - if (this._currentResponse) { - this._currentResponse.dispose(); - this._currentResponse = null; + if (this._currentDocumentResponse) { + this._currentDocumentResponse.dispose(); + this._currentDocumentResponse = null; } - if (this._currentRequestCancellationTokenSource) { - this._currentRequestCancellationTokenSource.cancel(); - this._currentRequestCancellationTokenSource = null; + if (this._currentDocumentRequestCancellationTokenSource) { + this._currentDocumentRequestCancellationTokenSource.cancel(); + this._currentDocumentRequestCancellationTokenSource = null; } - this._setSemanticTokens(null, null, null, []); + this._setDocumentSemanticTokens(null, null, null, []); this._isDisposed = true; super.dispose(); } - private _fetchSemanticTokensNow(): void { - if (this._currentRequestCancellationTokenSource) { + private _fetchDocumentSemanticTokensNow(): void { + if (this._currentDocumentRequestCancellationTokenSource) { // there is already a request running, let it finish... return; } @@ -776,7 +747,7 @@ class ModelSemanticColoring extends Disposable { if (!provider) { return; } - this._currentRequestCancellationTokenSource = new CancellationTokenSource(); + this._currentDocumentRequestCancellationTokenSource = new CancellationTokenSource(); const pendingChanges: IModelContentChangedEvent[] = []; const contentChangeListener = this._model.onDidChangeContent((e) => { @@ -785,13 +756,13 @@ class ModelSemanticColoring extends Disposable { const styling = this._semanticStyling.get(provider); - const lastResultId = this._currentResponse ? this._currentResponse.resultId || null : null; - const request = Promise.resolve(provider.provideDocumentSemanticTokens(this._model, lastResultId, this._currentRequestCancellationTokenSource.token)); + const lastResultId = this._currentDocumentResponse ? this._currentDocumentResponse.resultId || null : null; + const request = Promise.resolve(provider.provideDocumentSemanticTokens(this._model, lastResultId, this._currentDocumentRequestCancellationTokenSource.token)); request.then((res) => { - this._currentRequestCancellationTokenSource = null; + this._currentDocumentRequestCancellationTokenSource = null; contentChangeListener.dispose(); - this._setSemanticTokens(provider, res || null, styling, pendingChanges); + this._setDocumentSemanticTokens(provider, res || null, styling, pendingChanges); }, (err) => { if (!err || typeof err.message !== 'string' || err.message.indexOf('busy') === -1) { errors.onUnexpectedError(err); @@ -799,13 +770,13 @@ class ModelSemanticColoring extends Disposable { // Semantic tokens eats up all errors and considers errors to mean that the result is temporarily not available // The API does not have a special error kind to express this... - this._currentRequestCancellationTokenSource = null; + this._currentDocumentRequestCancellationTokenSource = null; contentChangeListener.dispose(); if (pendingChanges.length > 0) { // More changes occurred while the request was running - if (!this._fetchSemanticTokens.isScheduled()) { - this._fetchSemanticTokens.schedule(); + if (!this._fetchDocumentSemanticTokens.isScheduled()) { + this._fetchDocumentSemanticTokens.schedule(); } } }); @@ -825,11 +796,11 @@ class ModelSemanticColoring extends Disposable { } } - private _setSemanticTokens(provider: DocumentSemanticTokensProvider | null, tokens: SemanticTokens | SemanticTokensEdits | null, styling: SemanticColoringProviderStyling | null, pendingChanges: IModelContentChangedEvent[]): void { - const currentResponse = this._currentResponse; - if (this._currentResponse) { - this._currentResponse.dispose(); - this._currentResponse = null; + private _setDocumentSemanticTokens(provider: DocumentSemanticTokensProvider | null, tokens: SemanticTokens | SemanticTokensEdits | null, styling: SemanticTokensProviderStyling | null, pendingChanges: IModelContentChangedEvent[]): void { + const currentResponse = this._currentDocumentResponse; + if (this._currentDocumentResponse) { + this._currentDocumentResponse.dispose(); + this._currentDocumentResponse = null; } if (this._isDisposed) { // disposed! @@ -838,15 +809,19 @@ class ModelSemanticColoring extends Disposable { } return; } - if (!provider || !tokens || !styling) { - this._model.setSemanticTokens(null); + if (!provider || !styling) { + this._model.setSemanticTokens(null, false); + return; + } + if (!tokens) { + this._model.setSemanticTokens(null, true); return; } if (ModelSemanticColoring._isSemanticTokensEdits(tokens)) { if (!currentResponse) { // not possible! - this._model.setSemanticTokens(null); + this._model.setSemanticTokens(null, true); return; } if (tokens.edits.length === 0) { @@ -896,78 +871,9 @@ class ModelSemanticColoring extends Disposable { if (ModelSemanticColoring._isSemanticTokens(tokens)) { - this._currentResponse = new SemanticTokensResponse(provider, tokens.resultId, tokens.data); + this._currentDocumentResponse = new SemanticTokensResponse(provider, tokens.resultId, tokens.data); - const srcData = tokens.data; - const tokenCount = (tokens.data.length / 5) | 0; - const tokensPerArea = Math.max(Math.ceil(tokenCount / SemanticColoringConstants.DesiredMaxAreas), SemanticColoringConstants.DesiredTokensPerArea); - - const result: MultilineTokens2[] = []; - - let tokenIndex = 0; - let lastLineNumber = 1; - let lastStartCharacter = 0; - while (tokenIndex < tokenCount) { - const tokenStartIndex = tokenIndex; - let tokenEndIndex = Math.min(tokenStartIndex + tokensPerArea, tokenCount); - - // Keep tokens on the same line in the same area... - if (tokenEndIndex < tokenCount) { - - let smallTokenEndIndex = tokenEndIndex; - while (smallTokenEndIndex - 1 > tokenStartIndex && srcData[5 * smallTokenEndIndex] === 0) { - smallTokenEndIndex--; - } - - if (smallTokenEndIndex - 1 === tokenStartIndex) { - // there are so many tokens on this line that our area would be empty, we must now go right - let bigTokenEndIndex = tokenEndIndex; - while (bigTokenEndIndex + 1 < tokenCount && srcData[5 * bigTokenEndIndex] === 0) { - bigTokenEndIndex++; - } - tokenEndIndex = bigTokenEndIndex; - } else { - tokenEndIndex = smallTokenEndIndex; - } - } - - let destData = new Uint32Array((tokenEndIndex - tokenStartIndex) * 4); - let destOffset = 0; - let areaLine = 0; - while (tokenIndex < tokenEndIndex) { - const srcOffset = 5 * tokenIndex; - const deltaLine = srcData[srcOffset]; - const deltaCharacter = srcData[srcOffset + 1]; - const lineNumber = lastLineNumber + deltaLine; - const startCharacter = (deltaLine === 0 ? lastStartCharacter + deltaCharacter : deltaCharacter); - const length = srcData[srcOffset + 2]; - const tokenTypeIndex = srcData[srcOffset + 3]; - const tokenModifierSet = srcData[srcOffset + 4]; - const metadata = styling.getMetadata(tokenTypeIndex, tokenModifierSet); - - if (metadata !== Constants.NO_STYLING) { - if (areaLine === 0) { - areaLine = lineNumber; - } - destData[destOffset] = lineNumber - areaLine; - destData[destOffset + 1] = startCharacter; - destData[destOffset + 2] = startCharacter + length; - destData[destOffset + 3] = metadata; - destOffset += 4; - } - - lastLineNumber = lineNumber; - lastStartCharacter = startCharacter; - tokenIndex++; - } - - if (destOffset !== destData.length) { - destData = destData.subarray(0, destOffset); - } - - const tokens = new MultilineTokens2(areaLine, new SparseEncodedTokens(destData)); - result.push(tokens); - } + const result = toMultilineTokens2(tokens, styling, this._model.getLanguageIdentifier()); // Adjust incoming semantic tokens if (pendingChanges.length > 0) { @@ -983,16 +889,16 @@ class ModelSemanticColoring extends Disposable { } } - if (!this._fetchSemanticTokens.isScheduled()) { - this._fetchSemanticTokens.schedule(); + if (!this._fetchDocumentSemanticTokens.isScheduled()) { + this._fetchDocumentSemanticTokens.schedule(); } } - this._model.setSemanticTokens(result); + this._model.setSemanticTokens(result, true); return; } - this._model.setSemanticTokens(null); + this._model.setSemanticTokens(null, true); } private _getSemanticColoringProvider(): DocumentSemanticTokensProvider | null { diff --git a/src/vs/editor/common/services/semanticTokensProviderStyling.ts b/src/vs/editor/common/services/semanticTokensProviderStyling.ts new file mode 100644 index 00000000000..a1cbccf4a61 --- /dev/null +++ b/src/vs/editor/common/services/semanticTokensProviderStyling.ts @@ -0,0 +1,261 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { SemanticTokensLegend, TokenMetadata, FontStyle, MetadataConsts, SemanticTokens, LanguageIdentifier } from 'vs/editor/common/modes'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { ILogService, LogLevel } from 'vs/platform/log/common/log'; +import { MultilineTokens2, SparseEncodedTokens } from 'vs/editor/common/model/tokensStore'; + +export const enum SemanticTokensProviderStylingConstants { + NO_STYLING = 0b01111111111111111111111111111111 +} + +export class SemanticTokensProviderStyling { + + private readonly _hashTable: HashTable; + + constructor( + private readonly _legend: SemanticTokensLegend, + private readonly _themeService: IThemeService, + private readonly _logService: ILogService + ) { + this._hashTable = new HashTable(); + } + + public getMetadata(tokenTypeIndex: number, tokenModifierSet: number, languageId: LanguageIdentifier): number { + const entry = this._hashTable.get(tokenTypeIndex, tokenModifierSet, languageId.id); + let metadata: number; + if (entry) { + metadata = entry.metadata; + } else { + const tokenType = this._legend.tokenTypes[tokenTypeIndex]; + const tokenModifiers: string[] = []; + let modifierSet = tokenModifierSet; + for (let modifierIndex = 0; modifierSet > 0 && modifierIndex < this._legend.tokenModifiers.length; modifierIndex++) { + if (modifierSet & 1) { + tokenModifiers.push(this._legend.tokenModifiers[modifierIndex]); + } + modifierSet = modifierSet >> 1; + } + + const tokenStyle = this._themeService.getColorTheme().getTokenStyleMetadata(tokenType, tokenModifiers, languageId.language); + if (typeof tokenStyle === 'undefined') { + metadata = SemanticTokensProviderStylingConstants.NO_STYLING; + } else { + metadata = 0; + if (typeof tokenStyle.italic !== 'undefined') { + const italicBit = (tokenStyle.italic ? FontStyle.Italic : 0) << MetadataConsts.FONT_STYLE_OFFSET; + metadata |= italicBit | MetadataConsts.SEMANTIC_USE_ITALIC; + } + if (typeof tokenStyle.bold !== 'undefined') { + const boldBit = (tokenStyle.bold ? FontStyle.Bold : 0) << MetadataConsts.FONT_STYLE_OFFSET; + metadata |= boldBit | MetadataConsts.SEMANTIC_USE_BOLD; + } + if (typeof tokenStyle.underline !== 'undefined') { + const underlineBit = (tokenStyle.underline ? FontStyle.Underline : 0) << MetadataConsts.FONT_STYLE_OFFSET; + metadata |= underlineBit | MetadataConsts.SEMANTIC_USE_UNDERLINE; + } + if (tokenStyle.foreground) { + const foregroundBits = (tokenStyle.foreground) << MetadataConsts.FOREGROUND_OFFSET; + metadata |= foregroundBits | MetadataConsts.SEMANTIC_USE_FOREGROUND; + } + if (metadata === 0) { + // Nothing! + metadata = SemanticTokensProviderStylingConstants.NO_STYLING; + } + } + this._hashTable.add(tokenTypeIndex, tokenModifierSet, languageId.id, metadata); + } + if (this._logService.getLevel() === LogLevel.Trace) { + const type = this._legend.tokenTypes[tokenTypeIndex]; + const modifiers = tokenModifierSet ? ' ' + this._legend.tokenModifiers.filter((_, i) => tokenModifierSet & (1 << i)).join(' ') : ''; + this._logService.trace(`tokenStyleMetadata ${entry ? '[CACHED] ' : ''}${type}${modifiers}: foreground ${TokenMetadata.getForeground(metadata)}, fontStyle ${TokenMetadata.getFontStyle(metadata).toString(2)}`); + } + return metadata; + } +} + +const enum SemanticColoringConstants { + /** + * Let's aim at having 8KB buffers if possible... + * So that would be 8192 / (5 * 4) = 409.6 tokens per area + */ + DesiredTokensPerArea = 400, + + /** + * Try to keep the total number of areas under 1024 if possible, + * simply compensate by having more tokens per area... + */ + DesiredMaxAreas = 1024, +} + +export function toMultilineTokens2(tokens: SemanticTokens, styling: SemanticTokensProviderStyling, languageId: LanguageIdentifier): MultilineTokens2[] { + const srcData = tokens.data; + const tokenCount = (tokens.data.length / 5) | 0; + const tokensPerArea = Math.max(Math.ceil(tokenCount / SemanticColoringConstants.DesiredMaxAreas), SemanticColoringConstants.DesiredTokensPerArea); + const result: MultilineTokens2[] = []; + + let tokenIndex = 0; + let lastLineNumber = 1; + let lastStartCharacter = 0; + while (tokenIndex < tokenCount) { + const tokenStartIndex = tokenIndex; + let tokenEndIndex = Math.min(tokenStartIndex + tokensPerArea, tokenCount); + + // Keep tokens on the same line in the same area... + if (tokenEndIndex < tokenCount) { + + let smallTokenEndIndex = tokenEndIndex; + while (smallTokenEndIndex - 1 > tokenStartIndex && srcData[5 * smallTokenEndIndex] === 0) { + smallTokenEndIndex--; + } + + if (smallTokenEndIndex - 1 === tokenStartIndex) { + // there are so many tokens on this line that our area would be empty, we must now go right + let bigTokenEndIndex = tokenEndIndex; + while (bigTokenEndIndex + 1 < tokenCount && srcData[5 * bigTokenEndIndex] === 0) { + bigTokenEndIndex++; + } + tokenEndIndex = bigTokenEndIndex; + } else { + tokenEndIndex = smallTokenEndIndex; + } + } + + let destData = new Uint32Array((tokenEndIndex - tokenStartIndex) * 4); + let destOffset = 0; + let areaLine = 0; + while (tokenIndex < tokenEndIndex) { + const srcOffset = 5 * tokenIndex; + const deltaLine = srcData[srcOffset]; + const deltaCharacter = srcData[srcOffset + 1]; + const lineNumber = lastLineNumber + deltaLine; + const startCharacter = (deltaLine === 0 ? lastStartCharacter + deltaCharacter : deltaCharacter); + const length = srcData[srcOffset + 2]; + const tokenTypeIndex = srcData[srcOffset + 3]; + const tokenModifierSet = srcData[srcOffset + 4]; + const metadata = styling.getMetadata(tokenTypeIndex, tokenModifierSet, languageId); + + if (metadata !== SemanticTokensProviderStylingConstants.NO_STYLING) { + if (areaLine === 0) { + areaLine = lineNumber; + } + destData[destOffset] = lineNumber - areaLine; + destData[destOffset + 1] = startCharacter; + destData[destOffset + 2] = startCharacter + length; + destData[destOffset + 3] = metadata; + destOffset += 4; + } + + lastLineNumber = lineNumber; + lastStartCharacter = startCharacter; + tokenIndex++; + } + + if (destOffset !== destData.length) { + destData = destData.subarray(0, destOffset); + } + + const tokens = new MultilineTokens2(areaLine, new SparseEncodedTokens(destData)); + result.push(tokens); + } + + return result; +} + +class HashTableEntry { + public readonly tokenTypeIndex: number; + public readonly tokenModifierSet: number; + public readonly languageId: number; + public readonly metadata: number; + public next: HashTableEntry | null; + + constructor(tokenTypeIndex: number, tokenModifierSet: number, languageId: number, metadata: number) { + this.tokenTypeIndex = tokenTypeIndex; + this.tokenModifierSet = tokenModifierSet; + this.languageId = languageId; + this.metadata = metadata; + this.next = null; + } +} + +class HashTable { + + private static _SIZES = [3, 7, 13, 31, 61, 127, 251, 509, 1021, 2039, 4093, 8191, 16381, 32749, 65521, 131071, 262139, 524287, 1048573, 2097143]; + + private _elementsCount: number; + private _currentLengthIndex: number; + private _currentLength: number; + private _growCount: number; + private _elements: (HashTableEntry | null)[]; + + constructor() { + this._elementsCount = 0; + this._currentLengthIndex = 0; + this._currentLength = HashTable._SIZES[this._currentLengthIndex]; + this._growCount = Math.round(this._currentLengthIndex + 1 < HashTable._SIZES.length ? 2 / 3 * this._currentLength : 0); + this._elements = []; + HashTable._nullOutEntries(this._elements, this._currentLength); + } + + private static _nullOutEntries(entries: (HashTableEntry | null)[], length: number): void { + for (let i = 0; i < length; i++) { + entries[i] = null; + } + } + + private _hash2(n1: number, n2: number): number { + return (((n1 << 5) - n1) + n2) | 0; // n1 * 31 + n2, keep as int32 + } + + private _hashFunc(tokenTypeIndex: number, tokenModifierSet: number, languageId: number): number { + return this._hash2(this._hash2(tokenTypeIndex, tokenModifierSet), languageId) % this._currentLength; + } + + public get(tokenTypeIndex: number, tokenModifierSet: number, languageId: number): HashTableEntry | null { + const hash = this._hashFunc(tokenTypeIndex, tokenModifierSet, languageId); + + let p = this._elements[hash]; + while (p) { + if (p.tokenTypeIndex === tokenTypeIndex && p.tokenModifierSet === tokenModifierSet && p.languageId === languageId) { + return p; + } + p = p.next; + } + + return null; + } + + public add(tokenTypeIndex: number, tokenModifierSet: number, languageId: number, metadata: number): void { + this._elementsCount++; + if (this._growCount !== 0 && this._elementsCount >= this._growCount) { + // expand! + const oldElements = this._elements; + + this._currentLengthIndex++; + this._currentLength = HashTable._SIZES[this._currentLengthIndex]; + this._growCount = Math.round(this._currentLengthIndex + 1 < HashTable._SIZES.length ? 2 / 3 * this._currentLength : 0); + this._elements = []; + HashTable._nullOutEntries(this._elements, this._currentLength); + + for (const first of oldElements) { + let p = first; + while (p) { + const oldNext = p.next; + p.next = null; + this._add(p); + p = oldNext; + } + } + } + this._add(new HashTableEntry(tokenTypeIndex, tokenModifierSet, languageId, metadata)); + } + + private _add(element: HashTableEntry): void { + const hash = this._hashFunc(element.tokenTypeIndex, element.tokenModifierSet, element.languageId); + element.next = this._elements[hash]; + this._elements[hash] = element; + } +} diff --git a/src/vs/editor/common/services/textResourceConfigurationService.ts b/src/vs/editor/common/services/textResourceConfigurationService.ts index d83fc390eef..792cacda5b3 100644 --- a/src/vs/editor/common/services/textResourceConfigurationService.ts +++ b/src/vs/editor/common/services/textResourceConfigurationService.ts @@ -75,5 +75,5 @@ export interface ITextResourcePropertiesService { /** * Returns the End of Line characters for the given resource */ - getEOL(resource: URI | undefined, language?: string): string; + getEOL(resource: URI, language?: string): string; } diff --git a/src/vs/editor/common/standalone/promise-polyfill/cgmanifest.json b/src/vs/editor/common/standalone/promise-polyfill/cgmanifest.json deleted file mode 100644 index b62e25bccff..00000000000 --- a/src/vs/editor/common/standalone/promise-polyfill/cgmanifest.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "registrations": [ - { - "component": { - "type": "git", - "git": { - "name": "promise-polyfill", - "repositoryUrl": "https://github.com/taylorhakes/promise-polyfill", - "commitHash": "efe662be6ea569c439ec92a4f8662c0a7faf0b96" - } - }, - "license": "MIT", - "version": "8.0.0" - } - ], - "version": 1 -} diff --git a/src/vs/editor/common/standalone/promise-polyfill/polyfill.js b/src/vs/editor/common/standalone/promise-polyfill/polyfill.js deleted file mode 100644 index 4ddfcab7cd0..00000000000 --- a/src/vs/editor/common/standalone/promise-polyfill/polyfill.js +++ /dev/null @@ -1,291 +0,0 @@ -/*! -Copyright (c) 2014 Taylor Hakes -Copyright (c) 2014 Forbes Lindesay - */ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory() : - typeof define === 'function' && define.amd ? define(factory) : - (factory()); -}(this, (function () { - 'use strict'; - - /** - * @this {Promise} - */ - function finallyConstructor(callback) { - var constructor = this.constructor; - return this.then( - function (value) { - return constructor.resolve(callback()).then(function () { - return value; - }); - }, - function (reason) { - return constructor.resolve(callback()).then(function () { - return constructor.reject(reason); - }); - } - ); - } - - // Store setTimeout reference so promise-polyfill will be unaffected by - // other code modifying setTimeout (like sinon.useFakeTimers()) - var setTimeoutFunc = setTimeout; - - function noop() { } - - // Polyfill for Function.prototype.bind - function bind(fn, thisArg) { - return function () { - fn.apply(thisArg, arguments); - }; - } - - /** - * @constructor - * @param {Function} fn - */ - function Promise(fn) { - if (!(this instanceof Promise)) - throw new TypeError('Promises must be constructed via new'); - if (typeof fn !== 'function') throw new TypeError('not a function'); - /** @type {!number} */ - this._state = 0; - /** @type {!boolean} */ - this._handled = false; - /** @type {Promise|undefined} */ - this._value = undefined; - /** @type {!Array} */ - this._deferreds = []; - - doResolve(fn, this); - } - - function handle(self, deferred) { - while (self._state === 3) { - self = self._value; - } - if (self._state === 0) { - self._deferreds.push(deferred); - return; - } - self._handled = true; - Promise._immediateFn(function () { - var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected; - if (cb === null) { - (self._state === 1 ? resolve : reject)(deferred.promise, self._value); - return; - } - var ret; - try { - ret = cb(self._value); - } catch (e) { - reject(deferred.promise, e); - return; - } - resolve(deferred.promise, ret); - }); - } - - function resolve(self, newValue) { - try { - // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure - if (newValue === self) - throw new TypeError('A promise cannot be resolved with itself.'); - if ( - newValue && - (typeof newValue === 'object' || typeof newValue === 'function') - ) { - var then = newValue.then; - if (newValue instanceof Promise) { - self._state = 3; - self._value = newValue; - finale(self); - return; - } else if (typeof then === 'function') { - doResolve(bind(then, newValue), self); - return; - } - } - self._state = 1; - self._value = newValue; - finale(self); - } catch (e) { - reject(self, e); - } - } - - function reject(self, newValue) { - self._state = 2; - self._value = newValue; - finale(self); - } - - function finale(self) { - if (self._state === 2 && self._deferreds.length === 0) { - Promise._immediateFn(function () { - if (!self._handled) { - Promise._unhandledRejectionFn(self._value); - } - }); - } - - for (var i = 0, len = self._deferreds.length; i < len; i++) { - handle(self, self._deferreds[i]); - } - self._deferreds = null; - } - - /** - * @constructor - */ - function Handler(onFulfilled, onRejected, promise) { - this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; - this.onRejected = typeof onRejected === 'function' ? onRejected : null; - this.promise = promise; - } - - /** - * Take a potentially misbehaving resolver function and make sure - * onFulfilled and onRejected are only called once. - * - * Makes no guarantees about asynchrony. - */ - function doResolve(fn, self) { - var done = false; - try { - fn( - function (value) { - if (done) return; - done = true; - resolve(self, value); - }, - function (reason) { - if (done) return; - done = true; - reject(self, reason); - } - ); - } catch (ex) { - if (done) return; - done = true; - reject(self, ex); - } - } - - Promise.prototype['catch'] = function (onRejected) { - return this.then(null, onRejected); - }; - - Promise.prototype.then = function (onFulfilled, onRejected) { - // @ts-ignore - var prom = new this.constructor(noop); - - handle(this, new Handler(onFulfilled, onRejected, prom)); - return prom; - }; - - Promise.prototype['finally'] = finallyConstructor; - - Promise.all = function (arr) { - return new Promise(function (resolve, reject) { - if (!arr || typeof arr.length === 'undefined') - throw new TypeError('Promise.all accepts an array'); - var args = Array.prototype.slice.call(arr); - if (args.length === 0) return resolve([]); - var remaining = args.length; - - function res(i, val) { - try { - if (val && (typeof val === 'object' || typeof val === 'function')) { - var then = val.then; - if (typeof then === 'function') { - then.call( - val, - function (val) { - res(i, val); - }, - reject - ); - return; - } - } - args[i] = val; - if (--remaining === 0) { - resolve(args); - } - } catch (ex) { - reject(ex); - } - } - - for (var i = 0; i < args.length; i++) { - res(i, args[i]); - } - }); - }; - - Promise.resolve = function (value) { - if (value && typeof value === 'object' && value.constructor === Promise) { - return value; - } - - return new Promise(function (resolve) { - resolve(value); - }); - }; - - Promise.reject = function (value) { - return new Promise(function (resolve, reject) { - reject(value); - }); - }; - - Promise.race = function (values) { - return new Promise(function (resolve, reject) { - for (var i = 0, len = values.length; i < len; i++) { - values[i].then(resolve, reject); - } - }); - }; - - // Use polyfill for setImmediate for performance gains - Promise._immediateFn = - (typeof setImmediate === 'function' && - function (fn) { - setImmediate(fn); - }) || - function (fn) { - setTimeoutFunc(fn, 0); - }; - - Promise._unhandledRejectionFn = function _unhandledRejectionFn(err) { - if (typeof console !== 'undefined' && console) { - console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console - } - }; - - /** @suppress {undefinedVars} */ - var globalNS = (function () { - // the only reliable means to get the global object is - // `Function('return this')()` - // However, this causes CSP violations in Chrome apps. - if (typeof self !== 'undefined') { - return self; - } - if (typeof window !== 'undefined') { - return window; - } - if (typeof global !== 'undefined') { - return global; - } - throw new Error('unable to locate global object'); - })(); - - if (!('Promise' in globalNS)) { - globalNS['Promise'] = Promise; - } else if (!globalNS.Promise.prototype['finally']) { - globalNS.Promise.prototype['finally'] = finallyConstructor; - } - -}))); diff --git a/src/vs/editor/common/standalone/standaloneBase.ts b/src/vs/editor/common/standalone/standaloneBase.ts index 377b5185c28..2239e8d0234 100644 --- a/src/vs/editor/common/standalone/standaloneBase.ts +++ b/src/vs/editor/common/standalone/standaloneBase.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/editor/common/standalone/promise-polyfill/polyfill'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; import { KeyChord, KeyMod as ConstKeyMod } from 'vs/base/common/keyCodes'; diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index 418fe492a04..7a67090482c 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -53,7 +53,9 @@ export enum CompletionItemKind { Customcolor = 22, Folder = 23, TypeParameter = 24, - Snippet = 25 + User = 25, + Issue = 26, + Snippet = 27 } export enum CompletionItemTag { @@ -238,47 +240,49 @@ export enum EditorOption { quickSuggestions = 70, quickSuggestionsDelay = 71, readOnly = 72, - renderControlCharacters = 73, - renderIndentGuides = 74, - renderFinalNewline = 75, - renderLineHighlight = 76, - renderValidationDecorations = 77, - renderWhitespace = 78, - revealHorizontalRightPadding = 79, - roundedSelection = 80, - rulers = 81, - scrollbar = 82, - scrollBeyondLastColumn = 83, - scrollBeyondLastLine = 84, - scrollPredominantAxis = 85, - selectionClipboard = 86, - selectionHighlight = 87, - selectOnLineNumbers = 88, - showFoldingControls = 89, - showUnused = 90, - snippetSuggestions = 91, - smoothScrolling = 92, - stopRenderingLineAfter = 93, - suggest = 94, - suggestFontSize = 95, - suggestLineHeight = 96, - suggestOnTriggerCharacters = 97, - suggestSelection = 98, - tabCompletion = 99, - useTabStops = 100, - wordSeparators = 101, - wordWrap = 102, - wordWrapBreakAfterCharacters = 103, - wordWrapBreakBeforeCharacters = 104, - wordWrapColumn = 105, - wordWrapMinified = 106, - wrappingIndent = 107, - wrappingStrategy = 108, - editorClassName = 109, - pixelRatio = 110, - tabFocusMode = 111, - layoutInfo = 112, - wrappingInfo = 113 + renameOnType = 73, + renderControlCharacters = 74, + renderIndentGuides = 75, + renderFinalNewline = 76, + renderLineHighlight = 77, + renderLineHighlightOnlyWhenFocus = 78, + renderValidationDecorations = 79, + renderWhitespace = 80, + revealHorizontalRightPadding = 81, + roundedSelection = 82, + rulers = 83, + scrollbar = 84, + scrollBeyondLastColumn = 85, + scrollBeyondLastLine = 86, + scrollPredominantAxis = 87, + selectionClipboard = 88, + selectionHighlight = 89, + selectOnLineNumbers = 90, + showFoldingControls = 91, + showUnused = 92, + snippetSuggestions = 93, + smoothScrolling = 94, + stopRenderingLineAfter = 95, + suggest = 96, + suggestFontSize = 97, + suggestLineHeight = 98, + suggestOnTriggerCharacters = 99, + suggestSelection = 100, + tabCompletion = 101, + useTabStops = 102, + wordSeparators = 103, + wordWrap = 104, + wordWrapBreakAfterCharacters = 105, + wordWrapBreakBeforeCharacters = 106, + wordWrapColumn = 107, + wordWrapMinified = 108, + wrappingIndent = 109, + wrappingStrategy = 110, + editorClassName = 111, + pixelRatio = 112, + tabFocusMode = 113, + layoutInfo = 114, + wrappingInfo = 115 } /** diff --git a/src/vs/editor/common/standaloneStrings.ts b/src/vs/editor/common/standaloneStrings.ts index f0c107c0cbe..88104a63373 100644 --- a/src/vs/editor/common/standaloneStrings.ts +++ b/src/vs/editor/common/standaloneStrings.ts @@ -36,42 +36,25 @@ export namespace InspectTokensNLS { } export namespace GoToLineNLS { - export const gotoLineLabelValidLineAndColumn = nls.localize('gotoLineLabelValidLineAndColumn', "Go to line {0} and character {1}"); - export const gotoLineLabelValidLine = nls.localize('gotoLineLabelValidLine', "Go to line {0}"); - export const gotoLineLabelEmptyWithLineLimit = nls.localize('gotoLineLabelEmptyWithLineLimit', "Type a line number between 1 and {0} to navigate to"); - export const gotoLineLabelEmptyWithLineAndColumnLimit = nls.localize('gotoLineLabelEmptyWithLineAndColumnLimit', "Type a character between 1 and {0} to navigate to"); - export const gotoLineAriaLabel = nls.localize('gotoLineAriaLabel', "Current Line: {0}. Go to line {1}."); - export const gotoLineActionInput = nls.localize('gotoLineActionInput', "Type a line number, followed by an optional colon and a character number to navigate to"); - export const gotoLineActionLabel = nls.localize('gotoLineActionLabel', "Go to Line..."); + export const gotoLineActionLabel = nls.localize('gotoLineActionLabel', "Go to Line/Column..."); +} + +export namespace QuickHelpNLS { + export const helpQuickAccessActionLabel = nls.localize('helpQuickAccess', "Show all Quick Access Providers"); } export namespace QuickCommandNLS { - export const ariaLabelEntryWithKey = nls.localize('ariaLabelEntryWithKey', "{0}, {1}, commands"); - export const ariaLabelEntry = nls.localize('ariaLabelEntry', "{0}, commands"); - export const quickCommandActionInput = nls.localize('quickCommandActionInput', "Type the name of an action you want to execute"); export const quickCommandActionLabel = nls.localize('quickCommandActionLabel', "Command Palette"); + export const quickCommandHelp = nls.localize('quickCommandActionHelp', "Show And Run Commands"); } export namespace QuickOutlineNLS { - export const entryAriaLabel = nls.localize('entryAriaLabel', "{0}, symbols"); - export const quickOutlineActionInput = nls.localize('quickOutlineActionInput', "Type the name of an identifier you wish to navigate to"); export const quickOutlineActionLabel = nls.localize('quickOutlineActionLabel', "Go to Symbol..."); - export const _symbols_ = nls.localize('symbols', "symbols ({0})"); - export const _modules_ = nls.localize('modules', "modules ({0})"); - export const _class_ = nls.localize('class', "classes ({0})"); - export const _interface_ = nls.localize('interface', "interfaces ({0})"); - export const _method_ = nls.localize('method', "methods ({0})"); - export const _function_ = nls.localize('function', "functions ({0})"); - export const _property_ = nls.localize('property', "properties ({0})"); - export const _variable_ = nls.localize('variable', "variables ({0})"); - export const _variable2_ = nls.localize('variable2', "variables ({0})"); - export const _constructor_ = nls.localize('_constructor', "constructors ({0})"); - export const _call_ = nls.localize('call', "calls ({0})"); + export const quickOutlineByCategoryActionLabel = nls.localize('quickOutlineByCategoryActionLabel', "Go to Symbol by Category..."); } export namespace StandaloneCodeEditorNLS { export const editorViewAccessibleLabel = nls.localize('editorViewAccessibleLabel', "Editor content"); - export const accessibilityHelpMessageIE = nls.localize('accessibilityHelpMessageIE', "Press Ctrl+F1 for Accessibility Options."); export const accessibilityHelpMessage = nls.localize('accessibilityHelpMessage', "Press Alt+F1 for Accessibility Options."); } diff --git a/src/vs/editor/common/view/viewContext.ts b/src/vs/editor/common/view/viewContext.ts index 793a3a173a5..573b0827f0a 100644 --- a/src/vs/editor/common/view/viewContext.ts +++ b/src/vs/editor/common/view/viewContext.ts @@ -7,23 +7,23 @@ import { IConfiguration } from 'vs/editor/common/editorCommon'; import { ViewEventDispatcher } from 'vs/editor/common/view/viewEventDispatcher'; import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; import { IViewLayout, IViewModel } from 'vs/editor/common/viewModel/viewModel'; -import { ITheme, ThemeType } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, ThemeType } from 'vs/platform/theme/common/themeService'; import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; import { Color } from 'vs/base/common/color'; export class EditorTheme { - private _theme: ITheme; + private _theme: IColorTheme; public get type(): ThemeType { return this._theme.type; } - constructor(theme: ITheme) { + constructor(theme: IColorTheme) { this._theme = theme; } - public update(theme: ITheme): void { + public update(theme: IColorTheme): void { this._theme = theme; } @@ -42,7 +42,7 @@ export class ViewContext { constructor( configuration: IConfiguration, - theme: ITheme, + theme: IColorTheme, model: IViewModel, privateViewEventBus: ViewEventDispatcher ) { diff --git a/src/vs/editor/common/view/viewEvents.ts b/src/vs/editor/common/view/viewEvents.ts index cb3129b9a18..19c81ba6dce 100644 --- a/src/vs/editor/common/view/viewEvents.ts +++ b/src/vs/editor/common/view/viewEvents.ts @@ -205,7 +205,12 @@ export class ViewRevealRangeRequestEvent { /** * Range to be reavealed. */ - public readonly range: Range; + public readonly range: Range | null; + + /** + * Selections to be revealed. + */ + public readonly selections: Selection[] | null; public readonly verticalType: VerticalRevealType; /** @@ -221,9 +226,10 @@ export class ViewRevealRangeRequestEvent { */ readonly source: string; - constructor(source: string, range: Range, verticalType: VerticalRevealType, revealHorizontal: boolean, scrollType: ScrollType) { + constructor(source: string, range: Range | null, selections: Selection[] | null, verticalType: VerticalRevealType, revealHorizontal: boolean, scrollType: ScrollType) { this.source = source; this.range = range; + this.selections = selections; this.verticalType = verticalType; this.revealHorizontal = revealHorizontal; this.scrollType = scrollType; diff --git a/src/vs/editor/common/viewLayout/viewLineRenderer.ts b/src/vs/editor/common/viewLayout/viewLineRenderer.ts index c032724b8ba..c698400c183 100644 --- a/src/vs/editor/common/viewLayout/viewLineRenderer.ts +++ b/src/vs/editor/common/viewLayout/viewLineRenderer.ts @@ -683,7 +683,7 @@ function _applyRenderWhitespace(input: RenderLineInput, lineContent: string, len wasInWhitespace = isInWhitespace; - if (charIndex === tokenEndIndex) { + while (charIndex === tokenEndIndex) { tokenIndex++; if (tokenIndex < tokensLength) { tokenType = tokens[tokenIndex].type; @@ -813,7 +813,11 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render let prevPartContentCnt = 0; let partAbsoluteOffset = 0; - sb.appendASCIIString(''); + if (containsRTL) { + sb.appendASCIIString(''); + } else { + sb.appendASCIIString(''); + } for (let partIndex = 0, tokensLen = parts.length; partIndex < tokensLen; partIndex++) { partAbsoluteOffset += prevPartContentCnt; @@ -890,9 +894,6 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render let partContentCnt = 0; - if (containsRTL) { - sb.appendASCIIString(' dir="ltr"'); - } sb.appendASCII(CharCode.GreaterThan); for (; charIndex < partEndIndex; charIndex++) { diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index d184fc82add..8556f11f3fd 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -402,8 +402,26 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this._updateConfigurationViewLineCount.schedule(); } + public getVisibleRangesPlusViewportAboveBelow(): Range[] { + const layoutInfo = this.configuration.options.get(EditorOption.layoutInfo); + const lineHeight = this.configuration.options.get(EditorOption.lineHeight); + const linesAround = Math.max(20, Math.round(layoutInfo.height / lineHeight)); + const partialData = this.viewLayout.getLinesViewportData(); + const startViewLineNumber = Math.max(1, partialData.completelyVisibleStartLineNumber - linesAround); + const endViewLineNumber = Math.min(this.getLineCount(), partialData.completelyVisibleEndLineNumber + linesAround); + + return this._toModelVisibleRanges(new Range( + startViewLineNumber, this.getLineMinColumn(startViewLineNumber), + endViewLineNumber, this.getLineMaxColumn(endViewLineNumber) + )); + } + public getVisibleRanges(): Range[] { const visibleViewRange = this.getCompletelyVisibleViewRange(); + return this._toModelVisibleRanges(visibleViewRange); + } + + private _toModelVisibleRanges(visibleViewRange: Range): Range[] { const visibleRange = this.coordinatesConverter.convertViewRangeToModelRange(visibleViewRange); const hiddenAreas = this.lines.getHiddenAreas(); diff --git a/src/vs/editor/contrib/bracketMatching/test/bracketMatching.test.ts b/src/vs/editor/contrib/bracketMatching/test/bracketMatching.test.ts index 486693706a5..ae17d2f9de1 100644 --- a/src/vs/editor/contrib/bracketMatching/test/bracketMatching.test.ts +++ b/src/vs/editor/contrib/bracketMatching/test/bracketMatching.test.ts @@ -34,7 +34,7 @@ suite('bracket matching', () => { let model = createTextModel('var x = (3 + (5-7)) + ((5+3)+5);', undefined, mode.getLanguageIdentifier()); withTestCodeEditor(null, { model: model }, (editor, cursor) => { - let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); + let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); // start on closing bracket editor.setPosition(new Position(1, 20)); @@ -66,7 +66,7 @@ suite('bracket matching', () => { let model = createTextModel('var x = (3 + (5-7)); y();', undefined, mode.getLanguageIdentifier()); withTestCodeEditor(null, { model: model }, (editor, cursor) => { - let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); + let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); // start position between brackets editor.setPosition(new Position(1, 16)); @@ -103,7 +103,7 @@ suite('bracket matching', () => { let model = createTextModel('var x = (3 + (5-7)); y();', undefined, mode.getLanguageIdentifier()); withTestCodeEditor(null, { model: model }, (editor, cursor) => { - let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); + let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); // start position in open brackets @@ -155,7 +155,7 @@ suite('bracket matching', () => { const model = createTextModel(text, undefined, mode.getLanguageIdentifier()); withTestCodeEditor(null, { model: model }, (editor, cursor) => { - const bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); + const bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); editor.setPosition(new Position(3, 5)); bracketMatchingController.jumpToBracket(); @@ -180,7 +180,7 @@ suite('bracket matching', () => { const model = createTextModel(text, undefined, mode.getLanguageIdentifier()); withTestCodeEditor(null, { model: model }, (editor, cursor) => { - const bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); + const bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); editor.setPosition(new Position(3, 5)); bracketMatchingController.selectToBracket(false); @@ -198,7 +198,7 @@ suite('bracket matching', () => { let model = createTextModel('{ } { } { }', undefined, mode.getLanguageIdentifier()); withTestCodeEditor(null, { model: model }, (editor, cursor) => { - let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); + let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); // cursors inside brackets become selections of the entire bracket contents editor.setSelections([ diff --git a/src/vs/editor/contrib/caretOperations/caretOperations.ts b/src/vs/editor/contrib/caretOperations/caretOperations.ts index 3b715b871e7..abd6ab267fd 100644 --- a/src/vs/editor/contrib/caretOperations/caretOperations.ts +++ b/src/vs/editor/contrib/caretOperations/caretOperations.ts @@ -42,8 +42,8 @@ class MoveCaretLeftAction extends MoveCaretAction { constructor() { super(true, { id: 'editor.action.moveCarretLeftAction', - label: nls.localize('caret.moveLeft', "Move Caret Left"), - alias: 'Move Caret Left', + label: nls.localize('caret.moveLeft', "Move Selected Text Left"), + alias: 'Move Selected Text Left', precondition: EditorContextKeys.writable }); } @@ -53,8 +53,8 @@ class MoveCaretRightAction extends MoveCaretAction { constructor() { super(false, { id: 'editor.action.moveCarretRightAction', - label: nls.localize('caret.moveRight', "Move Caret Right"), - alias: 'Move Caret Right', + label: nls.localize('caret.moveRight', "Move Selected Text Right"), + alias: 'Move Selected Text Right', precondition: EditorContextKeys.writable }); } diff --git a/src/vs/editor/contrib/caretOperations/moveCaretCommand.ts b/src/vs/editor/contrib/caretOperations/moveCaretCommand.ts index 0ce4feb6174..81db0fd6217 100644 --- a/src/vs/editor/contrib/caretOperations/moveCaretCommand.ts +++ b/src/vs/editor/contrib/caretOperations/moveCaretCommand.ts @@ -13,66 +13,43 @@ export class MoveCaretCommand implements ICommand { private readonly _selection: Selection; private readonly _isMovingLeft: boolean; - private _cutStartIndex: number; - private _cutEndIndex: number; - private _moved: boolean; - - private _selectionId: string | null; - constructor(selection: Selection, isMovingLeft: boolean) { this._selection = selection; this._isMovingLeft = isMovingLeft; - this._cutStartIndex = -1; - this._cutEndIndex = -1; - this._moved = false; - this._selectionId = null; } public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void { - let s = this._selection; - this._selectionId = builder.trackSelection(s); - if (s.startLineNumber !== s.endLineNumber) { + if (this._selection.startLineNumber !== this._selection.endLineNumber || this._selection.isEmpty()) { return; } - if (this._isMovingLeft && s.startColumn === 0) { - return; - } else if (!this._isMovingLeft && s.endColumn === model.getLineMaxColumn(s.startLineNumber)) { + const lineNumber = this._selection.startLineNumber; + const startColumn = this._selection.startColumn; + const endColumn = this._selection.endColumn; + if (this._isMovingLeft && startColumn === 1) { + return; + } + if (!this._isMovingLeft && endColumn === model.getLineMaxColumn(lineNumber)) { return; } - - let lineNumber = s.selectionStartLineNumber; - let lineContent = model.getLineContent(lineNumber); - - let left: string; - let middle: string; - let right: string; if (this._isMovingLeft) { - left = lineContent.substring(0, s.startColumn - 2); - middle = lineContent.substring(s.startColumn - 1, s.endColumn - 1); - right = lineContent.substring(s.startColumn - 2, s.startColumn - 1) + lineContent.substring(s.endColumn - 1); + const rangeBefore = new Range(lineNumber, startColumn - 1, lineNumber, startColumn); + const charBefore = model.getValueInRange(rangeBefore); + builder.addEditOperation(rangeBefore, null); + builder.addEditOperation(new Range(lineNumber, endColumn, lineNumber, endColumn), charBefore); } else { - left = lineContent.substring(0, s.startColumn - 1) + lineContent.substring(s.endColumn - 1, s.endColumn); - middle = lineContent.substring(s.startColumn - 1, s.endColumn - 1); - right = lineContent.substring(s.endColumn); + const rangeAfter = new Range(lineNumber, endColumn, lineNumber, endColumn + 1); + const charAfter = model.getValueInRange(rangeAfter); + builder.addEditOperation(rangeAfter, null); + builder.addEditOperation(new Range(lineNumber, startColumn, lineNumber, startColumn), charAfter); } - - let newLineContent = left + middle + right; - - builder.addEditOperation(new Range(lineNumber, 1, lineNumber, model.getLineMaxColumn(lineNumber)), null); - builder.addEditOperation(new Range(lineNumber, 1, lineNumber, 1), newLineContent); - - this._cutStartIndex = s.startColumn + (this._isMovingLeft ? -1 : 1); - this._cutEndIndex = this._cutStartIndex + s.endColumn - s.startColumn; - this._moved = true; } public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { - let result = helper.getTrackedSelection(this._selectionId!); - if (this._moved) { - result = result.setStartPosition(result.startLineNumber, this._cutStartIndex); - result = result.setEndPosition(result.startLineNumber, this._cutEndIndex); + if (this._isMovingLeft) { + return new Selection(this._selection.startLineNumber, this._selection.startColumn - 1, this._selection.endLineNumber, this._selection.endColumn - 1); + } else { + return new Selection(this._selection.startLineNumber, this._selection.startColumn + 1, this._selection.endLineNumber, this._selection.endColumn + 1); } - return result; } } diff --git a/src/vs/editor/contrib/clipboard/clipboard.ts b/src/vs/editor/contrib/clipboard/clipboard.ts index 321543523a0..afa05e3bf9f 100644 --- a/src/vs/editor/contrib/clipboard/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/clipboard.ts @@ -23,7 +23,7 @@ const CLIPBOARD_CONTEXT_MENU_GROUP = '9_cutcopypaste'; const supportsCut = (platform.isNative || document.queryCommandSupported('cut')); const supportsCopy = (platform.isNative || document.queryCommandSupported('copy')); // IE and Edge have trouble with setting html content in clipboard -const supportsCopyWithSyntaxHighlighting = (supportsCopy && !browser.isEdgeOrIE); +const supportsCopyWithSyntaxHighlighting = (supportsCopy && !browser.isEdge); // Chrome incorrectly returns true for document.queryCommandSupported('paste') // when the paste feature is available but the calling script has insufficient // privileges to actually perform the action diff --git a/src/vs/editor/contrib/codeAction/codeActionUi.ts b/src/vs/editor/contrib/codeAction/codeActionUi.ts index 7d2aeff74f3..6419e5e42f6 100644 --- a/src/vs/editor/contrib/codeAction/codeActionUi.ts +++ b/src/vs/editor/contrib/codeAction/codeActionUi.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; -import { find } from 'vs/base/common/arrays'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; @@ -123,7 +122,7 @@ export class CodeActionUi extends Disposable { if ((trigger.autoApply === CodeActionAutoApply.First && actions.validActions.length === 0) || (trigger.autoApply === CodeActionAutoApply.IfSingle && actions.allActions.length === 1) ) { - return find(actions.allActions, action => action.disabled); + return actions.allActions.find(action => action.disabled); } return undefined; diff --git a/src/vs/editor/contrib/codeAction/lightBulbWidget.ts b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts index 04712201151..de78ca4fd51 100644 --- a/src/vs/editor/contrib/codeAction/lightBulbWidget.ts +++ b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts @@ -15,7 +15,7 @@ import { CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; import * as nls from 'vs/nls'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; -import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { editorLightBulbForeground, editorLightBulbAutoFixForeground } from 'vs/platform/theme/common/colorRegistry'; import { Gesture } from 'vs/base/browser/touch'; import type { CodeActionTrigger } from 'vs/editor/contrib/codeAction/types'; @@ -222,7 +222,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { // Lightbulb Icon const editorLightBulbForegroundColor = theme.getColor(editorLightBulbForeground); diff --git a/src/vs/editor/contrib/codelens/codeLensCache.ts b/src/vs/editor/contrib/codelens/codeLensCache.ts index b83619ffef5..82f8b6d3a1f 100644 --- a/src/vs/editor/contrib/codelens/codeLensCache.ts +++ b/src/vs/editor/contrib/codelens/codeLensCache.ts @@ -7,7 +7,7 @@ import { ITextModel } from 'vs/editor/common/model'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { CodeLensModel } from 'vs/editor/contrib/codelens/codelens'; -import { LRUCache, values } from 'vs/base/common/map'; +import { LRUCache } from 'vs/base/common/map'; import { CodeLensProvider, CodeLensList, CodeLens } from 'vs/editor/common/modes'; import { IStorageService, StorageScope, WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { Range } from 'vs/editor/common/core/range'; @@ -103,7 +103,7 @@ export class CodeLensCache implements ICodeLensCache { } data[key] = { lineCount: value.lineCount, - lines: values(lines) + lines: [...lines.values()] }; }); return JSON.stringify(data); diff --git a/src/vs/editor/contrib/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index 2b4865134c3..2f2721cf150 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -8,10 +8,10 @@ import { onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/err import { toDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { StableEditorScrollState } from 'vs/editor/browser/core/editorState'; import { ICodeEditor, MouseTargetType, IViewZoneChangeAccessor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { registerEditorContribution, ServicesAccessor, registerEditorAction, EditorAction } from 'vs/editor/browser/editorExtensions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { IModelDecorationsChangeAccessor } from 'vs/editor/common/model'; -import { CodeLensProviderRegistry, CodeLens } from 'vs/editor/common/modes'; +import { CodeLensProviderRegistry, CodeLens, Command } from 'vs/editor/common/modes'; import { CodeLensModel, getCodeLensData, CodeLensItem } from 'vs/editor/contrib/codelens/codelens'; import { CodeLensWidget, CodeLensHelper } from 'vs/editor/contrib/codelens/codelensWidget'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -20,6 +20,9 @@ import { ICodeLensCache } from 'vs/editor/contrib/codelens/codeLensCache'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import * as dom from 'vs/base/browser/dom'; import { hash } from 'vs/base/common/hash'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { localize } from 'vs/nls'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; export class CodeLensContribution implements IEditorContribution { @@ -402,6 +405,70 @@ export class CodeLensContribution implements IEditorContribution { } }); } + + getLenses(): readonly CodeLensWidget[] { + return this._lenses; + } } registerEditorContribution(CodeLensContribution.ID, CodeLensContribution); + +registerEditorAction(class ShowLensesInCurrentLine extends EditorAction { + + constructor() { + super({ + id: 'codelens.showLensesInCurrentLine', + precondition: EditorContextKeys.hasCodeLensProvider, + label: localize('showLensOnLine', "Show Code Lens Commands For Current Line"), + alias: 'Show Code Lens Commands For Current Line', + }); + } + + async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { + + if (!editor.hasModel()) { + return; + } + + const quickInputService = accessor.get(IQuickInputService); + const commandService = accessor.get(ICommandService); + const notificationService = accessor.get(INotificationService); + + const lineNumber = editor.getSelection().positionLineNumber; + const codelensController = editor.getContribution(CodeLensContribution.ID); + const items: { label: string, command: Command }[] = []; + + for (let lens of codelensController.getLenses()) { + if (lens.getLineNumber() === lineNumber) { + for (let item of lens.getItems()) { + const { command } = item.symbol; + if (command) { + items.push({ + label: command.title, + command: command + }); + } + } + } + } + + if (items.length === 0) { + // We dont want an empty picker + return; + } + + const item = await quickInputService.pick(items, { canPickMany: false }); + if (!item) { + // Nothing picked + return; + } + + try { + await commandService.executeCommand(item.command.id, ...(item.command.arguments || [])); + } catch (err) { + notificationService.error(err); + } + } +}); + + diff --git a/src/vs/editor/contrib/codelens/codelensWidget.ts b/src/vs/editor/contrib/codelens/codelensWidget.ts index 622e7785aaf..273fcfb7ea1 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/codelensWidget.ts @@ -336,6 +336,10 @@ export class CodeLensWidget { } } } + + getItems(): CodeLensItem[] { + return this._data; + } } registerThemingParticipant((theme, collector) => { diff --git a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts index 68de080e7d6..bfc533c38a1 100644 --- a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts +++ b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts @@ -34,7 +34,7 @@ export class ColorPickerHeader extends Disposable { const colorBox = dom.append(this.domNode, $('.original-color')); colorBox.style.backgroundColor = Color.Format.CSS.format(this.model.originalColor) || ''; - this.backgroundColor = themeService.getTheme().getColor(editorHoverBackground) || Color.white; + this.backgroundColor = themeService.getColorTheme().getColor(editorHoverBackground) || Color.white; this._register(registerThemingParticipant((theme, collector) => { this.backgroundColor = theme.getColor(editorHoverBackground) || Color.white; })); diff --git a/src/vs/editor/contrib/documentSymbols/media/outlineTree.css b/src/vs/editor/contrib/documentSymbols/media/outlineTree.css index e7953bf8ec6..fb53f7e8524 100644 --- a/src/vs/editor/contrib/documentSymbols/media/outlineTree.css +++ b/src/vs/editor/contrib/documentSymbols/media/outlineTree.css @@ -20,10 +20,6 @@ color: var(--outline-element-color); } -.monaco-tree .monaco-tree-row.focused .outline-element .outline-element-detail { - visibility: inherit; -} - .monaco-list .outline-element .outline-element-decoration { opacity: 0.75; font-size: 90%; diff --git a/src/vs/editor/contrib/documentSymbols/outlineTree.ts b/src/vs/editor/contrib/documentSymbols/outlineTree.ts index 03068119208..4c132058412 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineTree.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineTree.ts @@ -19,7 +19,7 @@ import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { OutlineConfigKeys } from 'vs/editor/contrib/documentSymbols/outline'; import { MarkerSeverity } from 'vs/platform/markers/common/markers'; -import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { registerColor, listErrorForeground, listWarningForeground, foreground } from 'vs/platform/theme/common/colorRegistry'; import { IdleValue } from 'vs/base/common/async'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; @@ -150,7 +150,7 @@ export class OutlineElementRenderer implements ITreeRenderer { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const symbolIconArrayColor = theme.getColor(SYMBOL_ICON_ARRAY_FOREGROUND); if (symbolIconArrayColor) { diff --git a/src/vs/editor/contrib/find/findDecorations.ts b/src/vs/editor/contrib/find/findDecorations.ts index ee93bccc3cf..5d4422e3bef 100644 --- a/src/vs/editor/contrib/find/findDecorations.ts +++ b/src/vs/editor/contrib/find/findDecorations.ts @@ -261,7 +261,7 @@ export class FindDecorations implements IDisposable { return result; } - private static readonly _CURRENT_FIND_MATCH_DECORATION = ModelDecorationOptions.register({ + public static readonly _CURRENT_FIND_MATCH_DECORATION = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, zIndex: 13, className: 'currentFindMatch', @@ -276,7 +276,7 @@ export class FindDecorations implements IDisposable { } }); - private static readonly _FIND_MATCH_DECORATION = ModelDecorationOptions.register({ + public static readonly _FIND_MATCH_DECORATION = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, className: 'findMatch', showIfCollapsed: true, @@ -290,7 +290,7 @@ export class FindDecorations implements IDisposable { } }); - private static readonly _FIND_MATCH_NO_OVERVIEW_DECORATION = ModelDecorationOptions.register({ + public static readonly _FIND_MATCH_NO_OVERVIEW_DECORATION = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, className: 'findMatch', showIfCollapsed: true diff --git a/src/vs/editor/contrib/find/findOptionsWidget.ts b/src/vs/editor/contrib/find/findOptionsWidget.ts index b3d9b69e1f5..e56b512e893 100644 --- a/src/vs/editor/contrib/find/findOptionsWidget.ts +++ b/src/vs/editor/contrib/find/findOptionsWidget.ts @@ -12,7 +12,7 @@ import { FIND_IDS } from 'vs/editor/contrib/find/findModel'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { contrastBorder, editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, widgetShadow, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry'; -import { ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; export class FindOptionsWidget extends Widget implements IOverlayWidget { @@ -46,8 +46,8 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { this._domNode.setAttribute('role', 'presentation'); this._domNode.setAttribute('aria-hidden', 'true'); - const inputActiveOptionBorderColor = themeService.getTheme().getColor(inputActiveOptionBorder); - const inputActiveOptionBackgroundColor = themeService.getTheme().getColor(inputActiveOptionBackground); + const inputActiveOptionBorderColor = themeService.getColorTheme().getColor(inputActiveOptionBorder); + const inputActiveOptionBackgroundColor = themeService.getColorTheme().getColor(inputActiveOptionBackground); this.caseSensitive = this._register(new CaseSensitiveCheckbox({ appendTitle: this._keybindingLabelFor(FIND_IDS.ToggleCaseSensitiveCommand), @@ -112,8 +112,8 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { this._register(dom.addDisposableNonBubblingMouseOutListener(this._domNode, (e) => this._onMouseOut())); this._register(dom.addDisposableListener(this._domNode, 'mouseover', (e) => this._onMouseOver())); - this._applyTheme(themeService.getTheme()); - this._register(themeService.onThemeChange(this._applyTheme.bind(this))); + this._applyTheme(themeService.getColorTheme()); + this._register(themeService.onDidColorThemeChange(this._applyTheme.bind(this))); } private _keybindingLabelFor(actionId: string): string { @@ -182,7 +182,7 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { this._domNode.style.display = 'none'; } - private _applyTheme(theme: ITheme) { + private _applyTheme(theme: IColorTheme) { let inputStyles = { inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder), inputActiveOptionBackground: theme.getColor(inputActiveOptionBackground) diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index 440c78fef72..fd03c7be5f1 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -31,7 +31,7 @@ import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contri import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { contrastBorder, editorFindMatch, editorFindMatchBorder, editorFindMatchHighlight, editorFindMatchHighlightBorder, editorFindRangeHighlight, editorFindRangeHighlightBorder, editorWidgetBackground, editorWidgetBorder, editorWidgetResizeBorder, errorForeground, inputActiveOptionBorder, inputActiveOptionBackground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow, editorWidgetForeground, focusBorder } from 'vs/platform/theme/common/colorRegistry'; -import { ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/browser/contextScopedHistoryWidget'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; @@ -244,8 +244,8 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas this._viewZone = new FindWidgetViewZone(0); // Put it before the first line then users can scroll beyond the first line. } - this._applyTheme(themeService.getTheme()); - this._register(themeService.onThemeChange(this._applyTheme.bind(this))); + this._applyTheme(themeService.getColorTheme()); + this._register(themeService.onDidColorThemeChange(this._applyTheme.bind(this))); this._register(this._codeEditor.onDidChangeModel(() => { if (!this._isVisible) { @@ -643,7 +643,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas }); } - private _applyTheme(theme: ITheme) { + private _applyTheme(theme: IColorTheme) { let inputStyles: IFindInputStyles = { inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder), inputActiveOptionBackground: theme.getColor(inputActiveOptionBackground), @@ -924,7 +924,8 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas return null; } try { - new RegExp(value); + // use `g` and `u` which are also used by the TextModel search + new RegExp(value, 'gu'); return null; } catch (e) { return { content: e.message }; diff --git a/src/vs/editor/contrib/find/images/chevron-next-dark.svg b/src/vs/editor/contrib/find/images/chevron-next-dark.svg deleted file mode 100644 index dbe70d742de..00000000000 --- a/src/vs/editor/contrib/find/images/chevron-next-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/find/images/chevron-next-light.svg b/src/vs/editor/contrib/find/images/chevron-next-light.svg deleted file mode 100644 index ec824f41cc0..00000000000 --- a/src/vs/editor/contrib/find/images/chevron-next-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/find/images/chevron-previous-dark.svg b/src/vs/editor/contrib/find/images/chevron-previous-dark.svg deleted file mode 100644 index 5db4f79da85..00000000000 --- a/src/vs/editor/contrib/find/images/chevron-previous-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/find/images/chevron-previous-light.svg b/src/vs/editor/contrib/find/images/chevron-previous-light.svg deleted file mode 100644 index aac3a5020cd..00000000000 --- a/src/vs/editor/contrib/find/images/chevron-previous-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/find/images/close-dark.svg b/src/vs/editor/contrib/find/images/close-dark.svg deleted file mode 100644 index 75644595d19..00000000000 --- a/src/vs/editor/contrib/find/images/close-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/find/images/close-light.svg b/src/vs/editor/contrib/find/images/close-light.svg deleted file mode 100644 index cf5f28ca35c..00000000000 --- a/src/vs/editor/contrib/find/images/close-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/find/images/find-selection-dark.svg b/src/vs/editor/contrib/find/images/find-selection-dark.svg deleted file mode 100644 index 6fc07d81a55..00000000000 --- a/src/vs/editor/contrib/find/images/find-selection-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/find/images/find-selection-light.svg b/src/vs/editor/contrib/find/images/find-selection-light.svg deleted file mode 100644 index 3608b15d29e..00000000000 --- a/src/vs/editor/contrib/find/images/find-selection-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/find/images/replace-all-dark.svg b/src/vs/editor/contrib/find/images/replace-all-dark.svg deleted file mode 100644 index 07bd41a789f..00000000000 --- a/src/vs/editor/contrib/find/images/replace-all-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/find/images/replace-all-light.svg b/src/vs/editor/contrib/find/images/replace-all-light.svg deleted file mode 100644 index cd3974fae7e..00000000000 --- a/src/vs/editor/contrib/find/images/replace-all-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/find/images/replace-dark.svg b/src/vs/editor/contrib/find/images/replace-dark.svg deleted file mode 100644 index 5882b22c589..00000000000 --- a/src/vs/editor/contrib/find/images/replace-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/find/images/replace-light.svg b/src/vs/editor/contrib/find/images/replace-light.svg deleted file mode 100644 index 220f2aba40c..00000000000 --- a/src/vs/editor/contrib/find/images/replace-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/find/test/findController.test.ts b/src/vs/editor/contrib/find/test/findController.test.ts index 3065cb082f9..cf65295701a 100644 --- a/src/vs/editor/contrib/find/test/findController.test.ts +++ b/src/vs/editor/contrib/find/test/findController.test.ts @@ -89,7 +89,7 @@ suite('FindController', () => { assert.ok(true); return; } - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let startFindAction = new StartFindAction(); // I select ABC on the first line editor.setSelection(new Selection(1, 1, 1, 4)); @@ -115,7 +115,7 @@ suite('FindController', () => { return; } - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let findState = findController.getState(); let nextMatchFindAction = new NextMatchFindAction(); @@ -141,7 +141,7 @@ suite('FindController', () => { return; } - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let findState = findController.getState(); findState.change({ searchString: 'ABC' }, true); @@ -161,7 +161,7 @@ suite('FindController', () => { ], { serviceCollection: serviceCollection }, (editor, cursor) => { clipboardState = ''; // The cursor is at the very top, of the file, at the first ABC - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let findState = findController.getState(); let startFindAction = new StartFindAction(); let nextMatchFindAction = new NextMatchFindAction(); @@ -215,7 +215,7 @@ suite('FindController', () => { 'import nls = require(\'vs/nls\');' ], { serviceCollection: serviceCollection }, (editor, cursor) => { clipboardState = ''; - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let nextMatchFindAction = new NextMatchFindAction(); editor.setPosition({ @@ -240,7 +240,7 @@ suite('FindController', () => { 'var z = (3 * 5)', ], { serviceCollection: serviceCollection }, (editor, cursor) => { clipboardState = ''; - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let startFindAction = new StartFindAction(); let nextMatchFindAction = new NextMatchFindAction(); @@ -264,7 +264,7 @@ suite('FindController', () => { 'test', ], { serviceCollection: serviceCollection }, (editor, cursor) => { let testRegexString = 'tes.'; - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let nextMatchFindAction = new NextMatchFindAction(); let startFindReplaceAction = new StartFindReplaceAction(); @@ -294,7 +294,7 @@ suite('FindController', () => { 'var z = (3 * 5)', ], { serviceCollection: serviceCollection }, (editor, cursor) => { clipboardState = ''; - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); findController.start({ forceRevealReplace: false, seedSearchStringFromSelection: false, @@ -322,7 +322,7 @@ suite('FindController', () => { 'HRESULT OnAmbientPropertyChange(DISPID dispid);' ], { serviceCollection: serviceCollection }, (editor, cursor) => { clipboardState = ''; - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let startFindAction = new StartFindAction(); startFindAction.run(null, editor); @@ -349,7 +349,7 @@ suite('FindController', () => { 'line3' ], { serviceCollection: serviceCollection }, (editor, cursor) => { clipboardState = ''; - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let startFindAction = new StartFindAction(); startFindAction.run(null, editor); @@ -376,7 +376,7 @@ suite('FindController', () => { '([funny]' ], { serviceCollection: serviceCollection }, (editor, cursor) => { clipboardState = ''; - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let nextSelectionMatchFindAction = new NextSelectionMatchFindAction(); // toggle regex @@ -403,7 +403,7 @@ suite('FindController', () => { '([funny]' ], { serviceCollection: serviceCollection }, (editor, cursor) => { clipboardState = ''; - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let startFindAction = new StartFindAction(); let nextSelectionMatchFindAction = new NextSelectionMatchFindAction(); @@ -454,7 +454,7 @@ suite('FindController query options persistence', () => { ], { serviceCollection: serviceCollection }, (editor, cursor) => { queryState = { 'editor.isRegex': false, 'editor.matchCase': true, 'editor.wholeWord': false }; // The cursor is at the very top, of the file, at the first ABC - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let findState = findController.getState(); let startFindAction = new StartFindAction(); @@ -481,7 +481,7 @@ suite('FindController query options persistence', () => { ], { serviceCollection: serviceCollection }, (editor, cursor) => { queryState = { 'editor.isRegex': false, 'editor.matchCase': false, 'editor.wholeWord': true }; // The cursor is at the very top, of the file, at the first ABC - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let findState = findController.getState(); let startFindAction = new StartFindAction(); @@ -506,7 +506,7 @@ suite('FindController query options persistence', () => { ], { serviceCollection: serviceCollection }, (editor, cursor) => { queryState = { 'editor.isRegex': false, 'editor.matchCase': false, 'editor.wholeWord': true }; // The cursor is at the very top, of the file, at the first ABC - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); findController.toggleRegex(); assert.equal(queryState['editor.isRegex'], true); @@ -522,7 +522,7 @@ suite('FindController query options persistence', () => { ], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, (editor, cursor) => { // clipboardState = ''; editor.setSelection(new Range(1, 1, 2, 1)); - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); findController.start({ forceRevealReplace: false, @@ -545,7 +545,7 @@ suite('FindController query options persistence', () => { ], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, (editor, cursor) => { // clipboardState = ''; editor.setSelection(new Range(1, 2, 1, 2)); - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); findController.start({ forceRevealReplace: false, @@ -568,7 +568,7 @@ suite('FindController query options persistence', () => { ], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, (editor, cursor) => { // clipboardState = ''; editor.setSelection(new Range(1, 2, 1, 3)); - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); findController.start({ forceRevealReplace: false, @@ -592,7 +592,7 @@ suite('FindController query options persistence', () => { ], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'multiline', globalFindClipboard: false } }, (editor, cursor) => { // clipboardState = ''; editor.setSelection(new Range(1, 6, 2, 1)); - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); findController.start({ forceRevealReplace: false, diff --git a/src/vs/editor/contrib/folding/foldingDecorations.ts b/src/vs/editor/contrib/folding/foldingDecorations.ts index 0ec51779f72..67a7ddd6b88 100644 --- a/src/vs/editor/contrib/folding/foldingDecorations.ts +++ b/src/vs/editor/contrib/folding/foldingDecorations.ts @@ -13,7 +13,8 @@ export class FoldingDecorationProvider implements IDecorationProvider { private static readonly COLLAPSED_VISUAL_DECORATION = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, afterContentClassName: 'inline-folded', - linesDecorationsClassName: 'codicon codicon-chevron-right' + isWholeLine: true, + firstLineDecorationClassName: 'codicon codicon-chevron-right' }); private static readonly COLLAPSED_HIGHLIGHTED_VISUAL_DECORATION = ModelDecorationOptions.register({ @@ -21,17 +22,23 @@ export class FoldingDecorationProvider implements IDecorationProvider { afterContentClassName: 'inline-folded', className: 'folded-background', isWholeLine: true, - linesDecorationsClassName: 'codicon codicon-chevron-right' + firstLineDecorationClassName: 'codicon codicon-chevron-right' }); private static readonly EXPANDED_AUTO_HIDE_VISUAL_DECORATION = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - linesDecorationsClassName: 'codicon codicon-chevron-down' + isWholeLine: true, + firstLineDecorationClassName: 'codicon codicon-chevron-down' }); private static readonly EXPANDED_VISUAL_DECORATION = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - linesDecorationsClassName: 'codicon codicon-chevron-down alwaysShowFoldIcons' + isWholeLine: true, + firstLineDecorationClassName: 'codicon codicon-chevron-down alwaysShowFoldIcons' + }); + + private static readonly HIDDEN_RANGE_DECORATION = ModelDecorationOptions.register({ + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges }); public autoHideFoldingControls: boolean = true; @@ -41,7 +48,10 @@ export class FoldingDecorationProvider implements IDecorationProvider { constructor(private readonly editor: ICodeEditor) { } - getDecorationOption(isCollapsed: boolean): ModelDecorationOptions { + getDecorationOption(isCollapsed: boolean, isHidden: boolean): ModelDecorationOptions { + if (isHidden) { + return FoldingDecorationProvider.HIDDEN_RANGE_DECORATION; + } if (isCollapsed) { return this.showFoldingHighlights ? FoldingDecorationProvider.COLLAPSED_HIGHLIGHTED_VISUAL_DECORATION : FoldingDecorationProvider.COLLAPSED_VISUAL_DECORATION; } else if (this.autoHideFoldingControls) { diff --git a/src/vs/editor/contrib/folding/foldingModel.ts b/src/vs/editor/contrib/folding/foldingModel.ts index 917217b929b..a9b7af52657 100644 --- a/src/vs/editor/contrib/folding/foldingModel.ts +++ b/src/vs/editor/contrib/folding/foldingModel.ts @@ -8,7 +8,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { FoldingRegions, ILineRange, FoldingRegion } from './foldingRanges'; export interface IDecorationProvider { - getDecorationOption(isCollapsed: boolean): IModelDecorationOptions; + getDecorationOption(isCollapsed: boolean, isHidden: boolean): IModelDecorationOptions; deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[]; changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T | null; } @@ -34,6 +34,7 @@ export class FoldingModel { public get regions(): FoldingRegions { return this._regions; } public get textModel() { return this._textModel; } public get isInitialized() { return this._isInitialized; } + public get decorationProvider() { return this._decorationProvider; } constructor(textModel: ITextModel, decorationProvider: IDecorationProvider) { this._textModel = textModel; @@ -43,24 +44,47 @@ export class FoldingModel { this._isInitialized = false; } - public toggleCollapseState(regions: FoldingRegion[]) { - if (!regions.length) { + public toggleCollapseState(toggledRegions: FoldingRegion[]) { + if (!toggledRegions.length) { return; } - let processed: { [key: string]: boolean | undefined } = {}; + toggledRegions = toggledRegions.sort((r1, r2) => r1.regionIndex - r2.regionIndex); + + const processed: { [key: string]: boolean | undefined } = {}; this._decorationProvider.changeDecorations(accessor => { - for (let region of regions) { + let k = 0; // index from [0 ... this.regions.length] + let dirtyRegionEndLine = -1; // end of the range where decorations need to be updated + let lastHiddenLine = -1; // the end of the last hidden lines + const updateDecorationsUntil = (index: number) => { + while (k < index) { + const endLineNumber = this._regions.getEndLineNumber(k); + const isCollapsed = this._regions.isCollapsed(k); + if (endLineNumber <= dirtyRegionEndLine) { + accessor.changeDecorationOptions(this._editorDecorationIds[k], this._decorationProvider.getDecorationOption(isCollapsed, endLineNumber <= lastHiddenLine)); + } + if (isCollapsed && endLineNumber > lastHiddenLine) { + lastHiddenLine = endLineNumber; + } + k++; + } + }; + for (let region of toggledRegions) { let index = region.regionIndex; let editorDecorationId = this._editorDecorationIds[index]; if (editorDecorationId && !processed[editorDecorationId]) { processed[editorDecorationId] = true; + + updateDecorationsUntil(index); // update all decorations up to current index using the old dirtyRegionEndLine + let newCollapseState = !this._regions.isCollapsed(index); this._regions.setCollapsed(index, newCollapseState); - accessor.changeDecorationOptions(editorDecorationId, this._decorationProvider.getDecorationOption(newCollapseState)); + + dirtyRegionEndLine = Math.max(dirtyRegionEndLine, this._regions.getEndLineNumber(index)); } } + updateDecorationsUntil(this._regions.length); }); - this._updateEventEmitter.fire({ model: this, collapseStateChanged: regions }); + this._updateEventEmitter.fire({ model: this, collapseStateChanged: toggledRegions }); } public update(newRegions: FoldingRegions, blockedLineNumers: number[] = []): void { @@ -75,20 +99,27 @@ export class FoldingModel { return false; }; + let lastHiddenLine = -1; + let initRange = (index: number, isCollapsed: boolean) => { - let startLineNumber = newRegions.getStartLineNumber(index); - if (isCollapsed && isBlocked(startLineNumber, newRegions.getEndLineNumber(index))) { + const startLineNumber = newRegions.getStartLineNumber(index); + const endLineNumber = newRegions.getEndLineNumber(index); + if (isCollapsed && isBlocked(startLineNumber, endLineNumber)) { isCollapsed = false; } newRegions.setCollapsed(index, isCollapsed); - let maxColumn = this._textModel.getLineMaxColumn(startLineNumber); - let decorationRange = { + + const maxColumn = this._textModel.getLineMaxColumn(startLineNumber); + const decorationRange = { startLineNumber: startLineNumber, startColumn: maxColumn, endLineNumber: startLineNumber, endColumn: maxColumn }; - newEditorDecorations.push({ range: decorationRange, options: this._decorationProvider.getDecorationOption(isCollapsed) }); + newEditorDecorations.push({ range: decorationRange, options: this._decorationProvider.getDecorationOption(isCollapsed, endLineNumber <= lastHiddenLine) }); + if (isCollapsed && endLineNumber > lastHiddenLine) { + lastHiddenLine = endLineNumber; + } }; let i = 0; let nextCollapsed = () => { @@ -318,7 +349,7 @@ export function setCollapseStateLevelsUp(foldingModel: FoldingModel, doCollapse: export function setCollapseStateUp(foldingModel: FoldingModel, doCollapse: boolean, lineNumbers: number[]): void { let toToggle: FoldingRegion[] = []; for (let lineNumber of lineNumbers) { - let regions = foldingModel.getAllRegionsAtLine(lineNumber, (region, ) => region.isCollapsed !== doCollapse); + let regions = foldingModel.getAllRegionsAtLine(lineNumber, (region,) => region.isCollapsed !== doCollapse); if (regions.length > 0) { toToggle.push(regions[0]); } diff --git a/src/vs/editor/contrib/folding/test/foldingModel.test.ts b/src/vs/editor/contrib/folding/test/foldingModel.test.ts index 97a8a209941..a646fa78bd6 100644 --- a/src/vs/editor/contrib/folding/test/foldingModel.test.ts +++ b/src/vs/editor/contrib/folding/test/foldingModel.test.ts @@ -21,9 +21,24 @@ interface ExpectedRegion { isCollapsed: boolean; } +interface ExpectedDecoration { + line: number; + type: 'hidden' | 'collapsed' | 'expanded'; +} + export class TestDecorationProvider { - private testDecorator = ModelDecorationOptions.register({ + private static readonly collapsedDecoration = ModelDecorationOptions.register({ + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + linesDecorationsClassName: 'folding' + }); + + private static readonly expandedDecoration = ModelDecorationOptions.register({ + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + linesDecorationsClassName: 'folding' + }); + + private static readonly hiddenDecoration = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, linesDecorationsClassName: 'folding' }); @@ -31,8 +46,14 @@ export class TestDecorationProvider { constructor(private model: ITextModel) { } - getDecorationOption(isCollapsed: boolean): ModelDecorationOptions { - return this.testDecorator; + getDecorationOption(isCollapsed: boolean, isHidden: boolean): ModelDecorationOptions { + if (isHidden) { + return TestDecorationProvider.hiddenDecoration; + } + if (isCollapsed) { + return TestDecorationProvider.collapsedDecoration; + } + return TestDecorationProvider.expandedDecoration; } deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[] { @@ -42,6 +63,21 @@ export class TestDecorationProvider { changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): (T | null) { return this.model.changeDecorations(callback); } + + getDecorations(): ExpectedDecoration[] { + const decorations = this.model.getAllDecorations(); + const res: ExpectedDecoration[] = []; + for (let decoration of decorations) { + if (decoration.options === TestDecorationProvider.hiddenDecoration) { + res.push({ line: decoration.range.startLineNumber, type: 'hidden' }); + } else if (decoration.options === TestDecorationProvider.collapsedDecoration) { + res.push({ line: decoration.range.startLineNumber, type: 'collapsed' }); + } else if (decoration.options === TestDecorationProvider.expandedDecoration) { + res.push({ line: decoration.range.startLineNumber, type: 'expanded' }); + } + } + return res; + } } suite('Folding Model', () => { @@ -49,6 +85,10 @@ suite('Folding Model', () => { return { startLineNumber, endLineNumber, isCollapsed }; } + function d(line: number, type: 'hidden' | 'collapsed' | 'expanded'): ExpectedDecoration { + return { line, type }; + } + function assertRegion(actual: FoldingRegion | null, expected: ExpectedRegion | null, message?: string) { assert.equal(!!actual, !!expected, message); if (actual && expected) { @@ -78,6 +118,11 @@ suite('Folding Model', () => { assert.deepEqual(actualRanges, expectedRegions, message); } + function assertDecorations(foldingModel: FoldingModel, expectedDecoration: ExpectedDecoration[], message?: string) { + const decorationProvider = foldingModel.decorationProvider as TestDecorationProvider; + assert.deepEqual(decorationProvider.getDecorations(), expectedDecoration, message); + } + function assertRegions(actual: FoldingRegion[], expectedRegions: ExpectedRegion[], message?: string) { assert.deepEqual(actual.map(r => ({ startLineNumber: r.startLineNumber, endLineNumber: r.endLineNumber, isCollapsed: r.isCollapsed })), expectedRegions, message); } @@ -672,4 +717,65 @@ suite('Folding Model', () => { }); + test('folding decoration', () => { + let lines = [ + /* 1*/ 'class A {', + /* 2*/ ' void foo() {', + /* 3*/ ' if (true) {', + /* 4*/ ' hoo();', + /* 5*/ ' }', + /* 6*/ ' }', + /* 7*/ '}']; + + let textModel = createTextModel(lines.join('\n')); + try { + let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); + + let ranges = computeRanges(textModel, false, undefined); + foldingModel.update(ranges); + + let r1 = r(1, 6, false); + let r2 = r(2, 5, false); + let r3 = r(3, 4, false); + + assertRanges(foldingModel, [r1, r2, r3]); + assertDecorations(foldingModel, [d(1, 'expanded'), d(2, 'expanded'), d(3, 'expanded')]); + + foldingModel.toggleCollapseState([foldingModel.getRegionAtLine(2)!]); + + assertRanges(foldingModel, [r1, r(2, 5, true), r3]); + assertDecorations(foldingModel, [d(1, 'expanded'), d(2, 'collapsed'), d(3, 'hidden')]); + + foldingModel.update(ranges); + + assertRanges(foldingModel, [r1, r(2, 5, true), r3]); + assertDecorations(foldingModel, [d(1, 'expanded'), d(2, 'collapsed'), d(3, 'hidden')]); + + foldingModel.toggleCollapseState([foldingModel.getRegionAtLine(1)!]); + + assertRanges(foldingModel, [r(1, 6, true), r(2, 5, true), r3]); + assertDecorations(foldingModel, [d(1, 'collapsed'), d(2, 'hidden'), d(3, 'hidden')]); + + foldingModel.update(ranges); + + assertRanges(foldingModel, [r(1, 6, true), r(2, 5, true), r3]); + assertDecorations(foldingModel, [d(1, 'collapsed'), d(2, 'hidden'), d(3, 'hidden')]); + + foldingModel.toggleCollapseState([foldingModel.getRegionAtLine(1)!, foldingModel.getRegionAtLine(3)!]); + + assertRanges(foldingModel, [r1, r(2, 5, true), r(3, 4, true)]); + assertDecorations(foldingModel, [d(1, 'expanded'), d(2, 'collapsed'), d(3, 'hidden')]); + + foldingModel.update(ranges); + + assertRanges(foldingModel, [r1, r(2, 5, true), r(3, 4, true)]); + assertDecorations(foldingModel, [d(1, 'expanded'), d(2, 'collapsed'), d(3, 'hidden')]); + + textModel.dispose(); + } finally { + textModel.dispose(); + } + + }); + }); diff --git a/src/vs/editor/contrib/format/format.ts b/src/vs/editor/contrib/format/format.ts index b18b6d63ad8..14dac4565c1 100644 --- a/src/vs/editor/contrib/format/format.ts +++ b/src/vs/editor/contrib/format/format.ts @@ -28,6 +28,7 @@ import { LinkedList } from 'vs/base/common/linkedList'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { assertType } from 'vs/base/common/types'; import { IProgress } from 'vs/platform/progress/common/progress'; +import { Iterable } from 'vs/base/common/iterator'; export function alertFormattingEdits(edits: ISingleEditOperation[]): void { @@ -111,7 +112,7 @@ export abstract class FormattingConflicts { if (formatter.length === 0) { return undefined; } - const { value: selector } = FormattingConflicts._selectors.iterator().next(); + const selector = Iterable.first(FormattingConflicts._selectors); if (selector) { return await selector(formatter, document, mode); } diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts index 6f33c54a4ab..f1f53191d57 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts @@ -12,7 +12,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { registerColor, oneOf, textLinkForeground, editorErrorForeground, editorErrorBorder, editorWarningForeground, editorWarningBorder, editorInfoForeground, editorInfoBorder } from 'vs/platform/theme/common/colorRegistry'; -import { IThemeService, ITheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; @@ -246,13 +246,13 @@ export class MarkerNavigationWidget extends PeekViewWidget { this._severity = MarkerSeverity.Warning; this._backgroundColor = Color.white; - this._applyTheme(_themeService.getTheme()); - this._callOnDispose.add(_themeService.onThemeChange(this._applyTheme.bind(this))); + this._applyTheme(_themeService.getColorTheme()); + this._callOnDispose.add(_themeService.onDidColorThemeChange(this._applyTheme.bind(this))); this.create(); } - private _applyTheme(theme: ITheme) { + private _applyTheme(theme: IColorTheme) { this._backgroundColor = theme.getColor(editorMarkerNavigationBackground); let colorId = editorMarkerNavigationError; if (this._severity === MarkerSeverity.Warning) { @@ -327,7 +327,7 @@ export class MarkerNavigationWidget extends PeekViewWidget { // update frame color (only applied on 'show') this._severity = marker.severity; - this._applyTheme(this._themeService.getTheme()); + this._applyTheme(this._themeService.getColorTheme()); // show let range = Range.lift(marker); diff --git a/src/vs/editor/contrib/quickOpen/quickOpen.ts b/src/vs/editor/contrib/gotoSymbol/documentSymbols.ts similarity index 99% rename from src/vs/editor/contrib/quickOpen/quickOpen.ts rename to src/vs/editor/contrib/gotoSymbol/documentSymbols.ts index 69c379413e2..d062af18789 100644 --- a/src/vs/editor/contrib/quickOpen/quickOpen.ts +++ b/src/vs/editor/contrib/gotoSymbol/documentSymbols.ts @@ -62,7 +62,6 @@ function flatten(bucket: DocumentSymbol[], entries: DocumentSymbol[], overrideCo } } - CommandsRegistry.registerCommand('_executeDocumentSymbolProvider', async function (accessor, ...args) { const [resource] = args; assertType(URI.isUri(resource)); diff --git a/src/vs/editor/contrib/gotoSymbol/link/clickLinkGesture.ts b/src/vs/editor/contrib/gotoSymbol/link/clickLinkGesture.ts index 003ed988f84..19f2de625f8 100644 --- a/src/vs/editor/contrib/gotoSymbol/link/clickLinkGesture.ts +++ b/src/vs/editor/contrib/gotoSymbol/link/clickLinkGesture.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { KeyCode } from 'vs/base/common/keyCodes'; -import * as browser from 'vs/base/browser/browser'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ICodeEditor, IEditorMouseEvent, IMouseTarget } from 'vs/editor/browser/editorBrowser'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -31,7 +30,7 @@ export class ClickLinkMouseEvent { this.target = source.target; this.hasTriggerModifier = hasModifier(source.event, opts.triggerModifier); this.hasSideBySideModifier = hasModifier(source.event, opts.triggerSideBySideModifier); - this.isNoneOrSingleMouseDown = (browser.isIE || source.event.detail <= 1); // IE does not support event.detail properly + this.isNoneOrSingleMouseDown = (source.event.detail <= 1); } } @@ -109,8 +108,9 @@ export class ClickLinkGesture extends Disposable { private readonly _editor: ICodeEditor; private _opts: ClickLinkOptions; - private lastMouseMoveEvent: ClickLinkMouseEvent | null; - private hasTriggerKeyOnMouseDown: boolean; + private _lastMouseMoveEvent: ClickLinkMouseEvent | null; + private _hasTriggerKeyOnMouseDown: boolean; + private _lineNumberOnMouseDown: number; constructor(editor: ICodeEditor) { super(); @@ -118,8 +118,9 @@ export class ClickLinkGesture extends Disposable { this._editor = editor; this._opts = createOptions(this._editor.getOption(EditorOption.multiCursorModifier)); - this.lastMouseMoveEvent = null; - this.hasTriggerKeyOnMouseDown = false; + this._lastMouseMoveEvent = null; + this._hasTriggerKeyOnMouseDown = false; + this._lineNumberOnMouseDown = 0; this._register(this._editor.onDidChangeConfiguration((e) => { if (e.hasChanged(EditorOption.multiCursorModifier)) { @@ -128,77 +129,80 @@ export class ClickLinkGesture extends Disposable { return; } this._opts = newOpts; - this.lastMouseMoveEvent = null; - this.hasTriggerKeyOnMouseDown = false; + this._lastMouseMoveEvent = null; + this._hasTriggerKeyOnMouseDown = false; + this._lineNumberOnMouseDown = 0; this._onCancel.fire(); } })); - this._register(this._editor.onMouseMove((e: IEditorMouseEvent) => this.onEditorMouseMove(new ClickLinkMouseEvent(e, this._opts)))); - this._register(this._editor.onMouseDown((e: IEditorMouseEvent) => this.onEditorMouseDown(new ClickLinkMouseEvent(e, this._opts)))); - this._register(this._editor.onMouseUp((e: IEditorMouseEvent) => this.onEditorMouseUp(new ClickLinkMouseEvent(e, this._opts)))); - this._register(this._editor.onKeyDown((e: IKeyboardEvent) => this.onEditorKeyDown(new ClickLinkKeyboardEvent(e, this._opts)))); - this._register(this._editor.onKeyUp((e: IKeyboardEvent) => this.onEditorKeyUp(new ClickLinkKeyboardEvent(e, this._opts)))); - this._register(this._editor.onMouseDrag(() => this.resetHandler())); + this._register(this._editor.onMouseMove((e: IEditorMouseEvent) => this._onEditorMouseMove(new ClickLinkMouseEvent(e, this._opts)))); + this._register(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(new ClickLinkMouseEvent(e, this._opts)))); + this._register(this._editor.onMouseUp((e: IEditorMouseEvent) => this._onEditorMouseUp(new ClickLinkMouseEvent(e, this._opts)))); + this._register(this._editor.onKeyDown((e: IKeyboardEvent) => this._onEditorKeyDown(new ClickLinkKeyboardEvent(e, this._opts)))); + this._register(this._editor.onKeyUp((e: IKeyboardEvent) => this._onEditorKeyUp(new ClickLinkKeyboardEvent(e, this._opts)))); + this._register(this._editor.onMouseDrag(() => this._resetHandler())); - this._register(this._editor.onDidChangeCursorSelection((e) => this.onDidChangeCursorSelection(e))); - this._register(this._editor.onDidChangeModel((e) => this.resetHandler())); - this._register(this._editor.onDidChangeModelContent(() => this.resetHandler())); + this._register(this._editor.onDidChangeCursorSelection((e) => this._onDidChangeCursorSelection(e))); + this._register(this._editor.onDidChangeModel((e) => this._resetHandler())); + this._register(this._editor.onDidChangeModelContent(() => this._resetHandler())); this._register(this._editor.onDidScrollChange((e) => { if (e.scrollTopChanged || e.scrollLeftChanged) { - this.resetHandler(); + this._resetHandler(); } })); } - private onDidChangeCursorSelection(e: ICursorSelectionChangedEvent): void { + private _onDidChangeCursorSelection(e: ICursorSelectionChangedEvent): void { if (e.selection && e.selection.startColumn !== e.selection.endColumn) { - this.resetHandler(); // immediately stop this feature if the user starts to select (https://github.com/Microsoft/vscode/issues/7827) + this._resetHandler(); // immediately stop this feature if the user starts to select (https://github.com/Microsoft/vscode/issues/7827) } } - private onEditorMouseMove(mouseEvent: ClickLinkMouseEvent): void { - this.lastMouseMoveEvent = mouseEvent; + private _onEditorMouseMove(mouseEvent: ClickLinkMouseEvent): void { + this._lastMouseMoveEvent = mouseEvent; this._onMouseMoveOrRelevantKeyDown.fire([mouseEvent, null]); } - private onEditorMouseDown(mouseEvent: ClickLinkMouseEvent): void { + private _onEditorMouseDown(mouseEvent: ClickLinkMouseEvent): void { // We need to record if we had the trigger key on mouse down because someone might select something in the editor // holding the mouse down and then while mouse is down start to press Ctrl/Cmd to start a copy operation and then // release the mouse button without wanting to do the navigation. // With this flag we prevent goto definition if the mouse was down before the trigger key was pressed. - this.hasTriggerKeyOnMouseDown = mouseEvent.hasTriggerModifier; + this._hasTriggerKeyOnMouseDown = mouseEvent.hasTriggerModifier; + this._lineNumberOnMouseDown = mouseEvent.target.position ? mouseEvent.target.position.lineNumber : 0; } - private onEditorMouseUp(mouseEvent: ClickLinkMouseEvent): void { - if (this.hasTriggerKeyOnMouseDown) { + private _onEditorMouseUp(mouseEvent: ClickLinkMouseEvent): void { + const currentLineNumber = mouseEvent.target.position ? mouseEvent.target.position.lineNumber : 0; + if (this._hasTriggerKeyOnMouseDown && this._lineNumberOnMouseDown && this._lineNumberOnMouseDown === currentLineNumber) { this._onExecute.fire(mouseEvent); } } - private onEditorKeyDown(e: ClickLinkKeyboardEvent): void { + private _onEditorKeyDown(e: ClickLinkKeyboardEvent): void { if ( - this.lastMouseMoveEvent + this._lastMouseMoveEvent && ( e.keyCodeIsTriggerKey // User just pressed Ctrl/Cmd (normal goto definition) || (e.keyCodeIsSideBySideKey && e.hasTriggerModifier) // User pressed Ctrl/Cmd+Alt (goto definition to the side) ) ) { - this._onMouseMoveOrRelevantKeyDown.fire([this.lastMouseMoveEvent, e]); + this._onMouseMoveOrRelevantKeyDown.fire([this._lastMouseMoveEvent, e]); } else if (e.hasTriggerModifier) { this._onCancel.fire(); // remove decorations if user holds another key with ctrl/cmd to prevent accident goto declaration } } - private onEditorKeyUp(e: ClickLinkKeyboardEvent): void { + private _onEditorKeyUp(e: ClickLinkKeyboardEvent): void { if (e.keyCodeIsTriggerKey) { this._onCancel.fire(); } } - private resetHandler(): void { - this.lastMouseMoveEvent = null; - this.hasTriggerKeyOnMouseDown = false; + private _resetHandler(): void { + this._lastMouseMoveEvent = null; + this._hasTriggerKeyOnMouseDown = false; this._onCancel.fire(); } } diff --git a/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts index 5d63164bab5..021c0dd6836 100644 --- a/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts @@ -217,10 +217,10 @@ export abstract class ReferencesController implements IEditorContribution { } closeWidget(focusEditor = true): void { - this._referenceSearchVisible.reset(); - this._disposables.clear(); dispose(this._widget); dispose(this._model); + this._referenceSearchVisible.reset(); + this._disposables.clear(); this._widget = undefined; this._model = undefined; if (focusEditor) { diff --git a/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts index dc6368509b3..5dab3d0ce2d 100644 --- a/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts @@ -28,7 +28,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ILabelService } from 'vs/platform/label/common/label'; import { WorkbenchAsyncDataTree, IWorkbenchAsyncDataTreeOptions } from 'vs/platform/list/browser/listService'; import { activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; -import { ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import * as peekView from 'vs/editor/contrib/peekView/peekView'; import { FileReferences, OneReference, ReferencesModel } from '../referencesModel'; import { FuzzyScore } from 'vs/base/common/filters'; @@ -221,8 +221,8 @@ export class ReferenceWidget extends peekView.PeekViewWidget { ) { super(editor, { showFrame: false, showArrow: true, isResizeable: true, isAccessible: true }); - this._applyTheme(themeService.getTheme()); - this._callOnDispose.add(themeService.onThemeChange(this._applyTheme.bind(this))); + this._applyTheme(themeService.getColorTheme()); + this._callOnDispose.add(themeService.onDidColorThemeChange(this._applyTheme.bind(this))); this._peekViewService.addExclusiveWidget(editor, this); this.create(); } @@ -239,7 +239,7 @@ export class ReferenceWidget extends peekView.PeekViewWidget { super.dispose(); } - private _applyTheme(theme: ITheme) { + private _applyTheme(theme: IColorTheme) { const borderColor = theme.getColor(peekView.peekViewBorder) || Color.transparent; this.style({ arrowColor: borderColor, diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index 3f03ee3e821..c50da11b0a0 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -581,7 +581,6 @@ export class ModesContentHoverWidget extends ContentHoverWidget { quickfixPlaceholderElement.textContent = nls.localize('checkingForQuickFixes', "Checking for quick fixes..."); disposables.add(toDisposable(() => quickfixPlaceholderElement.remove())); - const codeActionsPromise = this.getCodeActions(markerHover.marker); disposables.add(toDisposable(() => codeActionsPromise.cancel())); codeActionsPromise.then(actions => { @@ -639,11 +638,9 @@ export class ModesContentHoverWidget extends ContentHoverWidget { dom.append(action, $(`span.icon.${actionOptions.iconClass}`)); } const label = dom.append(action, $('span')); - label.textContent = actionOptions.label; const keybinding = this._keybindingService.lookupKeybinding(actionOptions.commandId); - if (keybinding) { - label.title = `${actionOptions.label} (${keybinding.getLabel()})`; - } + const keybindingLabel = keybinding ? keybinding.getLabel() : null; + label.textContent = keybindingLabel ? `${actionOptions.label} (${keybindingLabel})` : actionOptions.label; return dom.addDisposableListener(actionContainer, dom.EventType.CLICK, e => { e.stopPropagation(); e.preventDefault(); diff --git a/src/vs/editor/contrib/indentation/indentation.ts b/src/vs/editor/contrib/indentation/indentation.ts index 20280edd604..11c8b67f99a 100644 --- a/src/vs/editor/contrib/indentation/indentation.ts +++ b/src/vs/editor/contrib/indentation/indentation.ts @@ -241,7 +241,7 @@ export class ChangeIndentationSizeAction extends EditorAction { } } }); - }, 50/* quick open is sensitive to being opened so soon after another */); + }, 50/* quick input is sensitive to being opened so soon after another */); } } diff --git a/src/vs/editor/contrib/multicursor/multicursor.ts b/src/vs/editor/contrib/multicursor/multicursor.ts index 2388b709ff4..abebedd5080 100644 --- a/src/vs/editor/contrib/multicursor/multicursor.ts +++ b/src/vs/editor/contrib/multicursor/multicursor.ts @@ -786,11 +786,13 @@ class SelectionHighlighterState { public readonly searchText: string; public readonly matchCase: boolean; public readonly wordSeparators: string | null; + public readonly modelVersionId: number; - constructor(searchText: string, matchCase: boolean, wordSeparators: string | null) { + constructor(searchText: string, matchCase: boolean, wordSeparators: string | null, modelVersionId: number) { this.searchText = searchText; this.matchCase = matchCase; this.wordSeparators = wordSeparators; + this.modelVersionId = modelVersionId; } /** @@ -807,6 +809,7 @@ class SelectionHighlighterState { a.searchText === b.searchText && a.matchCase === b.matchCase && a.wordSeparators === b.wordSeparators + && a.modelVersionId === b.modelVersionId ); } } @@ -857,6 +860,11 @@ export class SelectionHighlighter extends Disposable implements IEditorContribut this._register(editor.onDidChangeModel((e) => { this._setState(null); })); + this._register(editor.onDidChangeModelContent((e) => { + if (this._isEnabled) { + this.updateSoon.schedule(); + } + })); this._register(CommonFindController.get(editor).getState().onFindReplaceStateChange((e) => { this._update(); })); @@ -939,7 +947,7 @@ export class SelectionHighlighter extends Disposable implements IEditorContribut } } - return new SelectionHighlighterState(r.searchText, r.matchCase, r.wholeWord ? editor.getOption(EditorOption.wordSeparators) : null); + return new SelectionHighlighterState(r.searchText, r.matchCase, r.wholeWord ? editor.getOption(EditorOption.wordSeparators) : null, editor.getModel().getVersionId()); } private _setState(state: SelectionHighlighterState | null): void { diff --git a/src/vs/editor/contrib/multicursor/test/multicursor.test.ts b/src/vs/editor/contrib/multicursor/test/multicursor.test.ts index 157a018ac0d..a1276a70c7d 100644 --- a/src/vs/editor/contrib/multicursor/test/multicursor.test.ts +++ b/src/vs/editor/contrib/multicursor/test/multicursor.test.ts @@ -80,8 +80,8 @@ suite('Multicursor selection', () => { 'var z = (3 * 5)', ], { serviceCollection: serviceCollection }, (editor, cursor) => { - let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); - let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); + let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); + let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); let selectHighlightsAction = new SelectHighlightsAction(); editor.setSelection(new Selection(2, 9, 2, 16)); @@ -110,8 +110,8 @@ suite('Multicursor selection', () => { 'nothing' ], { serviceCollection: serviceCollection }, (editor, cursor) => { - let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); - let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); + let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); + let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); let selectHighlightsAction = new SelectHighlightsAction(); editor.setSelection(new Selection(1, 1, 1, 1)); @@ -144,8 +144,8 @@ suite('Multicursor selection', () => { 'rty' ], { serviceCollection: serviceCollection }, (editor, cursor) => { - let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); - let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); + let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); + let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); let addSelectionToNextFindMatch = new AddSelectionToNextFindMatchAction(); editor.setSelection(new Selection(2, 1, 3, 4)); @@ -172,8 +172,8 @@ suite('Multicursor selection', () => { 'abcabc', ], { serviceCollection: serviceCollection }, (editor, cursor) => { - let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); - let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); + let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); + let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); let addSelectionToNextFindMatch = new AddSelectionToNextFindMatchAction(); editor.setSelection(new Selection(1, 1, 1, 4)); @@ -229,8 +229,8 @@ suite('Multicursor selection', () => { editor.getModel()!.setEOL(EndOfLineSequence.CRLF); - let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); - let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); + let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); + let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); let addSelectionToNextFindMatch = new AddSelectionToNextFindMatchAction(); editor.setSelection(new Selection(2, 1, 3, 4)); @@ -252,8 +252,8 @@ suite('Multicursor selection', () => { function testMulticursor(text: string[], callback: (editor: TestCodeEditor, findController: CommonFindController) => void): void { withTestCodeEditor(text, { serviceCollection: serviceCollection }, (editor, cursor) => { - let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); - let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); + let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); + let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); callback(editor, findController); diff --git a/src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts b/src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts new file mode 100644 index 00000000000..7db794fa83e --- /dev/null +++ b/src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AbstractCommandsQuickAccessProvider, ICommandQuickPick, ICommandsQuickAccessOptions } from 'vs/platform/quickinput/browser/commandsQuickAccess'; +import { IEditor } from 'vs/editor/common/editorCommon'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { stripCodicons } from 'vs/base/common/codicons'; + +export abstract class AbstractEditorCommandsQuickAccessProvider extends AbstractCommandsQuickAccessProvider { + + constructor( + options: ICommandsQuickAccessOptions, + instantiationService: IInstantiationService, + keybindingService: IKeybindingService, + commandService: ICommandService, + telemetryService: ITelemetryService, + notificationService: INotificationService + ) { + super(options, instantiationService, keybindingService, commandService, telemetryService, notificationService); + } + + /** + * Subclasses to provide the current active editor control. + */ + protected abstract activeTextEditorControl: IEditor | undefined; + + protected getCodeEditorCommandPicks(): ICommandQuickPick[] { + const activeTextEditorControl = this.activeTextEditorControl; + if (!activeTextEditorControl) { + return []; + } + + const editorCommandPicks: ICommandQuickPick[] = []; + for (const editorAction of activeTextEditorControl.getSupportedActions()) { + editorCommandPicks.push({ + commandId: editorAction.id, + commandAlias: editorAction.alias, + label: stripCodicons(editorAction.label) || editorAction.id, + }); + } + + return editorCommandPicks; + } +} diff --git a/src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts b/src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts new file mode 100644 index 00000000000..039dfbd30df --- /dev/null +++ b/src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts @@ -0,0 +1,219 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { IEditor, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon'; +import { IModelDeltaDecoration, OverviewRulerLane, ITextModel } from 'vs/editor/common/model'; +import { IRange } from 'vs/editor/common/core/range'; +import { themeColorFromId } from 'vs/platform/theme/common/themeService'; +import { overviewRulerRangeHighlight } from 'vs/editor/common/view/editorColorRegistry'; +import { IQuickPick, IQuickPickItem, IKeyMods } from 'vs/platform/quickinput/common/quickInput'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IDisposable, DisposableStore, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; +import { isDiffEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { once } from 'vs/base/common/functional'; + +interface IEditorLineDecoration { + rangeHighlightId: string; + overviewRulerDecorationId: string; +} + +export interface IEditorNavigationQuickAccessOptions { + canAcceptInBackground?: boolean; +} + +/** + * A reusable quick access provider for the editor with support + * for adding decorations for navigating in the currently active file + * (for example "Go to line", "Go to symbol"). + */ +export abstract class AbstractEditorNavigationQuickAccessProvider implements IQuickAccessProvider { + + constructor(protected options?: IEditorNavigationQuickAccessOptions) { } + + //#region Provider methods + + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // Apply options if any + picker.canAcceptInBackground = !!this.options?.canAcceptInBackground; + + // Disable filtering & sorting, we control the results + picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false; + + // Provide based on current active editor + const pickerDisposable = disposables.add(new MutableDisposable()); + pickerDisposable.value = this.doProvide(picker, token); + + // Re-create whenever the active editor changes + disposables.add(this.onDidActiveTextEditorControlChange(() => { + + // Clear old + pickerDisposable.value = undefined; + + // Add new + pickerDisposable.value = this.doProvide(picker, token); + })); + + return disposables; + } + + private doProvide(picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // With text control + const editor = this.activeTextEditorControl; + if (editor && this.canProvideWithTextEditor(editor)) { + + // Restore any view state if this picker was closed + // without actually going to a line + const codeEditor = getCodeEditor(editor); + if (codeEditor) { + + // Remember view state and update it when the cursor position + // changes even later because it could be that the user has + // configured quick access to remain open when focus is lost and + // we always want to restore the current location. + let lastKnownEditorViewState = withNullAsUndefined(editor.saveViewState()); + disposables.add(codeEditor.onDidChangeCursorPosition(() => { + lastKnownEditorViewState = withNullAsUndefined(editor.saveViewState()); + })); + + disposables.add(once(token.onCancellationRequested)(() => { + if (lastKnownEditorViewState && editor === this.activeTextEditorControl) { + editor.restoreViewState(lastKnownEditorViewState); + } + })); + } + + // Clean up decorations on dispose + disposables.add(toDisposable(() => this.clearDecorations(editor))); + + // Ask subclass for entries + disposables.add(this.provideWithTextEditor(editor, picker, token)); + } + + // Without text control + else { + disposables.add(this.provideWithoutTextEditor(picker, token)); + } + + return disposables; + } + + /** + * Subclasses to implement if they can operate on the text editor. + */ + protected canProvideWithTextEditor(editor: IEditor): boolean { + return true; + } + + /** + * Subclasses to implement to provide picks for the picker when an editor is active. + */ + protected abstract provideWithTextEditor(editor: IEditor, picker: IQuickPick, token: CancellationToken): IDisposable; + + /** + * Subclasses to implement to provide picks for the picker when no editor is active. + */ + protected abstract provideWithoutTextEditor(picker: IQuickPick, token: CancellationToken): IDisposable; + + protected gotoLocation(editor: IEditor, options: { range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean, preserveFocus?: boolean }): void { + editor.setSelection(options.range); + editor.revealRangeInCenter(options.range, ScrollType.Smooth); + if (!options.preserveFocus) { + editor.focus(); + } + } + + protected getModel(editor: IEditor | IDiffEditor): ITextModel | undefined { + return isDiffEditor(editor) ? + editor.getModel()?.modified : + editor.getModel() as ITextModel; + } + + //#endregion + + + //#region Editor access + + /** + * Subclasses to provide an event when the active editor control changes. + */ + protected abstract readonly onDidActiveTextEditorControlChange: Event; + + /** + * Subclasses to provide the current active editor control. + */ + protected abstract activeTextEditorControl: IEditor | undefined; + + //#endregion + + + //#region Decorations Utils + + private rangeHighlightDecorationId: IEditorLineDecoration | undefined = undefined; + + protected addDecorations(editor: IEditor, range: IRange): void { + editor.changeDecorations(changeAccessor => { + + // Reset old decorations if any + const deleteDecorations: string[] = []; + if (this.rangeHighlightDecorationId) { + deleteDecorations.push(this.rangeHighlightDecorationId.overviewRulerDecorationId); + deleteDecorations.push(this.rangeHighlightDecorationId.rangeHighlightId); + + this.rangeHighlightDecorationId = undefined; + } + + // Add new decorations for the range + const newDecorations: IModelDeltaDecoration[] = [ + + // highlight the entire line on the range + { + range, + options: { + className: 'rangeHighlight', + isWholeLine: true + } + }, + + // also add overview ruler highlight + { + range, + options: { + overviewRuler: { + color: themeColorFromId(overviewRulerRangeHighlight), + position: OverviewRulerLane.Full + } + } + } + ]; + + const [rangeHighlightId, overviewRulerDecorationId] = changeAccessor.deltaDecorations(deleteDecorations, newDecorations); + + this.rangeHighlightDecorationId = { rangeHighlightId, overviewRulerDecorationId }; + }); + } + + protected clearDecorations(editor: IEditor): void { + const rangeHighlightDecorationId = this.rangeHighlightDecorationId; + if (rangeHighlightDecorationId) { + editor.changeDecorations(changeAccessor => { + changeAccessor.deltaDecorations([ + rangeHighlightDecorationId.overviewRulerDecorationId, + rangeHighlightDecorationId.rangeHighlightId + ], []); + }); + + this.rangeHighlightDecorationId = undefined; + } + } + + //#endregion +} diff --git a/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts new file mode 100644 index 00000000000..0c17d7c0b41 --- /dev/null +++ b/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts @@ -0,0 +1,169 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { DisposableStore, IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IEditor, ScrollType } from 'vs/editor/common/editorCommon'; +import { IRange } from 'vs/editor/common/core/range'; +import { AbstractEditorNavigationQuickAccessProvider } from 'vs/editor/contrib/quickAccess/editorNavigationQuickAccess'; +import { IPosition } from 'vs/editor/common/core/position'; +import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EditorOption, RenderLineNumbersType } from 'vs/editor/common/config/editorOptions'; + +interface IGotoLineQuickPickItem extends IQuickPickItem, Partial { } + +export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditorNavigationQuickAccessProvider { + + static PREFIX = ':'; + + constructor() { + super({ canAcceptInBackground: true }); + } + + protected provideWithoutTextEditor(picker: IQuickPick): IDisposable { + const label = localize('cannotRunGotoLine', "Open a text editor first to go to a line."); + + picker.items = [{ label }]; + picker.ariaLabel = label; + + return Disposable.None; + } + + protected provideWithTextEditor(editor: IEditor, picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // Goto line once picked + disposables.add(picker.onDidAccept(event => { + const [item] = picker.selectedItems; + if (item) { + if (!this.isValidLineNumber(editor, item.lineNumber)) { + return; + } + + this.gotoLocation(editor, { range: this.toRange(item.lineNumber, item.column), keyMods: picker.keyMods, preserveFocus: event.inBackground }); + + if (!event.inBackground) { + picker.hide(); + } + } + })); + + // React to picker changes + const updatePickerAndEditor = () => { + const position = this.parsePosition(editor, picker.value.trim().substr(AbstractGotoLineQuickAccessProvider.PREFIX.length)); + const label = this.getPickLabel(editor, position.lineNumber, position.column); + + // Picker + picker.items = [{ + lineNumber: position.lineNumber, + column: position.column, + label + }]; + + // ARIA Label + picker.ariaLabel = label; + + // Clear decorations for invalid range + if (!this.isValidLineNumber(editor, position.lineNumber)) { + this.clearDecorations(editor); + return; + } + + // Reveal + const range = this.toRange(position.lineNumber, position.column); + editor.revealRangeInCenter(range, ScrollType.Smooth); + + // Decorate + this.addDecorations(editor, range); + }; + updatePickerAndEditor(); + disposables.add(picker.onDidChangeValue(() => updatePickerAndEditor())); + + // Adjust line number visibility as needed + const codeEditor = getCodeEditor(editor); + if (codeEditor) { + const options = codeEditor.getOptions(); + const lineNumbers = options.get(EditorOption.lineNumbers); + if (lineNumbers.renderType === RenderLineNumbersType.Relative) { + codeEditor.updateOptions({ lineNumbers: 'on' }); + + disposables.add(toDisposable(() => codeEditor.updateOptions({ lineNumbers: 'relative' }))); + } + } + + return disposables; + } + + private toRange(lineNumber = 1, column = 1): IRange { + return { + startLineNumber: lineNumber, + startColumn: column, + endLineNumber: lineNumber, + endColumn: column + }; + } + + private parsePosition(editor: IEditor, value: string): IPosition { + + // Support line-col formats of `line,col`, `line:col`, `line#col` + const numbers = value.split(/,|:|#/).map(part => parseInt(part, 10)).filter(part => !isNaN(part)); + const endLine = this.lineCount(editor) + 1; + + return { + lineNumber: numbers[0] > 0 ? numbers[0] : endLine + numbers[0], + column: numbers[1] + }; + } + + private getPickLabel(editor: IEditor, lineNumber: number, column: number | undefined): string { + + // Location valid: indicate this as picker label + if (this.isValidLineNumber(editor, lineNumber)) { + if (this.isValidColumn(editor, lineNumber, column)) { + return localize('gotoLineColumnLabel', "Go to line {0} and column {1}.", lineNumber, column); + } + + return localize('gotoLineLabel', "Go to line {0}.", lineNumber); + } + + // Location invalid: show generic label + const position = editor.getPosition() || { lineNumber: 1, column: 1 }; + const lineCount = this.lineCount(editor); + if (lineCount > 1) { + return localize('gotoLineLabelEmptyWithLimit', "Current Line: {0}, Character: {1}. Type a line number between 1 and {2} to navigate to.", position.lineNumber, position.column, lineCount); + } + + return localize('gotoLineLabelEmpty', "Current Line: {0}, Character: {1}. Type a line number to navigate to.", position.lineNumber, position.column); + } + + private isValidLineNumber(editor: IEditor, lineNumber: number | undefined): boolean { + if (!lineNumber || typeof lineNumber !== 'number') { + return false; + } + + return lineNumber > 0 && lineNumber <= this.lineCount(editor); + } + + private isValidColumn(editor: IEditor, lineNumber: number, column: number | undefined): boolean { + if (!column || typeof column !== 'number') { + return false; + } + + const model = this.getModel(editor); + if (!model) { + return false; + } + + const positionCandidate = { lineNumber, column }; + + return model.validatePosition(positionCandidate).equals(positionCandidate); + } + + private lineCount(editor: IEditor): number { + return this.getModel(editor)?.getLineCount() ?? 0; + } +} diff --git a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts new file mode 100644 index 00000000000..30079dd5e7f --- /dev/null +++ b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts @@ -0,0 +1,469 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IQuickPick, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { DisposableStore, IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IEditor, ScrollType } from 'vs/editor/common/editorCommon'; +import { ITextModel } from 'vs/editor/common/model'; +import { IRange, Range } from 'vs/editor/common/core/range'; +import { AbstractEditorNavigationQuickAccessProvider, IEditorNavigationQuickAccessOptions } from 'vs/editor/contrib/quickAccess/editorNavigationQuickAccess'; +import { DocumentSymbol, SymbolKinds, SymbolTag, DocumentSymbolProviderRegistry, SymbolKind } from 'vs/editor/common/modes'; +import { OutlineModel, OutlineElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; +import { values } from 'vs/base/common/collections'; +import { trim, format } from 'vs/base/common/strings'; +import { prepareQuery, IPreparedQuery, pieceToQuery, scoreFuzzy2 } from 'vs/base/common/fuzzyScorer'; +import { IMatch } from 'vs/base/common/filters'; + +export interface IGotoSymbolQuickPickItem extends IQuickPickItem { + kind: SymbolKind, + index: number, + score?: number; + range?: { decoration: IRange, selection: IRange } +} + +export interface IGotoSymbolQuickAccessProviderOptions extends IEditorNavigationQuickAccessOptions { + openSideBySideDirection: () => undefined | 'right' | 'down' +} + +export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEditorNavigationQuickAccessProvider { + + static PREFIX = '@'; + static SCOPE_PREFIX = ':'; + static PREFIX_BY_CATEGORY = `${AbstractGotoSymbolQuickAccessProvider.PREFIX}${AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX}`; + + constructor(protected options?: IGotoSymbolQuickAccessProviderOptions) { + super({ ...options, canAcceptInBackground: true }); + } + + protected provideWithoutTextEditor(picker: IQuickPick): IDisposable { + const label = localize('cannotRunGotoSymbolWithoutEditor', "To go to a symbol, first open a text editor with symbol information."); + + picker.items = [{ label, index: 0, kind: SymbolKind.String }]; + picker.ariaLabel = label; + + return Disposable.None; + } + + protected provideWithTextEditor(editor: IEditor, picker: IQuickPick, token: CancellationToken): IDisposable { + const model = this.getModel(editor); + if (!model) { + return Disposable.None; + } + + // Provide symbols from model if available in registry + if (DocumentSymbolProviderRegistry.has(model)) { + return this.doProvideWithEditorSymbols(editor, model, picker, token); + } + + // Otherwise show an entry for a model without registry + // But give a chance to resolve the symbols at a later + // point if possible + return this.doProvideWithoutEditorSymbols(editor, model, picker, token); + } + + private doProvideWithoutEditorSymbols(editor: IEditor, model: ITextModel, picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // Generic pick for not having any symbol information + const label = localize('cannotRunGotoSymbolWithoutSymbolProvider', "The active text editor does not provide symbol information."); + picker.items = [{ label, index: 0, kind: SymbolKind.String }]; + picker.ariaLabel = label; + + // Wait for changes to the registry and see if eventually + // we do get symbols. This can happen if the picker is opened + // very early after the model has loaded but before the + // language registry is ready. + // https://github.com/microsoft/vscode/issues/70607 + (async () => { + const result = await this.waitForLanguageSymbolRegistry(model, disposables); + if (!result || token.isCancellationRequested) { + return; + } + + disposables.add(this.doProvideWithEditorSymbols(editor, model, picker, token)); + })(); + + return disposables; + } + + protected async waitForLanguageSymbolRegistry(model: ITextModel, disposables: DisposableStore): Promise { + if (DocumentSymbolProviderRegistry.has(model)) { + return true; + } + + let symbolProviderRegistryPromiseResolve: (res: boolean) => void; + const symbolProviderRegistryPromise = new Promise(resolve => symbolProviderRegistryPromiseResolve = resolve); + + // Resolve promise when registry knows model + const symbolProviderListener = disposables.add(DocumentSymbolProviderRegistry.onDidChange(() => { + if (DocumentSymbolProviderRegistry.has(model)) { + symbolProviderListener.dispose(); + + symbolProviderRegistryPromiseResolve(true); + } + })); + + // Resolve promise when we get disposed too + disposables.add(toDisposable(() => symbolProviderRegistryPromiseResolve(false))); + + return symbolProviderRegistryPromise; + } + + private doProvideWithEditorSymbols(editor: IEditor, model: ITextModel, picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // Goto symbol once picked + disposables.add(picker.onDidAccept(event => { + const [item] = picker.selectedItems; + if (item && item.range) { + this.gotoLocation(editor, { range: item.range.selection, keyMods: picker.keyMods, preserveFocus: event.inBackground }); + + if (!event.inBackground) { + picker.hide(); + } + } + })); + + // Goto symbol side by side if enabled + disposables.add(picker.onDidTriggerItemButton(({ item }) => { + if (item && item.range) { + this.gotoLocation(editor, { range: item.range.selection, keyMods: picker.keyMods, forceSideBySide: true }); + + picker.hide(); + } + })); + + // Resolve symbols from document once and reuse this + // request for all filtering and typing then on + const symbolsPromise = this.getDocumentSymbols(model, true, token); + + // Set initial picks and update on type + let picksCts: CancellationTokenSource | undefined = undefined; + const updatePickerItems = async () => { + + // Cancel any previous ask for picks and busy + picksCts?.dispose(true); + picker.busy = false; + + // Create new cancellation source for this run + picksCts = new CancellationTokenSource(token); + + // Collect symbol picks + picker.busy = true; + try { + const items = await this.doGetSymbolPicks(symbolsPromise, prepareQuery(picker.value.substr(AbstractGotoSymbolQuickAccessProvider.PREFIX.length).trim()), undefined, picksCts.token); + if (token.isCancellationRequested) { + return; + } + + picker.items = items; + } finally { + if (!token.isCancellationRequested) { + picker.busy = false; + } + } + }; + disposables.add(picker.onDidChangeValue(() => updatePickerItems())); + updatePickerItems(); + + // Reveal and decorate when active item changes + // However, ignore the very first event so that + // opening the picker is not immediately revealing + // and decorating the first entry. + let ignoreFirstActiveEvent = true; + disposables.add(picker.onDidChangeActive(() => { + const [item] = picker.activeItems; + if (item && item.range) { + if (ignoreFirstActiveEvent) { + ignoreFirstActiveEvent = false; + return; + } + + // Reveal + editor.revealRangeInCenter(item.range.selection, ScrollType.Smooth); + + // Decorate + this.addDecorations(editor, item.range.decoration); + } + })); + + return disposables; + } + + protected async doGetSymbolPicks(symbolsPromise: Promise, query: IPreparedQuery, options: { extraContainerLabel?: string } | undefined, token: CancellationToken): Promise> { + const symbols = await symbolsPromise; + if (token.isCancellationRequested) { + return []; + } + + const filterBySymbolKind = query.original.indexOf(AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX) === 0; + const filterPos = filterBySymbolKind ? 1 : 0; + + // Split between symbol and container query + let symbolQuery: IPreparedQuery; + let containerQuery: IPreparedQuery | undefined; + if (query.values && query.values.length > 1) { + symbolQuery = pieceToQuery(query.values[0]); // symbol: only match on first part + containerQuery = pieceToQuery(query.values.slice(1)); // container: match on all but first parts + } else { + symbolQuery = query; + } + + // Convert to symbol picks and apply filtering + const filteredSymbolPicks: IGotoSymbolQuickPickItem[] = []; + for (let index = 0; index < symbols.length; index++) { + const symbol = symbols[index]; + + const symbolLabel = trim(symbol.name); + const symbolLabelWithIcon = `$(symbol-${SymbolKinds.toString(symbol.kind) || 'property'}) ${symbolLabel}`; + + let containerLabel = symbol.containerName; + if (options?.extraContainerLabel) { + if (containerLabel) { + containerLabel = `${options.extraContainerLabel} • ${containerLabel}`; + } else { + containerLabel = options.extraContainerLabel; + } + } + + let symbolScore: number | undefined = undefined; + let symbolMatches: IMatch[] | undefined = undefined; + + let containerScore: number | undefined = undefined; + let containerMatches: IMatch[] | undefined = undefined; + + if (query.original.length > filterPos) { + + // Score by symbol + [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabel, symbolQuery, filterPos, symbolLabelWithIcon.length - symbolLabel.length /* Readjust matches to account for codicons in label */); + if (!symbolScore) { + continue; + } + + // Score by container if specified + if (containerQuery) { + if (containerLabel && containerQuery.original.length > 0) { + [containerScore, containerMatches] = scoreFuzzy2(containerLabel, containerQuery); + } + + if (!containerScore) { + continue; + } + + if (symbolScore) { + symbolScore += containerScore; // boost symbolScore by containerScore + } + } + } + + const deprecated = symbol.tags && symbol.tags.indexOf(SymbolTag.Deprecated) >= 0; + + filteredSymbolPicks.push({ + index, + kind: symbol.kind, + score: symbolScore, + label: symbolLabelWithIcon, + ariaLabel: symbolLabel, + description: containerLabel, + highlights: deprecated ? undefined : { + label: symbolMatches, + description: containerMatches + }, + range: { + selection: Range.collapseToStart(symbol.selectionRange), + decoration: symbol.range + }, + strikethrough: deprecated, + buttons: (() => { + const openSideBySideDirection = this.options?.openSideBySideDirection(); + if (!openSideBySideDirection) { + return undefined; + } + + return [ + { + iconClass: openSideBySideDirection === 'right' ? 'codicon-split-horizontal' : 'codicon-split-vertical', + tooltip: openSideBySideDirection === 'right' ? localize('openToSide', "Open to the Side") : localize('openToBottom', "Open to the Bottom") + } + ]; + })() + }); + } + + // Sort by score + const sortedFilteredSymbolPicks = filteredSymbolPicks.sort((symbolA, symbolB) => filterBySymbolKind ? + this.compareByKindAndScore(symbolA, symbolB) : + this.compareByScore(symbolA, symbolB) + ); + + // Add separator for types + // - @ only total number of symbols + // - @: grouped by symbol kind + let symbolPicks: Array = []; + if (filterBySymbolKind) { + let lastSymbolKind: SymbolKind | undefined = undefined; + let lastSeparator: IQuickPickSeparator | undefined = undefined; + let lastSymbolKindCounter = 0; + + function updateLastSeparatorLabel(): void { + if (lastSeparator && typeof lastSymbolKind === 'number' && lastSymbolKindCounter > 0) { + lastSeparator.label = format(NLS_SYMBOL_KIND_CACHE[lastSymbolKind] || FALLBACK_NLS_SYMBOL_KIND, lastSymbolKindCounter); + } + } + + for (const symbolPick of sortedFilteredSymbolPicks) { + + // Found new kind + if (lastSymbolKind !== symbolPick.kind) { + + // Update last separator with number of symbols we found for kind + updateLastSeparatorLabel(); + + lastSymbolKind = symbolPick.kind; + lastSymbolKindCounter = 1; + + // Add new separator for new kind + lastSeparator = { type: 'separator' }; + symbolPicks.push(lastSeparator); + } + + // Existing kind, keep counting + else { + lastSymbolKindCounter++; + } + + // Add to final result + symbolPicks.push(symbolPick); + } + + // Update last separator with number of symbols we found for kind + updateLastSeparatorLabel(); + } else { + symbolPicks = [ + { label: localize('symbols', "symbols ({0})", filteredSymbolPicks.length), type: 'separator' }, + ...sortedFilteredSymbolPicks + ]; + } + + return symbolPicks; + } + + private compareByScore(symbolA: IGotoSymbolQuickPickItem, symbolB: IGotoSymbolQuickPickItem): number { + if (!symbolA.score && symbolB.score) { + return 1; + } else if (symbolA.score && !symbolB.score) { + return -1; + } + + if (symbolA.score && symbolB.score) { + if (symbolA.score > symbolB.score) { + return -1; + } else if (symbolA.score < symbolB.score) { + return 1; + } + } + + if (symbolA.index < symbolB.index) { + return -1; + } else if (symbolA.index > symbolB.index) { + return 1; + } + + return 0; + } + + private compareByKindAndScore(symbolA: IGotoSymbolQuickPickItem, symbolB: IGotoSymbolQuickPickItem): number { + const kindA = NLS_SYMBOL_KIND_CACHE[symbolA.kind] || FALLBACK_NLS_SYMBOL_KIND; + const kindB = NLS_SYMBOL_KIND_CACHE[symbolB.kind] || FALLBACK_NLS_SYMBOL_KIND; + + // Sort by type first if scoped search + const result = kindA.localeCompare(kindB); + if (result === 0) { + return this.compareByScore(symbolA, symbolB); + } + + return result; + } + + protected async getDocumentSymbols(document: ITextModel, flatten: boolean, token: CancellationToken): Promise { + const model = await OutlineModel.create(document, token); + if (token.isCancellationRequested) { + return []; + } + + const roots: DocumentSymbol[] = []; + for (const child of values(model.children)) { + if (child instanceof OutlineElement) { + roots.push(child.symbol); + } else { + roots.push(...values(child.children).map(child => child.symbol)); + } + } + + let flatEntries: DocumentSymbol[] = []; + if (flatten) { + this.flattenDocumentSymbols(flatEntries, roots, ''); + } else { + flatEntries = roots; + } + + return flatEntries.sort((symbolA, symbolB) => Range.compareRangesUsingStarts(symbolA.range, symbolB.range)); + } + + private flattenDocumentSymbols(bucket: DocumentSymbol[], entries: DocumentSymbol[], overrideContainerLabel: string): void { + for (const entry of entries) { + bucket.push({ + kind: entry.kind, + tags: entry.tags, + name: entry.name, + detail: entry.detail, + containerName: entry.containerName || overrideContainerLabel, + range: entry.range, + selectionRange: entry.selectionRange, + children: undefined, // we flatten it... + }); + + // Recurse over children + if (entry.children) { + this.flattenDocumentSymbols(bucket, entry.children, entry.name); + } + } + } +} + +// #region NLS Helpers + +const FALLBACK_NLS_SYMBOL_KIND = localize('property', "properties ({0})"); +const NLS_SYMBOL_KIND_CACHE: { [type: number]: string } = { + [SymbolKind.Method]: localize('method', "methods ({0})"), + [SymbolKind.Function]: localize('function', "functions ({0})"), + [SymbolKind.Constructor]: localize('_constructor', "constructors ({0})"), + [SymbolKind.Variable]: localize('variable', "variables ({0})"), + [SymbolKind.Class]: localize('class', "classes ({0})"), + [SymbolKind.Struct]: localize('struct', "structs ({0})"), + [SymbolKind.Event]: localize('event', "events ({0})"), + [SymbolKind.Operator]: localize('operator', "operators ({0})"), + [SymbolKind.Interface]: localize('interface', "interfaces ({0})"), + [SymbolKind.Namespace]: localize('namespace', "namespaces ({0})"), + [SymbolKind.Package]: localize('package', "packages ({0})"), + [SymbolKind.TypeParameter]: localize('typeParameter', "type parameters ({0})"), + [SymbolKind.Module]: localize('modules', "modules ({0})"), + [SymbolKind.Property]: localize('property', "properties ({0})"), + [SymbolKind.Enum]: localize('enum', "enumerations ({0})"), + [SymbolKind.EnumMember]: localize('enumMember', "enumeration members ({0})"), + [SymbolKind.String]: localize('string', "strings ({0})"), + [SymbolKind.File]: localize('file', "files ({0})"), + [SymbolKind.Array]: localize('array', "arrays ({0})"), + [SymbolKind.Number]: localize('number', "numbers ({0})"), + [SymbolKind.Boolean]: localize('boolean', "booleans ({0})"), + [SymbolKind.Object]: localize('object', "objects ({0})"), + [SymbolKind.Key]: localize('key', "keys ({0})"), + [SymbolKind.Field]: localize('field', "fields ({0})"), + [SymbolKind.Constant]: localize('constant', "constants ({0})") +}; + +//#endregion diff --git a/src/vs/workbench/contrib/files/browser/media/fileactions.css b/src/vs/editor/contrib/rename/media/onTypeRename.css similarity index 64% rename from src/vs/workbench/contrib/files/browser/media/fileactions.css rename to src/vs/editor/contrib/rename/media/onTypeRename.css index 466bf9ec189..2c80c5957b1 100644 --- a/src/vs/workbench/contrib/files/browser/media/fileactions.css +++ b/src/vs/editor/contrib/rename/media/onTypeRename.css @@ -3,6 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .codicon-close::before { - content: "\ea71"; +.monaco-editor .on-type-rename-decoration { + background: rgba(255, 0, 0, 0.3); + border-left: 1px solid rgba(255, 0, 0, 0.3); + /* So border can be transparent */ + background-clip: padding-box; } diff --git a/src/vs/editor/contrib/rename/onTypeRename.ts b/src/vs/editor/contrib/rename/onTypeRename.ts new file mode 100644 index 00000000000..8a3177f139f --- /dev/null +++ b/src/vs/editor/contrib/rename/onTypeRename.ts @@ -0,0 +1,367 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./media/onTypeRename'; +import * as nls from 'vs/nls'; +import { registerEditorContribution, registerModelAndPositionCommand, EditorAction, EditorCommand, ServicesAccessor, registerEditorAction, registerEditorCommand } from 'vs/editor/browser/editorExtensions'; +import * as arrays from 'vs/base/common/arrays'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { Position, IPosition } from 'vs/editor/common/core/position'; +import { ITextModel, IModelDeltaDecoration, TrackedRangeStickiness, IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IRange, Range } from 'vs/editor/common/core/range'; +import { OnTypeRenameProviderRegistry } from 'vs/editor/common/modes'; +import { first, createCancelablePromise, CancelablePromise, RunOnceScheduler } from 'vs/base/common/async'; +import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; +import { ContextKeyExpr, RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { URI } from 'vs/base/common/uri'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/errors'; +import * as strings from 'vs/base/common/strings'; + +export const CONTEXT_ONTYPE_RENAME_INPUT_VISIBLE = new RawContextKey('onTypeRenameInputVisible', false); + +export class OnTypeRenameContribution extends Disposable implements IEditorContribution { + + public static readonly ID = 'editor.contrib.onTypeRename'; + + private static readonly DECORATION = ModelDecorationOptions.register({ + stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges, + className: 'on-type-rename-decoration' + }); + + static get(editor: ICodeEditor): OnTypeRenameContribution { + return editor.getContribution(OnTypeRenameContribution.ID); + } + + private readonly _editor: ICodeEditor; + private _enabled: boolean; + + private readonly _visibleContextKey: IContextKey; + + private _currentRequest: CancelablePromise<{ + ranges: IRange[], + stopPattern?: RegExp + } | null | undefined> | null; + private _currentDecorations: string[]; // The one at index 0 is the reference one + private _stopPattern: RegExp; + private _ignoreChangeEvent: boolean; + private _updateMirrors: RunOnceScheduler; + + constructor( + editor: ICodeEditor, + @IContextKeyService contextKeyService: IContextKeyService + ) { + super(); + this._editor = editor; + this._enabled = this._editor.getOption(EditorOption.renameOnType); + this._visibleContextKey = CONTEXT_ONTYPE_RENAME_INPUT_VISIBLE.bindTo(contextKeyService); + this._currentRequest = null; + this._currentDecorations = []; + this._stopPattern = /^\s/; + this._ignoreChangeEvent = false; + this._updateMirrors = this._register(new RunOnceScheduler(() => this._doUpdateMirrors(), 0)); + + this._register(this._editor.onDidChangeModel((e) => { + this.stopAll(); + this.run(); + })); + + this._register(this._editor.onDidChangeConfiguration((e) => { + if (e.hasChanged(EditorOption.renameOnType)) { + this._enabled = this._editor.getOption(EditorOption.renameOnType); + this.stopAll(); + this.run(); + } + })); + + this._register(this._editor.onDidChangeCursorPosition((e) => { + // no regions, run + if (this._currentDecorations.length === 0) { + this.run(e.position); + } + + // has cached regions, don't run + if (!this._editor.hasModel()) { + return; + } + if (this._currentDecorations.length === 0) { + return; + } + const model = this._editor.getModel(); + const currentRanges = this._currentDecorations.map(decId => model.getDecorationRange(decId)!); + + // just moving cursor around, don't run again + if (Range.containsPosition(currentRanges[0], e.position)) { + return; + } + + // moving cursor out of primary region, run + this.run(e.position); + })); + + this._register(OnTypeRenameProviderRegistry.onDidChange(() => { + this.run(); + })); + + this._register(this._editor.onDidChangeModelContent((e) => { + if (this._ignoreChangeEvent) { + return; + } + if (!this._editor.hasModel()) { + return; + } + if (this._currentDecorations.length === 0) { + // nothing to do + return; + } + if (e.isUndoing || e.isRedoing) { + return; + } + if (e.changes[0] && this._stopPattern.test(e.changes[0].text)) { + this.stopAll(); + return; + } + this._updateMirrors.schedule(); + })); + } + + private _doUpdateMirrors(): void { + if (!this._editor.hasModel()) { + return; + } + if (this._currentDecorations.length === 0) { + // nothing to do + return; + } + + const model = this._editor.getModel(); + const currentRanges = this._currentDecorations.map(decId => model.getDecorationRange(decId)!); + + const referenceRange = currentRanges[0]; + if (referenceRange.startLineNumber !== referenceRange.endLineNumber) { + return this.stopAll(); + } + + const referenceValue = model.getValueInRange(referenceRange); + if (this._stopPattern.test(referenceValue)) { + return this.stopAll(); + } + + let edits: IIdentifiedSingleEditOperation[] = []; + for (let i = 1, len = currentRanges.length; i < len; i++) { + const mirrorRange = currentRanges[i]; + if (mirrorRange.startLineNumber !== mirrorRange.endLineNumber) { + edits.push({ + range: mirrorRange, + text: referenceValue + }); + } else { + let oldValue = model.getValueInRange(mirrorRange); + let newValue = referenceValue; + let rangeStartColumn = mirrorRange.startColumn; + let rangeEndColumn = mirrorRange.endColumn; + + const commonPrefixLength = strings.commonPrefixLength(oldValue, newValue); + rangeStartColumn += commonPrefixLength; + oldValue = oldValue.substr(commonPrefixLength); + newValue = newValue.substr(commonPrefixLength); + + const commonSuffixLength = strings.commonSuffixLength(oldValue, newValue); + rangeEndColumn -= commonSuffixLength; + oldValue = oldValue.substr(0, oldValue.length - commonSuffixLength); + newValue = newValue.substr(0, newValue.length - commonSuffixLength); + + if (rangeStartColumn !== rangeEndColumn || newValue.length !== 0) { + edits.push({ + range: new Range(mirrorRange.startLineNumber, rangeStartColumn, mirrorRange.endLineNumber, rangeEndColumn), + text: newValue + }); + } + } + } + + if (edits.length === 0) { + return; + } + + try { + this._ignoreChangeEvent = true; + const prevEditOperationType = this._editor._getCursors().getPrevEditOperationType(); + this._editor.executeEdits('onTypeRename', edits); + this._editor._getCursors().setPrevEditOperationType(prevEditOperationType); + } finally { + this._ignoreChangeEvent = false; + } + } + + public dispose(): void { + super.dispose(); + this.stopAll(); + } + + stopAll(): void { + this._visibleContextKey.set(false); + this._currentDecorations = this._editor.deltaDecorations(this._currentDecorations, []); + } + + async run(position: Position | null = this._editor.getPosition(), force = false): Promise { + if (!position) { + return; + } + if (!this._enabled && !force) { + return; + } + if (!this._editor.hasModel()) { + return; + } + + if (this._currentRequest) { + this._currentRequest.cancel(); + this._currentRequest = null; + } + + const model = this._editor.getModel(); + + this._currentRequest = createCancelablePromise(token => getOnTypeRenameRanges(model, position, token)); + try { + const response = await this._currentRequest; + + let ranges: IRange[] = []; + if (response?.ranges) { + ranges = response.ranges; + } + if (response?.stopPattern) { + this._stopPattern = response.stopPattern; + } + + let foundReferenceRange = false; + for (let i = 0, len = ranges.length; i < len; i++) { + if (Range.containsPosition(ranges[i], position)) { + foundReferenceRange = true; + if (i !== 0) { + const referenceRange = ranges[i]; + ranges.splice(i, 1); + ranges.unshift(referenceRange); + } + break; + } + } + + if (!foundReferenceRange) { + // Cannot do on type rename if the ranges are not where the cursor is... + this.stopAll(); + return; + } + + const decorations: IModelDeltaDecoration[] = ranges.map(range => ({ range: range, options: OnTypeRenameContribution.DECORATION })); + this._visibleContextKey.set(true); + this._currentDecorations = this._editor.deltaDecorations(this._currentDecorations, decorations); + } catch (err) { + onUnexpectedError(err); + this.stopAll(); + } + } +} + +export class OnTypeRenameAction extends EditorAction { + constructor() { + super({ + id: 'editor.action.onTypeRename', + label: nls.localize('onTypeRename.label', "On Type Rename Symbol"), + alias: 'On Type Rename Symbol', + precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasRenameProvider), + kbOpts: { + kbExpr: EditorContextKeys.editorTextFocus, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.F2, + weight: KeybindingWeight.EditorContrib + } + }); + } + + runCommand(accessor: ServicesAccessor, args: [URI, IPosition]): void | Promise { + const editorService = accessor.get(ICodeEditorService); + const [uri, pos] = Array.isArray(args) && args || [undefined, undefined]; + + if (URI.isUri(uri) && Position.isIPosition(pos)) { + return editorService.openCodeEditor({ resource: uri }, editorService.getActiveCodeEditor()).then(editor => { + if (!editor) { + return; + } + editor.setPosition(pos); + editor.invokeWithinContext(accessor => { + this.reportTelemetry(accessor, editor); + return this.run(accessor, editor); + }); + }, onUnexpectedError); + } + + return super.runCommand(accessor, args); + } + + run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { + const controller = OnTypeRenameContribution.get(editor); + if (controller) { + return Promise.resolve(controller.run(editor.getPosition(), true)); + } + return Promise.resolve(); + } +} + +const OnTypeRenameCommand = EditorCommand.bindToContribution(OnTypeRenameContribution.get); +registerEditorCommand(new OnTypeRenameCommand({ + id: 'cancelOnTypeRenameInput', + precondition: CONTEXT_ONTYPE_RENAME_INPUT_VISIBLE, + handler: x => x.stopAll(), + kbOpts: { + kbExpr: EditorContextKeys.editorTextFocus, + weight: KeybindingWeight.EditorContrib + 99, + primary: KeyCode.Escape, + secondary: [KeyMod.Shift | KeyCode.Escape] + } +})); + + +export function getOnTypeRenameRanges(model: ITextModel, position: Position, token: CancellationToken): Promise<{ + ranges: IRange[], + stopPattern?: RegExp +} | undefined | null> { + const orderedByScore = OnTypeRenameProviderRegistry.ordered(model); + + // in order of score ask the occurrences provider + // until someone response with a good result + // (good = none empty array) + return first<{ + ranges: IRange[], + stopPattern?: RegExp + } | undefined>(orderedByScore.map(provider => () => { + return Promise.resolve(provider.provideOnTypeRenameRanges(model, position, token)).then((ranges) => { + if (!ranges) { + return undefined; + } + + return { + ranges, + stopPattern: provider.stopPattern + }; + }, (err) => { + onUnexpectedExternalError(err); + return undefined; + }); + + }), result => !!result && arrays.isNonEmptyArray(result?.ranges)); +} + + +registerModelAndPositionCommand('_executeRenameOnTypeProvider', (model, position) => getOnTypeRenameRanges(model, position, CancellationToken.None)); + +registerEditorContribution(OnTypeRenameContribution.ID, OnTypeRenameContribution); +registerEditorAction(OnTypeRenameAction); diff --git a/src/vs/editor/contrib/rename/renameInputField.ts b/src/vs/editor/contrib/rename/renameInputField.ts index 1a3e60e0071..78fafad59a0 100644 --- a/src/vs/editor/contrib/rename/renameInputField.ts +++ b/src/vs/editor/contrib/rename/renameInputField.ts @@ -12,7 +12,7 @@ import { ScrollType } from 'vs/editor/common/editorCommon'; import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { inputBackground, inputBorder, inputForeground, widgetShadow, editorWidgetBackground } from 'vs/platform/theme/common/colorRegistry'; -import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { toggleClass } from 'vs/base/browser/dom'; @@ -53,7 +53,7 @@ export class RenameInputField implements IContentWidget { } })); - this._disposables.add(_themeService.onThemeChange(this._updateStyles, this)); + this._disposables.add(_themeService.onDidColorThemeChange(this._updateStyles, this)); } dispose(): void { @@ -88,12 +88,12 @@ export class RenameInputField implements IContentWidget { this._disposables.add(this._keybindingService.onDidUpdateKeybindings(updateLabel)); this._updateFont(); - this._updateStyles(this._themeService.getTheme()); + this._updateStyles(this._themeService.getColorTheme()); } return this._domNode; } - private _updateStyles(theme: ITheme): void { + private _updateStyles(theme: IColorTheme): void { if (!this._input || !this._domNode) { return; } diff --git a/src/vs/editor/contrib/rename/test/onTypeRename.test.ts b/src/vs/editor/contrib/rename/test/onTypeRename.test.ts new file mode 100644 index 00000000000..f57996f48c3 --- /dev/null +++ b/src/vs/editor/contrib/rename/test/onTypeRename.test.ts @@ -0,0 +1,451 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { DisposableStore } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; +import { Handler } from 'vs/editor/common/editorCommon'; +import * as modes from 'vs/editor/common/modes'; +import { OnTypeRenameContribution } from 'vs/editor/contrib/rename/onTypeRename'; +import { createTestCodeEditor, TestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; +import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; + +const mockFile = URI.parse('test:somefile.ttt'); +const mockFileSelector = { scheme: 'test' }; +const timeout = 30; + +suite('On type rename', () => { + const disposables = new DisposableStore(); + + setup(() => { + disposables.clear(); + }); + + teardown(() => { + disposables.clear(); + }); + + function createMockEditor(text: string | string[]) { + const model = typeof text === 'string' + ? createTextModel(text, undefined, undefined, mockFile) + : createTextModel(text.join('\n'), undefined, undefined, mockFile); + + const editor = createTestCodeEditor({ model }); + disposables.add(model); + disposables.add(editor); + + return editor; + } + + + function testCase( + name: string, + initialState: { text: string | string[], ranges: Range[], stopPattern?: RegExp }, + operations: (editor: TestCodeEditor, contrib: OnTypeRenameContribution) => Promise, + expectedEndText: string | string[] + ) { + test(name, async () => { + disposables.add(modes.OnTypeRenameProviderRegistry.register(mockFileSelector, { + stopPattern: initialState.stopPattern || /^\s/, + + provideOnTypeRenameRanges() { + return initialState.ranges; + } + })); + + const editor = createMockEditor(initialState.text); + const ontypeRenameContribution = editor.registerAndInstantiateContribution( + OnTypeRenameContribution.ID, + OnTypeRenameContribution + ); + + await operations(editor, ontypeRenameContribution); + + return new Promise((resolve) => { + setTimeout(() => { + if (typeof expectedEndText === 'string') { + assert.equal(editor.getModel()!.getValue(), expectedEndText); + } else { + assert.equal(editor.getModel()!.getValue(), expectedEndText.join('\n')); + } + resolve(); + }, timeout); + }); + }); + } + + const state = { + text: '', + ranges: [ + new Range(1, 2, 1, 5), + new Range(1, 8, 1, 11), + ] + }; + + /** + * Simple insertion + */ + testCase('Simple insert - initial', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 2); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Type, { text: 'i' }); + }, ''); + + testCase('Simple insert - middle', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 3); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Type, { text: 'i' }); + }, ''); + + testCase('Simple insert - end', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 5); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Type, { text: 'i' }); + }, ''); + + /** + * Simple insertion - end + */ + testCase('Simple insert end - initial', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 8); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Type, { text: 'i' }); + }, ''); + + testCase('Simple insert end - middle', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 9); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Type, { text: 'i' }); + }, ''); + + testCase('Simple insert end - end', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 11); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Type, { text: 'i' }); + }, ''); + + /** + * Boundary insertion + */ + testCase('Simple insert - out of boundary', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 1); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Type, { text: 'i' }); + }, 'i'); + + testCase('Simple insert - out of boundary 2', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 6); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Type, { text: 'i' }); + }, 'i'); + + testCase('Simple insert - out of boundary 3', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 7); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Type, { text: 'i' }); + }, ''); + + testCase('Simple insert - out of boundary 4', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 12); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Type, { text: 'i' }); + }, 'i'); + + /** + * Insert + Move + */ + testCase('Continuous insert', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 2); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Type, { text: 'i' }); + editor.trigger('keyboard', Handler.Type, { text: 'i' }); + }, ''); + + testCase('Insert - move - insert', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 2); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Type, { text: 'i' }); + editor.setPosition(new Position(1, 4)); + editor.trigger('keyboard', Handler.Type, { text: 'i' }); + }, ''); + + testCase('Insert - move - insert outside region', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 2); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Type, { text: 'i' }); + editor.setPosition(new Position(1, 7)); + editor.trigger('keyboard', Handler.Type, { text: 'i' }); + }, 'i'); + + /** + * Selection insert + */ + testCase('Selection insert - simple', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 2); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.setSelection(new Range(1, 2, 1, 3)); + editor.trigger('keyboard', Handler.Type, { text: 'i' }); + }, ''); + + testCase('Selection insert - whole', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 2); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.setSelection(new Range(1, 2, 1, 5)); + editor.trigger('keyboard', Handler.Type, { text: 'i' }); + }, ''); + + testCase('Selection insert - across boundary', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 2); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.setSelection(new Range(1, 1, 1, 3)); + editor.trigger('keyboard', Handler.Type, { text: 'i' }); + }, 'ioo>'); + + /** + * @todo + * Undefined behavior + */ + // testCase('Selection insert - across two boundary', state, async (editor, ontypeRenameContribution) => { + // const pos = new Position(1, 2); + // editor.setPosition(pos); + // await ontypeRenameContribution.run(pos, true); + // editor.setSelection(new Range(1, 4, 1, 9)); + // editor.trigger('keyboard', Handler.Type, { text: 'i' }); + // }, ''); + + /** + * Break out behavior + */ + testCase('Breakout - type space', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 5); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Type, { text: ' ' }); + }, ''); + + testCase('Breakout - type space then undo', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 5); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Type, { text: ' ' }); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); + }, ''); + + testCase('Breakout - type space in middle', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 4); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Type, { text: ' ' }); + }, ''); + + testCase('Breakout - paste content starting with space', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 5); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Paste, { text: ' i="i"' }); + }, ''); + + testCase('Breakout - paste content starting with space then undo', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 5); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Paste, { text: ' i="i"' }); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); + }, ''); + + testCase('Breakout - paste content starting with space in middle', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 4); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Paste, { text: ' i' }); + }, ''); + + /** + * Break out with custom stopPattern + */ + + const state3 = { + ...state, + stopPattern: /^s/ + }; + + testCase('Breakout with stop pattern - insert', state3, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 2); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Type, { text: 'i' }); + }, ''); + + testCase('Breakout with stop pattern - insert stop char', state3, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 2); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Type, { text: 's' }); + }, ''); + + testCase('Breakout with stop pattern - paste char', state3, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 2); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Paste, { text: 's' }); + }, ''); + + testCase('Breakout with stop pattern - paste string', state3, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 2); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Paste, { text: 'so' }); + }, ''); + + testCase('Breakout with stop pattern - insert at end', state3, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 5); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Type, { text: 's' }); + }, ''); + + /** + * Delete + */ + testCase('Delete - left char', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 5); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', 'deleteLeft', {}); + }, ''); + + testCase('Delete - left char then undo', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 5); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', 'deleteLeft', {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); + }, ''); + + testCase('Delete - left word', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 5); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', 'deleteWordLeft', {}); + }, '<>'); + + testCase('Delete - left word then undo', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 5); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', 'deleteWordLeft', {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); + }, ''); + + /** + * Todo: Fix test + */ + // testCase('Delete - left all', state, async (editor, ontypeRenameContribution) => { + // const pos = new Position(1, 3); + // editor.setPosition(pos); + // await ontypeRenameContribution.run(pos, true); + // editor.trigger('keyboard', 'deleteAllLeft', {}); + // }, '>'); + + /** + * Todo: Fix test + */ + // testCase('Delete - left all then undo', state, async (editor, ontypeRenameContribution) => { + // const pos = new Position(1, 5); + // editor.setPosition(pos); + // await ontypeRenameContribution.run(pos, true); + // editor.trigger('keyboard', 'deleteAllLeft', {}); + // CoreEditingCommands.Undo.runEditorCommand(null, editor, null); + // }, '>'); + + testCase('Delete - left all then undo twice', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 5); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', 'deleteAllLeft', {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); + }, ''); + + testCase('Delete - selection', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 5); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.setSelection(new Range(1, 2, 1, 3)); + editor.trigger('keyboard', 'deleteLeft', {}); + }, ''); + + testCase('Delete - selection across boundary', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 3); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.setSelection(new Range(1, 1, 1, 3)); + editor.trigger('keyboard', 'deleteLeft', {}); + }, 'oo>'); + + /** + * Undo / redo + */ + testCase('Undo/redo - simple undo', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 2); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Type, { text: 'i' }); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); + }, ''); + + testCase('Undo/redo - simple undo/redo', state, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 2); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Type, { text: 'i' }); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); + }, ''); + + /** + * Multi line + */ + const state2 = { + text: [ + '', + '' + ], + ranges: [ + new Range(1, 2, 1, 5), + new Range(2, 3, 2, 6), + ] + }; + + testCase('Multiline insert', state2, async (editor, ontypeRenameContribution) => { + const pos = new Position(1, 2); + editor.setPosition(pos); + await ontypeRenameContribution.run(pos, true); + editor.trigger('keyboard', Handler.Type, { text: 'i' }); + }, [ + '', + '' + ]); +}); diff --git a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts index e56cb2bb72f..4c17969b9cd 100644 --- a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts +++ b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts @@ -50,7 +50,8 @@ suite('SmartSelect', () => { setup(() => { const configurationService = new TestConfigurationService(); - modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService), new TestThemeService(), new NullLogService(), new UndoRedoService(new TestDialogService(), new TestNotificationService())); + const dialogService = new TestDialogService(); + modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService), new TestThemeService(), new NullLogService(), new UndoRedoService(dialogService, new TestNotificationService()), dialogService); mode = new MockJSMode(); }); diff --git a/src/vs/editor/contrib/snippet/snippetVariables.ts b/src/vs/editor/contrib/snippet/snippetVariables.ts index 09fd3ca9806..e614ec9cfee 100644 --- a/src/vs/editor/contrib/snippet/snippetVariables.ts +++ b/src/vs/editor/contrib/snippet/snippetVariables.ts @@ -10,7 +10,7 @@ import { ITextModel } from 'vs/editor/common/model'; import { Selection } from 'vs/editor/common/core/selection'; import { VariableResolver, Variable, Text } from 'vs/editor/contrib/snippet/snippetParser'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; -import { getLeadingWhitespace, commonPrefixLength, isFalsyOrWhitespace, pad, endsWith } from 'vs/base/common/strings'; +import { getLeadingWhitespace, commonPrefixLength, isFalsyOrWhitespace } from 'vs/base/common/strings'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { isSingleFolderWorkspaceIdentifier, toWorkspaceIdentifier, WORKSPACE_EXTENSION, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -244,15 +244,15 @@ export class TimeBasedVariableResolver implements VariableResolver { } else if (name === 'CURRENT_YEAR_SHORT') { return String(new Date().getFullYear()).slice(-2); } else if (name === 'CURRENT_MONTH') { - return pad((new Date().getMonth().valueOf() + 1), 2); + return String(new Date().getMonth().valueOf() + 1).padStart(2, '0'); } else if (name === 'CURRENT_DATE') { - return pad(new Date().getDate().valueOf(), 2); + return String(new Date().getDate().valueOf()).padStart(2, '0'); } else if (name === 'CURRENT_HOUR') { - return pad(new Date().getHours().valueOf(), 2); + return String(new Date().getHours().valueOf()).padStart(2, '0'); } else if (name === 'CURRENT_MINUTE') { - return pad(new Date().getMinutes().valueOf(), 2); + return String(new Date().getMinutes().valueOf()).padStart(2, '0'); } else if (name === 'CURRENT_SECOND') { - return pad(new Date().getSeconds().valueOf(), 2); + return String(new Date().getSeconds().valueOf()).padStart(2, '0'); } else if (name === 'CURRENT_DAY_NAME') { return TimeBasedVariableResolver.dayNames[new Date().getDay()]; } else if (name === 'CURRENT_DAY_NAME_SHORT') { @@ -300,7 +300,7 @@ export class WorkspaceBasedVariableResolver implements VariableResolver { } let filename = path.basename(workspaceIdentifier.configPath.path); - if (endsWith(filename, WORKSPACE_EXTENSION)) { + if (filename.endsWith(WORKSPACE_EXTENSION)) { filename = filename.substr(0, filename.length - WORKSPACE_EXTENSION.length - 1); } return filename; @@ -312,7 +312,7 @@ export class WorkspaceBasedVariableResolver implements VariableResolver { let filename = path.basename(workspaceIdentifier.configPath.path); let folderpath = workspaceIdentifier.configPath.fsPath; - if (endsWith(folderpath, filename)) { + if (folderpath.endsWith(filename)) { folderpath = folderpath.substr(0, folderpath.length - filename.length - 1); } return (folderpath ? normalizeDriveLetter(folderpath) : '/'); diff --git a/src/vs/editor/contrib/snippet/test/snippetController2.old.test.ts b/src/vs/editor/contrib/snippet/test/snippetController2.old.test.ts index 05db9b1f12b..8a1d8bfa9c2 100644 --- a/src/vs/editor/contrib/snippet/test/snippetController2.old.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetController2.old.test.ts @@ -45,7 +45,7 @@ suite('SnippetController', () => { editor.getModel()!.updateOptions({ insertSpaces: false }); - let snippetController = editor.registerAndInstantiateContribution(TestSnippetController.ID, TestSnippetController); + let snippetController = editor.registerAndInstantiateContribution(TestSnippetController.ID, TestSnippetController); let template = [ 'for (var ${1:index}; $1 < ${2:array}.length; $1++) {', '\tvar element = $2[$1];', diff --git a/src/vs/editor/contrib/suggest/suggest.ts b/src/vs/editor/contrib/suggest/suggest.ts index b7fa71fefe7..a7ddbeda964 100644 --- a/src/vs/editor/contrib/suggest/suggest.ts +++ b/src/vs/editor/contrib/suggest/suggest.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { first } from 'vs/base/common/async'; -import { assign } from 'vs/base/common/objects'; import { onUnexpectedExternalError, canceled, isPromiseCanceledError } from 'vs/base/common/errors'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; @@ -98,7 +97,7 @@ export class CompletionItem { this.resolve = (token) => { if (!cached) { cached = Promise.resolve(resolveCompletionItem.call(provider, model, Position.lift(position), completion, token)).then(value => { - assign(completion, value); + Object.assign(completion, value); this.isResolved = true; }, err => { if (isPromiseCanceledError(err)) { diff --git a/src/vs/editor/contrib/suggest/suggestController.ts b/src/vs/editor/contrib/suggest/suggestController.ts index 8d821f00fd0..07790011ffb 100644 --- a/src/vs/editor/contrib/suggest/suggestController.ts +++ b/src/vs/editor/contrib/suggest/suggestController.ts @@ -37,7 +37,6 @@ import { IPosition, Position } from 'vs/editor/common/core/position'; import { TrackedRangeStickiness, ITextModel } from 'vs/editor/common/model'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import * as platform from 'vs/base/common/platform'; -import { SuggestRangeHighlighter } from 'vs/editor/contrib/suggest/suggestRangeHighlighter'; import { MenuRegistry } from 'vs/platform/actions/common/actions'; // sticky suggest widget which doesn't disappear on focus out and such @@ -233,9 +232,6 @@ export class SuggestController implements IEditorContribution { }; this._toDispose.add(this.editor.onDidChangeConfiguration(() => updateFromConfig())); updateFromConfig(); - - // create range highlighter - this._toDispose.add(new SuggestRangeHighlighter(this)); } dispose(): void { @@ -738,6 +734,7 @@ registerEditorCommand(new SuggestCommand({ registerEditorCommand(new SuggestCommand({ id: 'insertBestCompletion', precondition: ContextKeyExpr.and( + EditorContextKeys.textInputFocus, ContextKeyExpr.equals('config.editor.tabCompletion', 'on'), WordContextKey.AtEnd, SuggestContext.Visible.toNegated(), @@ -757,6 +754,7 @@ registerEditorCommand(new SuggestCommand({ registerEditorCommand(new SuggestCommand({ id: 'insertNextSuggestion', precondition: ContextKeyExpr.and( + EditorContextKeys.textInputFocus, ContextKeyExpr.equals('config.editor.tabCompletion', 'on'), SuggestAlternatives.OtherSuggestions, SuggestContext.Visible.toNegated(), @@ -773,6 +771,7 @@ registerEditorCommand(new SuggestCommand({ registerEditorCommand(new SuggestCommand({ id: 'insertPrevSuggestion', precondition: ContextKeyExpr.and( + EditorContextKeys.textInputFocus, ContextKeyExpr.equals('config.editor.tabCompletion', 'on'), SuggestAlternatives.OtherSuggestions, SuggestContext.Visible.toNegated(), diff --git a/src/vs/editor/contrib/suggest/suggestMemory.ts b/src/vs/editor/contrib/suggest/suggestMemory.ts index 20f3b4b352d..9fff61fe466 100644 --- a/src/vs/editor/contrib/suggest/suggestMemory.ts +++ b/src/vs/editor/contrib/suggest/suggestMemory.ts @@ -9,15 +9,18 @@ import { IStorageService, StorageScope, WillSaveStateReason } from 'vs/platform/ import { ITextModel } from 'vs/editor/common/model'; import { IPosition } from 'vs/editor/common/core/position'; import { CompletionItemKind, completionKindFromString } from 'vs/editor/common/modes'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { CompletionItem } from 'vs/editor/contrib/suggest/suggest'; +import { IModeService } from 'vs/editor/common/services/modeService'; export abstract class Memory { + constructor(readonly name: MemMode) { } + select(model: ITextModel, pos: IPosition, items: CompletionItem[]): number { if (items.length === 0) { return 0; @@ -46,6 +49,10 @@ export abstract class Memory { export class NoMemory extends Memory { + constructor() { + super('first'); + } + memorize(model: ITextModel, pos: IPosition, item: CompletionItem): void { // no-op } @@ -67,6 +74,10 @@ export interface MemItem { export class LRUMemory extends Memory { + constructor() { + super('recentlyUsed'); + } + private _cache = new LRUCache(300, 0.66); private _seq = 0; @@ -143,6 +154,10 @@ export class LRUMemory extends Memory { export class PrefixMemory extends Memory { + constructor() { + super('recentlyUsedByPrefix'); + } + private _trie = TernarySearchTree.forStrings(); private _seq = 0; @@ -206,85 +221,86 @@ export class PrefixMemory extends Memory { export type MemMode = 'first' | 'recentlyUsed' | 'recentlyUsedByPrefix'; -export class SuggestMemoryService extends Disposable implements ISuggestMemoryService { +export class SuggestMemoryService implements ISuggestMemoryService { + + private static readonly _strategyCtors = new Map([ + ['recentlyUsedByPrefix', PrefixMemory], + ['recentlyUsed', LRUMemory], + ['first', NoMemory] + ]); + + private static readonly _storagePrefix = 'suggest/memories'; readonly _serviceBrand: undefined; - private readonly _storagePrefix = 'suggest/memories'; private readonly _persistSoon: RunOnceScheduler; - private _mode!: MemMode; - private _shareMem!: boolean; - private _strategy!: Memory; + private readonly _disposables = new DisposableStore(); + + private _strategy?: Memory; constructor( @IStorageService private readonly _storageService: IStorageService, + @IModeService private readonly _modeService: IModeService, @IConfigurationService private readonly _configService: IConfigurationService, ) { - super(); - - const update = () => { - const mode = this._configService.getValue('editor.suggestSelection'); - const share = this._configService.getValue('editor.suggest.shareSuggestSelections'); - this._update(mode, share, false); - }; - - this._persistSoon = this._register(new RunOnceScheduler(() => this._saveState(), 500)); - this._register(_storageService.onWillSaveState(e => { + this._persistSoon = new RunOnceScheduler(() => this._saveState(), 500); + this._disposables.add(_storageService.onWillSaveState(e => { if (e.reason === WillSaveStateReason.SHUTDOWN) { this._saveState(); } })); - - this._register(this._configService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('editor.suggestSelection') || e.affectsConfiguration('editor.suggest.shareSuggestSelections')) { - update(); - } - })); - this._register(this._storageService.onDidChangeStorage(e => { - if (e.scope === StorageScope.GLOBAL && e.key.indexOf(this._storagePrefix) === 0) { - if (!document.hasFocus()) { - // windows that aren't focused have to drop their current - // storage value and accept what's stored now - this._update(this._mode, this._shareMem, true); - } - } - })); - update(); } - private _update(mode: MemMode, shareMem: boolean, force: boolean): void { - if (!force && this._mode === mode && this._shareMem === shareMem) { - return; - } - this._shareMem = shareMem; - this._mode = mode; - this._strategy = mode === 'recentlyUsedByPrefix' ? new PrefixMemory() : mode === 'recentlyUsed' ? new LRUMemory() : new NoMemory(); - - try { - const scope = shareMem ? StorageScope.GLOBAL : StorageScope.WORKSPACE; - const raw = this._storageService.get(`${this._storagePrefix}/${this._mode}`, scope); - if (raw) { - this._strategy.fromJSON(JSON.parse(raw)); - } - } catch (e) { - // things can go wrong with JSON... - } + dispose(): void { + this._disposables.dispose(); + this._persistSoon.dispose(); } memorize(model: ITextModel, pos: IPosition, item: CompletionItem): void { - this._strategy.memorize(model, pos, item); + this._withStrategy(model, pos).memorize(model, pos, item); this._persistSoon.schedule(); } select(model: ITextModel, pos: IPosition, items: CompletionItem[]): number { - return this._strategy.select(model, pos, items); + return this._withStrategy(model, pos).select(model, pos, items); + } + + private _withStrategy(model: ITextModel, pos: IPosition): Memory { + + const mode = this._configService.getValue('editor.suggestSelection', { + overrideIdentifier: this._modeService.getLanguageIdentifier(model.getLanguageIdAtPosition(pos.lineNumber, pos.column))?.language, + resource: model.uri + }); + + if (this._strategy?.name !== mode) { + + this._saveState(); + const ctor = SuggestMemoryService._strategyCtors.get(mode) || NoMemory; + this._strategy = new ctor(); + + try { + const share = this._configService.getValue('editor.suggest.shareSuggestSelections'); + const scope = share ? StorageScope.GLOBAL : StorageScope.WORKSPACE; + const raw = this._storageService.get(`${SuggestMemoryService._storagePrefix}/${mode}`, scope); + if (raw) { + this._strategy.fromJSON(JSON.parse(raw)); + } + } catch (e) { + // things can go wrong with JSON... + } + } + + return this._strategy; } private _saveState() { - const raw = JSON.stringify(this._strategy); - const scope = this._shareMem ? StorageScope.GLOBAL : StorageScope.WORKSPACE; - this._storageService.store(`${this._storagePrefix}/${this._mode}`, raw, scope); + if (this._strategy) { + const share = this._configService.getValue('editor.suggest.shareSuggestSelections'); + const scope = share ? StorageScope.GLOBAL : StorageScope.WORKSPACE; + const raw = JSON.stringify(this._strategy); + this._storageService.store(`${SuggestMemoryService._storagePrefix}/${this._strategy.name}`, raw, scope); + } } } diff --git a/src/vs/editor/contrib/suggest/suggestModel.ts b/src/vs/editor/contrib/suggest/suggestModel.ts index f4607a3b65c..42fb74bfa9c 100644 --- a/src/vs/editor/contrib/suggest/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/suggestModel.ts @@ -511,6 +511,8 @@ export class SuggestModel implements IDisposable { if (!suggestOptions.showFolders) { result.add(CompletionItemKind.Folder); } if (!suggestOptions.showTypeParameters) { result.add(CompletionItemKind.TypeParameter); } if (!suggestOptions.showSnippets) { result.add(CompletionItemKind.Snippet); } + if (!suggestOptions.showUsers) { result.add(CompletionItemKind.User); } + if (!suggestOptions.showIssues) { result.add(CompletionItemKind.Issue); } return result; } diff --git a/src/vs/editor/contrib/suggest/suggestRangeHighlighter.ts b/src/vs/editor/contrib/suggest/suggestRangeHighlighter.ts deleted file mode 100644 index 923e03f09bc..00000000000 --- a/src/vs/editor/contrib/suggest/suggestRangeHighlighter.ts +++ /dev/null @@ -1,128 +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 { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { Range } from 'vs/editor/common/core/range'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; -import { CompletionItem } from 'vs/editor/contrib/suggest/suggest'; -import { IModelDeltaDecoration } from 'vs/editor/common/model'; -import { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; -import { Emitter } from 'vs/base/common/event'; -import { domEvent } from 'vs/base/browser/event'; -import { domContentLoaded } from 'vs/base/browser/dom'; - -export class SuggestRangeHighlighter { - - private readonly _disposables = new DisposableStore(); - - private _decorations: string[] = []; - private _widgetListener?: IDisposable; - private _shiftKeyListener?: IDisposable; - private _currentItem?: CompletionItem; - - constructor(private readonly _controller: SuggestController) { - - this._disposables.add(_controller.model.onDidSuggest(e => { - if (!e.shy) { - const widget = this._controller.widget.getValue(); - const focused = widget.getFocusedItem(); - if (focused) { - this._highlight(focused.item); - } - if (!this._widgetListener) { - this._widgetListener = widget.onDidFocus(e => this._highlight(e.item)); - } - } - })); - - this._disposables.add(_controller.model.onDidCancel(() => { - this._reset(); - })); - } - - dispose(): void { - this._reset(); - this._disposables.dispose(); - dispose(this._widgetListener); - dispose(this._shiftKeyListener); - } - - private _reset(): void { - this._decorations = this._controller.editor.deltaDecorations(this._decorations, []); - if (this._shiftKeyListener) { - this._shiftKeyListener.dispose(); - this._shiftKeyListener = undefined; - } - } - - private _highlight(item: CompletionItem) { - - this._currentItem = item; - const opts = this._controller.editor.getOption(EditorOption.suggest); - let newDeco: IModelDeltaDecoration[] = []; - - if (opts.insertHighlight) { - if (!this._shiftKeyListener) { - this._shiftKeyListener = shiftKey.event(() => this._highlight(this._currentItem!)); - } - - const info = this._controller.getOverwriteInfo(item, shiftKey.isPressed); - const position = this._controller.editor.getPosition()!; - - if (opts.insertMode === 'insert' && info.overwriteAfter > 0) { - // wants inserts but got replace-mode -> highlight AFTER range - newDeco = [{ - range: new Range(position.lineNumber, position.column, position.lineNumber, position.column + info.overwriteAfter), - options: { inlineClassName: 'suggest-insert-unexpected' } - }]; - - } else if (opts.insertMode === 'replace' && info.overwriteAfter === 0) { - // want replace but likely got insert -> highlight AFTER range - const wordInfo = this._controller.editor.getModel()?.getWordAtPosition(position); - if (wordInfo && wordInfo.endColumn > position.column) { - newDeco = [{ - range: new Range(position.lineNumber, position.column, position.lineNumber, wordInfo.endColumn), - options: { inlineClassName: 'suggest-insert-unexpected' } - }]; - } - } - } - - // update editor decorations - this._decorations = this._controller.editor.deltaDecorations(this._decorations, newDeco); - } -} - -const shiftKey = new class ShiftKey extends Emitter { - - private readonly _subscriptions = new DisposableStore(); - private _isPressed: boolean = false; - - constructor() { - super(); - domContentLoaded().then(() => { - this._subscriptions.add(domEvent(document.body, 'keydown')(e => this.isPressed = e.shiftKey)); - this._subscriptions.add(domEvent(document.body, 'keyup')(() => this.isPressed = false)); - this._subscriptions.add(domEvent(document.body, 'mouseleave')(() => this.isPressed = false)); - this._subscriptions.add(domEvent(document.body, 'blur')(() => this.isPressed = false)); - }); - } - - get isPressed(): boolean { - return this._isPressed; - } - - set isPressed(value: boolean) { - if (this._isPressed !== value) { - this._isPressed = value; - this.fire(value); - } - } - - dispose() { - this._subscriptions.dispose(); - super.dispose(); - } -}; diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index 3613e737d07..6973a14bff2 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -25,7 +25,7 @@ import { Context as SuggestContext, CompletionItem, suggestWidgetStatusbarMenu } import { CompletionModel } from './completionModel'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { attachListStyler } from 'vs/platform/theme/common/styler'; -import { IThemeService, ITheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { registerColor, editorWidgetBackground, listFocusBackground, activeContrastBorder, listHighlightForeground, editorForeground, editorWidgetBorder, focusBorder, textLinkForeground, textCodeBlockBackground } from 'vs/platform/theme/common/colorRegistry'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; @@ -63,7 +63,7 @@ interface ISuggestionTemplateData { colorspan: HTMLElement; iconLabel: IconLabel; iconContainer: HTMLElement; - signatureLabel: HTMLElement; + parametersLabel: HTMLElement; qualifierLabel: HTMLElement; /** * Showing either `CompletionItem#details` or `CompletionItemLabel#type` @@ -151,7 +151,7 @@ class ItemRenderer implements IListRenderer detailClasses.length ? labelClasses : detailClasses; - } else if (suggestion.kind === CompletionItemKind.Folder && this._themeService.getIconTheme().hasFolderIcons) { + } else if (suggestion.kind === CompletionItemKind.Folder && this._themeService.getFileIconTheme().hasFolderIcons) { // special logic for 'folder' completion items data.icon.className = 'icon hide'; data.iconContainer.className = 'icon hide'; @@ -230,7 +229,7 @@ class ItemRenderer implements IListRenderer= 0) { @@ -240,12 +239,12 @@ class ItemRenderer implements IListRenderer | null = null; @@ -604,6 +604,12 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate false }, mouseSupport: false, + ariaRole: 'listbox', + ariaProvider: { + getRole: () => 'option', + getSetSize: (_: CompletionItem, _index: number, listLength: number) => listLength, + getPosInSet: (_: CompletionItem, index: number) => index, + }, accessibilityProvider: { getAriaLabel: (item: CompletionItem) => { const textLabel = typeof item.completion.label === 'string' ? item.completion.label : item.completion.label.name; @@ -626,12 +632,12 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate this.onThemeChange(t))); + this.toDispose.add(themeService.onDidColorThemeChange(t => this.onThemeChange(t))); this.toDispose.add(editor.onDidLayoutChange(() => this.onEditorLayoutChange())); this.toDispose.add(this.list.onMouseDown(e => this.onListMouseDownOrTap(e))); this.toDispose.add(this.list.onTap(e => this.onListMouseDownOrTap(e))); - this.toDispose.add(this.list.onSelectionChange(e => this.onListSelection(e))); - this.toDispose.add(this.list.onFocusChange(e => this.onListFocus(e))); + this.toDispose.add(this.list.onDidChangeSelection(e => this.onListSelection(e))); + this.toDispose.add(this.list.onDidChangeFocus(e => this.onListFocus(e))); this.toDispose.add(this.editor.onDidChangeCursorSelection(() => this.onCursorSelectionChanged())); this.toDispose.add(this.editor.onDidChangeConfiguration(e => { if (e.hasChanged(EditorOption.suggest)) { @@ -644,10 +650,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate { this._onDetailsKeydown.fire(e); @@ -714,7 +717,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate(ViewportSemanticTokensContribution.ID); + } + + private readonly _editor: ICodeEditor; + private readonly _tokenizeViewport: RunOnceScheduler; + private _outstandingRequests: CancelablePromise[]; + + constructor( + editor: ICodeEditor, + @IModelService private readonly _modelService: IModelService, + @IThemeService private readonly _themeService: IThemeService, + @IConfigurationService private readonly _configurationService: IConfigurationService + ) { + super(); + this._editor = editor; + this._tokenizeViewport = new RunOnceScheduler(() => this._tokenizeViewportNow(), 100); + this._outstandingRequests = []; + this._register(this._editor.onDidScrollChange(() => { + this._tokenizeViewport.schedule(); + })); + this._register(this._editor.onDidChangeModel(() => { + this._cancelAll(); + this._tokenizeViewport.schedule(); + })); + this._register(this._editor.onDidChangeModelContent((e) => { + this._cancelAll(); + this._tokenizeViewport.schedule(); + })); + this._register(DocumentRangeSemanticTokensProviderRegistry.onDidChange(() => { + this._cancelAll(); + this._tokenizeViewport.schedule(); + })); + this._register(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(SEMANTIC_HIGHLIGHTING_SETTING_ID)) { + this._cancelAll(); + this._tokenizeViewport.schedule(); + } + })); + this._register(this._themeService.onDidColorThemeChange(() => { + this._cancelAll(); + this._tokenizeViewport.schedule(); + })); + } + + private static _getSemanticColoringProvider(model: ITextModel): DocumentRangeSemanticTokensProvider | null { + const result = DocumentRangeSemanticTokensProviderRegistry.ordered(model); + return (result.length > 0 ? result[0] : null); + } + + private _cancelAll(): void { + for (const request of this._outstandingRequests) { + request.cancel(); + } + this._outstandingRequests = []; + } + + private _removeOutstandingRequest(req: CancelablePromise): void { + for (let i = 0, len = this._outstandingRequests.length; i < len; i++) { + if (this._outstandingRequests[i] === req) { + this._outstandingRequests.splice(i, 1); + return; + } + } + } + + private _tokenizeViewportNow(): void { + if (!this._editor.hasModel()) { + return; + } + const model = this._editor.getModel(); + if (model.hasSemanticTokens()) { + return; + } + if (!isSemanticColoringEnabled(model, this._themeService, this._configurationService)) { + return; + } + const provider = ViewportSemanticTokensContribution._getSemanticColoringProvider(model); + if (!provider) { + return; + } + const styling = this._modelService.getSemanticTokensProviderStyling(provider); + const visibleRanges = this._editor.getVisibleRangesPlusViewportAboveBelow(); + + this._outstandingRequests = this._outstandingRequests.concat(visibleRanges.map(range => this._requestRange(model, range, provider, styling))); + } + + private _requestRange(model: ITextModel, range: Range, provider: DocumentRangeSemanticTokensProvider, styling: SemanticTokensProviderStyling): CancelablePromise { + const requestVersionId = model.getVersionId(); + const request = createCancelablePromise(token => Promise.resolve(provider.provideDocumentRangeSemanticTokens(model, range, token))); + request.then((r) => { + if (!r || model.isDisposed() || model.getVersionId() !== requestVersionId) { + return; + } + model.setPartialSemanticTokens(range, toMultilineTokens2(r, styling, model.getLanguageIdentifier())); + }).then(() => this._removeOutstandingRequest(request), () => this._removeOutstandingRequest(request)); + return request; + } +} + +registerEditorContribution(ViewportSemanticTokensContribution.ID, ViewportSemanticTokensContribution); diff --git a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts index d2af95af60d..0cd3d0c40a5 100644 --- a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts @@ -401,12 +401,13 @@ class WordHighlighter { private renderDecorations(): void { this.renderDecorationsTimer = -1; let decorations: IModelDeltaDecoration[] = []; - for (let i = 0, len = this.workerRequestValue.length; i < len; i++) { - let info = this.workerRequestValue[i]; - decorations.push({ - range: info.range, - options: WordHighlighter._getDecorationOptions(info.kind) - }); + for (const info of this.workerRequestValue) { + if (info.range) { + decorations.push({ + range: info.range, + options: WordHighlighter._getDecorationOptions(info.kind) + }); + } } this._decorationIds = this.editor.deltaDecorations(this._decorationIds, decorations); diff --git a/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts b/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts index 3981c3bf1f1..70e9c87c680 100644 --- a/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts +++ b/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts @@ -37,7 +37,7 @@ suite('WordPartOperations', () => { test('cursorWordPartLeft - basic', () => { const EXPECTED = [ '|start| |line|', - '|this|Is|A|Camel|Case|Var| |this|_is|_a|_snake|_case|_var| |THIS|_IS|_CAPS|_SNAKE| |this|_IS|Mixed|Use|', + '|this|Is|A|Camel|Case|Var| |this_|is_|a_|snake_|case_|var| |THIS_|IS_|CAPS_|SNAKE| |this_|IS|Mixed|Use|', '|end| |line' ].join('\n'); const [text,] = deserializePipePositions(EXPECTED); @@ -67,7 +67,7 @@ suite('WordPartOperations', () => { }); test('cursorWordPartLeft - issue #53899: underscores', () => { - const EXPECTED = '|myvar| |=| |\'|demonstration|_____of| |selection| |with| |space|\''; + const EXPECTED = '|myvar| |=| |\'|demonstration_____|of| |selection| |with| |space|\''; const [text,] = deserializePipePositions(EXPECTED); const actualStops = testRepeatedActionAndExtractPositions( text, @@ -83,7 +83,7 @@ suite('WordPartOperations', () => { test('cursorWordPartRight - basic', () => { const EXPECTED = [ 'start| |line|', - '|this|Is|A|Camel|Case|Var| |this_|is_|a_|snake_|case_|var| |THIS_|IS_|CAPS_|SNAKE| |this_|IS|Mixed|Use|', + '|this|Is|A|Camel|Case|Var| |this|_is|_a|_snake|_case|_var| |THIS|_IS|_CAPS|_SNAKE| |this|_IS|Mixed|Use|', '|end| |line|' ].join('\n'); const [text,] = deserializePipePositions(EXPECTED); @@ -113,7 +113,7 @@ suite('WordPartOperations', () => { }); test('cursorWordPartRight - issue #53899: underscores', () => { - const EXPECTED = 'myvar| |=| |\'|demonstration_____|of| |selection| |with| |space|\'|'; + const EXPECTED = 'myvar| |=| |\'|demonstration|_____of| |selection| |with| |space|\'|'; const [text,] = deserializePipePositions(EXPECTED); const actualStops = testRepeatedActionAndExtractPositions( text, @@ -145,8 +145,40 @@ suite('WordPartOperations', () => { assert.deepEqual(actual, EXPECTED); }); + test('issue #93239 - cursorWordPartRight', () => { + const EXPECTED = [ + 'foo|_bar|', + ].join('\n'); + const [text,] = deserializePipePositions(EXPECTED); + const actualStops = testRepeatedActionAndExtractPositions( + text, + new Position(1, 1), + ed => cursorWordPartRight(ed), + ed => ed.getPosition()!, + ed => ed.getPosition()!.equals(new Position(1, 8)) + ); + const actual = serializePipePositions(text, actualStops); + assert.deepEqual(actual, EXPECTED); + }); + + test('issue #93239 - cursorWordPartLeft', () => { + const EXPECTED = [ + '|foo_|bar', + ].join('\n'); + const [text,] = deserializePipePositions(EXPECTED); + const actualStops = testRepeatedActionAndExtractPositions( + text, + new Position(1, 8), + ed => cursorWordPartLeft(ed), + ed => ed.getPosition()!, + ed => ed.getPosition()!.equals(new Position(1, 1)) + ); + const actual = serializePipePositions(text, actualStops); + assert.deepEqual(actual, EXPECTED); + }); + test('deleteWordPartLeft - basic', () => { - const EXPECTED = '| |/*| |Just| |some| |text| |a|+=| |3| |+|5|-|3| |*/| |this|Is|A|Camel|Case|Var| |this|_is|_a|_snake|_case|_var| |THIS|_IS|_CAPS|_SNAKE| |this|_IS|Mixed|Use'; + const EXPECTED = '| |/*| |Just| |some| |text| |a|+=| |3| |+|5|-|3| |*/| |this|Is|A|Camel|Case|Var| |this_|is_|a_|snake_|case_|var| |THIS_|IS_|CAPS_|SNAKE| |this_|IS|Mixed|Use'; const [text,] = deserializePipePositions(EXPECTED); const actualStops = testRepeatedActionAndExtractPositions( text, @@ -160,7 +192,7 @@ suite('WordPartOperations', () => { }); test('deleteWordPartRight - basic', () => { - const EXPECTED = ' |/*| |Just| |some| |text| |a|+=| |3| |+|5|-|3| |*/| |this|Is|A|Camel|Case|Var| |this_|is_|a_|snake_|case_|var| |THIS_|IS_|CAPS_|SNAKE| |this_|IS|Mixed|Use|'; + const EXPECTED = ' |/*| |Just| |some| |text| |a|+=| |3| |+|5|-|3| |*/| |this|Is|A|Camel|Case|Var| |this|_is|_a|_snake|_case|_var| |THIS|_IS|_CAPS|_SNAKE| |this|_IS|Mixed|Use|'; const [text,] = deserializePipePositions(EXPECTED); const actualStops = testRepeatedActionAndExtractPositions( text, diff --git a/src/vs/editor/contrib/zoneWidget/zoneWidget.ts b/src/vs/editor/contrib/zoneWidget/zoneWidget.ts index 6a0346a2137..aa731fe46b5 100644 --- a/src/vs/editor/contrib/zoneWidget/zoneWidget.ts +++ b/src/vs/editor/contrib/zoneWidget/zoneWidget.ts @@ -360,10 +360,8 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { const lineHeight = this.editor.getOption(EditorOption.lineHeight); // adjust heightInLines to viewport - const maxHeightInLines = (this.editor.getLayoutInfo().height / lineHeight) * 0.8; - if (heightInLines >= maxHeightInLines) { - heightInLines = maxHeightInLines; - } + const maxHeightInLines = Math.max(12, (this.editor.getLayoutInfo().height / lineHeight) * 0.8); + heightInLines = Math.min(heightInLines, maxHeightInLines); let arrowHeight = 0; let frameThickness = 0; diff --git a/src/vs/editor/editor.all.ts b/src/vs/editor/editor.all.ts index 31a6ad0497b..b1a6c4a8286 100644 --- a/src/vs/editor/editor.all.ts +++ b/src/vs/editor/editor.all.ts @@ -22,21 +22,25 @@ import 'vs/editor/contrib/find/findController'; import 'vs/editor/contrib/folding/folding'; import 'vs/editor/contrib/fontZoom/fontZoom'; import 'vs/editor/contrib/format/formatActions'; +import 'vs/editor/contrib/gotoSymbol/documentSymbols'; import 'vs/editor/contrib/gotoSymbol/goToCommands'; import 'vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition'; import 'vs/editor/contrib/gotoError/gotoError'; import 'vs/editor/contrib/hover/hover'; +import 'vs/editor/contrib/indentation/indentation'; import 'vs/editor/contrib/inPlaceReplace/inPlaceReplace'; import 'vs/editor/contrib/linesOperations/linesOperations'; import 'vs/editor/contrib/links/links'; import 'vs/editor/contrib/multicursor/multicursor'; import 'vs/editor/contrib/parameterHints/parameterHints'; +import 'vs/editor/contrib/rename/onTypeRename'; import 'vs/editor/contrib/rename/rename'; import 'vs/editor/contrib/smartSelect/smartSelect'; import 'vs/editor/contrib/snippet/snippetController2'; import 'vs/editor/contrib/suggest/suggestController'; import 'vs/editor/contrib/tokenization/tokenization'; import 'vs/editor/contrib/toggleTabFocusMode/toggleTabFocusMode'; +import 'vs/editor/contrib/viewportSemanticTokens/viewportSemanticTokens'; import 'vs/editor/contrib/wordHighlighter/wordHighlighter'; import 'vs/editor/contrib/wordOperations/wordOperations'; import 'vs/editor/contrib/wordPartOperations/wordPartOperations'; diff --git a/src/vs/editor/editor.main.ts b/src/vs/editor/editor.main.ts index 2566ecac65b..0dceb4a9e8a 100644 --- a/src/vs/editor/editor.main.ts +++ b/src/vs/editor/editor.main.ts @@ -7,9 +7,10 @@ import 'vs/editor/editor.all'; import 'vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp'; import 'vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard'; import 'vs/editor/standalone/browser/inspectTokens/inspectTokens'; -import 'vs/editor/standalone/browser/quickOpen/gotoLine'; -import 'vs/editor/standalone/browser/quickOpen/quickCommand'; -import 'vs/editor/standalone/browser/quickOpen/quickOutline'; +import 'vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess'; +import 'vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess'; +import 'vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess'; +import 'vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess'; import 'vs/editor/standalone/browser/referenceSearch/standaloneReferenceSearch'; import 'vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast'; diff --git a/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts b/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts index ade5fe6494c..274ad8a2060 100644 --- a/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts +++ b/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./accessibilityHelp'; -import * as browser from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; @@ -330,8 +329,12 @@ class ShowAccessibilityHelpAction extends EditorAction { precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.focus, - primary: (browser.isIE ? KeyMod.CtrlCmd | KeyCode.F1 : KeyMod.Alt | KeyCode.F1), - weight: KeybindingWeight.EditorContrib + primary: KeyMod.Alt | KeyCode.F1, + weight: KeybindingWeight.EditorContrib, + linux: { + primary: KeyMod.Alt | KeyMod.Shift | KeyCode.F1, + secondary: [KeyMod.Alt | KeyCode.F1] + } } }); } diff --git a/src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts b/src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts new file mode 100644 index 00000000000..f4839b34f21 --- /dev/null +++ b/src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts @@ -0,0 +1,76 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Registry } from 'vs/platform/registry/common/platform'; +import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; +import { QuickCommandNLS } from 'vs/editor/common/standaloneStrings'; +import { ICommandQuickPick } from 'vs/platform/quickinput/browser/commandsQuickAccess'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { AbstractEditorCommandsQuickAccessProvider } from 'vs/editor/contrib/quickAccess/commandsQuickAccess'; +import { IEditor } from 'vs/editor/common/editorCommon'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { EditorAction, registerEditorAction } from 'vs/editor/browser/editorExtensions'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; + +export class StandaloneCommandsQuickAccessProvider extends AbstractEditorCommandsQuickAccessProvider { + + protected get activeTextEditorControl(): IEditor | undefined { return withNullAsUndefined(this.codeEditorService.getFocusedCodeEditor()); } + + constructor( + @IInstantiationService instantiationService: IInstantiationService, + @ICodeEditorService private readonly codeEditorService: ICodeEditorService, + @IKeybindingService keybindingService: IKeybindingService, + @ICommandService commandService: ICommandService, + @ITelemetryService telemetryService: ITelemetryService, + @INotificationService notificationService: INotificationService + ) { + super({ showAlias: false }, instantiationService, keybindingService, commandService, telemetryService, notificationService); + } + + protected async getCommandPicks(): Promise> { + return this.getCodeEditorCommandPicks(); + } +} + +Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ + ctor: StandaloneCommandsQuickAccessProvider, + prefix: StandaloneCommandsQuickAccessProvider.PREFIX, + helpEntries: [{ description: QuickCommandNLS.quickCommandHelp, needsEditor: true }] +}); + +export class GotoLineAction extends EditorAction { + + constructor() { + super({ + id: 'editor.action.quickCommand', + label: QuickCommandNLS.quickCommandActionLabel, + alias: 'Command Palette', + precondition: undefined, + kbOpts: { + kbExpr: EditorContextKeys.focus, + primary: KeyCode.F1, + weight: KeybindingWeight.EditorContrib + }, + contextMenuOpts: { + group: 'z_commands', + order: 1 + } + }); + } + + run(accessor: ServicesAccessor): void { + accessor.get(IQuickInputService).quickAccess.show(StandaloneCommandsQuickAccessProvider.PREFIX); + } +} + +registerEditorAction(GotoLineAction); diff --git a/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts new file mode 100644 index 00000000000..75d54811065 --- /dev/null +++ b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts @@ -0,0 +1,60 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AbstractGotoLineQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoLineQuickAccess'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { GoToLineNLS } from 'vs/editor/common/standaloneStrings'; +import { Event } from 'vs/base/common/event'; +import { EditorAction, registerEditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; + +export class StandaloneGotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProvider { + + protected readonly onDidActiveTextEditorControlChange = Event.None; + + constructor(@ICodeEditorService private readonly editorService: ICodeEditorService) { + super(); + } + + protected get activeTextEditorControl() { + return withNullAsUndefined(this.editorService.getFocusedCodeEditor()); + } +} + +Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ + ctor: StandaloneGotoLineQuickAccessProvider, + prefix: StandaloneGotoLineQuickAccessProvider.PREFIX, + helpEntries: [{ description: GoToLineNLS.gotoLineActionLabel, needsEditor: true }] +}); + +export class GotoLineAction extends EditorAction { + + constructor() { + super({ + id: 'editor.action.gotoLine', + label: GoToLineNLS.gotoLineActionLabel, + alias: 'Go to Line/Column...', + precondition: undefined, + kbOpts: { + kbExpr: EditorContextKeys.focus, + primary: KeyMod.CtrlCmd | KeyCode.KEY_G, + mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_G }, + weight: KeybindingWeight.EditorContrib + } + }); + } + + run(accessor: ServicesAccessor): void { + accessor.get(IQuickInputService).quickAccess.show(StandaloneGotoLineQuickAccessProvider.PREFIX); + } +} + +registerEditorAction(GotoLineAction); diff --git a/src/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.ts b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.ts new file mode 100644 index 00000000000..b5850c3f761 --- /dev/null +++ b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.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. + *--------------------------------------------------------------------------------------------*/ + +import { AbstractGotoSymbolQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoSymbolQuickAccess'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { QuickOutlineNLS } from 'vs/editor/common/standaloneStrings'; +import { Event } from 'vs/base/common/event'; +import { EditorAction, registerEditorAction } from 'vs/editor/browser/editorExtensions'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; + +export class StandaloneGotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccessProvider { + + protected readonly onDidActiveTextEditorControlChange = Event.None; + + constructor(@ICodeEditorService private readonly editorService: ICodeEditorService) { + super(); + } + + protected get activeTextEditorControl() { + return withNullAsUndefined(this.editorService.getFocusedCodeEditor()); + } +} + +Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ + ctor: StandaloneGotoSymbolQuickAccessProvider, + prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX, + helpEntries: [ + { description: QuickOutlineNLS.quickOutlineActionLabel, prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX, needsEditor: true }, + { description: QuickOutlineNLS.quickOutlineByCategoryActionLabel, prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX_BY_CATEGORY, needsEditor: true } + ] +}); + +export class GotoLineAction extends EditorAction { + + constructor() { + super({ + id: 'editor.action.quickOutline', + label: QuickOutlineNLS.quickOutlineActionLabel, + alias: 'Go to Symbol...', + precondition: EditorContextKeys.hasDocumentSymbolProvider, + kbOpts: { + kbExpr: EditorContextKeys.focus, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_O, + weight: KeybindingWeight.EditorContrib + }, + contextMenuOpts: { + group: 'navigation', + order: 3 + } + }); + } + + run(accessor: ServicesAccessor): void { + accessor.get(IQuickInputService).quickAccess.show(AbstractGotoSymbolQuickAccessProvider.PREFIX); + } +} + +registerEditorAction(GotoLineAction); diff --git a/src/vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess.ts b/src/vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess.ts new file mode 100644 index 00000000000..9eecd4a9a4c --- /dev/null +++ b/src/vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess.ts @@ -0,0 +1,15 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Registry } from 'vs/platform/registry/common/platform'; +import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; +import { QuickHelpNLS } from 'vs/editor/common/standaloneStrings'; +import { HelpQuickAccessProvider } from 'vs/platform/quickinput/browser/helpQuickAccess'; + +Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ + ctor: HelpQuickAccessProvider, + prefix: '', + helpEntries: [{ description: QuickHelpNLS.helpQuickAccessActionLabel, needsEditor: true }] +}); diff --git a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInput.css b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInput.css new file mode 100644 index 00000000000..c95fe332ef5 --- /dev/null +++ b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInput.css @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.quick-input-widget { + font-size: 13px; +} + +.quick-input-widget .monaco-highlighted-label .highlight, +.quick-input-widget .monaco-highlighted-label .highlight { + color: #0066BF; +} + +.vs-dark .quick-input-widget .monaco-highlighted-label .highlight, +.vs-dark .quick-input-widget .monaco-highlighted-label .highlight { + color: #0097fb; +} + +.hc-black .quick-input-widget .monaco-highlighted-label .highlight, +.hc-black .quick-input-widget .monaco-highlighted-label .highlight { + color: #F38518; +} diff --git a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputServiceImpl.ts b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputServiceImpl.ts new file mode 100644 index 00000000000..f409b0df555 --- /dev/null +++ b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputServiceImpl.ts @@ -0,0 +1,180 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./standaloneQuickInput'; +import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; +import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IQuickInputService, IQuickInputButton, IQuickPickItem, IQuickPick, IInputBox, IQuickNavigateConfiguration, IPickOptions, QuickPickInput, IInputOptions } from 'vs/platform/quickinput/common/quickInput'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { QuickInputController } from 'vs/base/parts/quickinput/browser/quickInput'; +import { QuickInputService, IQuickInputControllerHost } from 'vs/platform/quickinput/browser/quickInput'; +import { once } from 'vs/base/common/functional'; +import { IQuickAccessController } from 'vs/platform/quickinput/common/quickAccess'; + +export class EditorScopedQuickInputServiceImpl extends QuickInputService { + + private host: IQuickInputControllerHost | undefined = undefined; + + constructor( + editor: ICodeEditor, + @IInstantiationService instantiationService: IInstantiationService, + @IContextKeyService contextKeyService: IContextKeyService, + @IThemeService themeService: IThemeService, + @IAccessibilityService accessibilityService: IAccessibilityService, + @ILayoutService layoutService: ILayoutService + ) { + super(instantiationService, contextKeyService, themeService, accessibilityService, layoutService); + + // Use the passed in code editor as host for the quick input widget + const contribution = QuickInputEditorContribution.get(editor); + this.host = { + _serviceBrand: undefined, + get container() { return contribution.widget.getDomNode(); }, + get dimension() { return editor.getLayoutInfo(); }, + get onLayout() { return editor.onDidLayoutChange; }, + focus: () => editor.focus() + }; + } + + protected createController(): QuickInputController { + return super.createController(this.host); + } +} + +export class StandaloneQuickInputServiceImpl implements IQuickInputService { + + _serviceBrand: undefined; + + private mapEditorToService = new Map(); + private get activeService(): IQuickInputService { + const editor = this.codeEditorService.getFocusedCodeEditor(); + if (!editor) { + throw new Error('Quick input service needs a focused editor to work.'); + } + + // Find the quick input implementation for the focused + // editor or create it lazily if not yet created + let quickInputService = this.mapEditorToService.get(editor); + if (!quickInputService) { + const newQuickInputService = quickInputService = this.instantiationService.createInstance(EditorScopedQuickInputServiceImpl, editor); + this.mapEditorToService.set(editor, quickInputService); + + once(editor.onDidDispose)(() => { + newQuickInputService.dispose(); + this.mapEditorToService.delete(editor); + }); + } + + return quickInputService; + } + + get quickAccess(): IQuickAccessController { return this.activeService.quickAccess; } + + get backButton(): IQuickInputButton { return this.activeService.backButton; } + + get onShow() { return this.activeService.onShow; } + get onHide() { return this.activeService.onHide; } + + constructor( + @IInstantiationService private readonly instantiationService: IInstantiationService, + @ICodeEditorService private readonly codeEditorService: ICodeEditorService + ) { + } + + pick>(picks: Promise[]> | QuickPickInput[], options: O = {}, token: CancellationToken = CancellationToken.None): Promise { + return (this.activeService as unknown as QuickInputController /* TS fail */).pick(picks, options, token); + } + + input(options?: IInputOptions | undefined, token?: CancellationToken | undefined): Promise { + return this.activeService.input(options, token); + } + + createQuickPick(): IQuickPick { + return this.activeService.createQuickPick(); + } + + createInputBox(): IInputBox { + return this.activeService.createInputBox(); + } + + focus(): void { + return this.activeService.focus(); + } + + toggle(): void { + return this.activeService.toggle(); + } + + navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration | undefined): void { + return this.activeService.navigate(next, quickNavigate); + } + + accept(): Promise { + return this.activeService.accept(); + } + + back(): Promise { + return this.activeService.back(); + } + + cancel(): Promise { + return this.activeService.cancel(); + } +} + +export class QuickInputEditorContribution implements IEditorContribution { + + static readonly ID = 'editor.controller.quickInput'; + + static get(editor: ICodeEditor): QuickInputEditorContribution { + return editor.getContribution(QuickInputEditorContribution.ID); + } + + readonly widget = new QuickInputEditorWidget(this.editor); + + constructor(private editor: ICodeEditor) { } + + dispose(): void { + this.widget.dispose(); + } +} + +export class QuickInputEditorWidget implements IOverlayWidget { + + private static readonly ID = 'editor.contrib.quickInputWidget'; + + private domNode: HTMLElement; + + constructor(private codeEditor: ICodeEditor) { + this.domNode = document.createElement('div'); + + this.codeEditor.addOverlayWidget(this); + } + + getId(): string { + return QuickInputEditorWidget.ID; + } + + getDomNode(): HTMLElement { + return this.domNode; + } + + getPosition(): IOverlayWidgetPosition | null { + return { preference: OverlayWidgetPositionPreference.TOP_CENTER }; + } + + dispose(): void { + this.codeEditor.removeOverlayWidget(this); + } +} + +registerEditorContribution(QuickInputEditorContribution.ID, QuickInputEditorContribution); diff --git a/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.css b/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.css deleted file mode 100644 index fdc3b7e9a24..00000000000 --- a/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.css +++ /dev/null @@ -1,19 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-quick-open-widget .monaco-tree .monaco-tree-row .monaco-highlighted-label .highlight, -.monaco-quick-open-widget .monaco-list .monaco-list-row .monaco-highlighted-label .highlight { - color: #0066BF; -} - -.vs-dark .monaco-quick-open-widget .monaco-tree .monaco-tree-row .monaco-highlighted-label .highlight, -.vs-dark .monaco-quick-open-widget .monaco-list .monaco-list-row .monaco-highlighted-label .highlight { - color: #0097fb; -} - -.hc-black .monaco-quick-open-widget .monaco-tree .monaco-tree-row .monaco-highlighted-label .highlight, -.hc-black .monaco-quick-open-widget .monaco-list .monaco-list-row .monaco-highlighted-label .highlight { - color: #F38518; -} \ No newline at end of file diff --git a/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts b/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts deleted file mode 100644 index b5d6aea1470..00000000000 --- a/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts +++ /dev/null @@ -1,172 +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 'vs/css!./editorQuickOpen'; -import { QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorAction, IActionOptions, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import { Range } from 'vs/editor/common/core/range'; -import { Selection } from 'vs/editor/common/core/selection'; -import { IEditorContribution, ScrollType, IEditor } from 'vs/editor/common/editorCommon'; -import { IModelDeltaDecoration } from 'vs/editor/common/model'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { QuickOpenEditorWidget } from 'vs/editor/standalone/browser/quickOpen/quickOpenEditorWidget'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; - -export interface IQuickOpenControllerOpts { - inputAriaLabel: string; - getModel(value: string): QuickOpenModel; - getAutoFocus(searchValue: string): IAutoFocus; -} - -export class QuickOpenController implements IEditorContribution, IDecorator { - - public static readonly ID = 'editor.controller.quickOpenController'; - - public static get(editor: ICodeEditor): QuickOpenController { - return editor.getContribution(QuickOpenController.ID); - } - - private readonly editor: ICodeEditor; - private widget: QuickOpenEditorWidget | null = null; - private rangeHighlightDecorationId: string | null = null; - private lastKnownEditorSelection: Selection | null = null; - - constructor(editor: ICodeEditor, @IThemeService private readonly themeService: IThemeService) { - this.editor = editor; - } - - public dispose(): void { - // Dispose widget - if (this.widget) { - this.widget.destroy(); - this.widget = null; - } - } - - public run(opts: IQuickOpenControllerOpts): void { - if (this.widget) { - this.widget.destroy(); - this.widget = null; - } - - // Create goto line widget - let onClose = (canceled: boolean) => { - // Clear Highlight Decorations if present - this.clearDecorations(); - - // Restore selection if canceled - if (canceled && this.lastKnownEditorSelection) { - this.editor.setSelection(this.lastKnownEditorSelection); - this.editor.revealRangeInCenterIfOutsideViewport(this.lastKnownEditorSelection, ScrollType.Smooth); - } - - this.lastKnownEditorSelection = null; - - // Return focus to the editor if - // - focus is back on the element because no other focusable element was clicked - // - a command was picked from the picker which indicates the editor should get focused - if (document.activeElement === document.body || !canceled) { - this.editor.focus(); - } - }; - - this.widget = new QuickOpenEditorWidget( - this.editor, - () => onClose(false), - () => onClose(true), - (value: string) => { - this.widget!.setInput(opts.getModel(value), opts.getAutoFocus(value)); - }, - { - inputAriaLabel: opts.inputAriaLabel - }, - this.themeService - ); - - // Remember selection to be able to restore on cancel - if (!this.lastKnownEditorSelection) { - this.lastKnownEditorSelection = this.editor.getSelection(); - } - - // Show - this.widget.show(''); - } - - private static readonly _RANGE_HIGHLIGHT_DECORATION = ModelDecorationOptions.register({ - className: 'rangeHighlight', - isWholeLine: true - }); - - public decorateLine(range: Range, editor: ICodeEditor): void { - const oldDecorations: string[] = []; - if (this.rangeHighlightDecorationId) { - oldDecorations.push(this.rangeHighlightDecorationId); - this.rangeHighlightDecorationId = null; - } - - const newDecorations: IModelDeltaDecoration[] = [ - { - range: range, - options: QuickOpenController._RANGE_HIGHLIGHT_DECORATION - } - ]; - - const decorations = editor.deltaDecorations(oldDecorations, newDecorations); - this.rangeHighlightDecorationId = decorations[0]; - } - - public clearDecorations(): void { - if (this.rangeHighlightDecorationId) { - this.editor.deltaDecorations([this.rangeHighlightDecorationId], []); - this.rangeHighlightDecorationId = null; - } - } -} - -export interface IQuickOpenOpts { - /** - * provide the quick open model for the given search value. - */ - getModel(value: string): QuickOpenModel; - - /** - * provide the quick open auto focus mode for the given search value. - */ - getAutoFocus(searchValue: string): IAutoFocus; -} - -/** - * Base class for providing quick open in the editor. - */ -export abstract class BaseEditorQuickOpenAction extends EditorAction { - - private readonly _inputAriaLabel: string; - - constructor(inputAriaLabel: string, opts: IActionOptions) { - super(opts); - this._inputAriaLabel = inputAriaLabel; - } - - protected getController(editor: ICodeEditor): QuickOpenController { - return QuickOpenController.get(editor); - } - - protected _show(controller: QuickOpenController, opts: IQuickOpenOpts): void { - controller.run({ - inputAriaLabel: this._inputAriaLabel, - getModel: (value: string): QuickOpenModel => opts.getModel(value), - getAutoFocus: (searchValue: string): IAutoFocus => opts.getAutoFocus(searchValue) - }); - } -} - -export interface IDecorator { - decorateLine(range: Range, editor: IEditor): void; - clearDecorations(): void; -} - -registerEditorContribution(QuickOpenController.ID, QuickOpenController); diff --git a/src/vs/editor/standalone/browser/quickOpen/gotoLine.ts b/src/vs/editor/standalone/browser/quickOpen/gotoLine.ts deleted file mode 100644 index 5274a3ae2c3..00000000000 --- a/src/vs/editor/standalone/browser/quickOpen/gotoLine.ts +++ /dev/null @@ -1,177 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import 'vs/css!./gotoLine'; -import * as strings from 'vs/base/common/strings'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { QuickOpenEntry, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { IAutoFocus, Mode, IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen'; -import { ICodeEditor, IDiffEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions'; -import { Position } from 'vs/editor/common/core/position'; -import { Range } from 'vs/editor/common/core/range'; -import { IEditor, ScrollType } from 'vs/editor/common/editorCommon'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { ITextModel } from 'vs/editor/common/model'; -import { BaseEditorQuickOpenAction, IDecorator } from 'vs/editor/standalone/browser/quickOpen/editorQuickOpen'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { GoToLineNLS } from 'vs/editor/common/standaloneStrings'; - -interface ParseResult { - position: Position; - isValid: boolean; - label: string; -} - -export class GotoLineEntry extends QuickOpenEntry { - private readonly parseResult: ParseResult; - private readonly decorator: IDecorator; - private readonly editor: IEditor; - - constructor(line: string, editor: IEditor, decorator: IDecorator) { - super(); - - this.editor = editor; - this.decorator = decorator; - this.parseResult = this.parseInput(line); - } - - private parseInput(line: string): ParseResult { - const numbers = line.split(',').map(part => parseInt(part, 10)).filter(part => !isNaN(part)); - let position: Position; - - if (numbers.length === 0) { - position = new Position(-1, -1); - } else if (numbers.length === 1) { - position = new Position(numbers[0], 1); - } else { - position = new Position(numbers[0], numbers[1]); - } - - let model: ITextModel | null; - if (isCodeEditor(this.editor)) { - model = this.editor.getModel(); - } else { - const diffModel = (this.editor).getModel(); - model = diffModel ? diffModel.modified : null; - } - - const isValid = model ? model.validatePosition(position).equals(position) : false; - let label: string; - - if (isValid) { - if (position.column && position.column > 1) { - label = strings.format(GoToLineNLS.gotoLineLabelValidLineAndColumn, position.lineNumber, position.column); - } else { - label = strings.format(GoToLineNLS.gotoLineLabelValidLine, position.lineNumber); - } - } else if (position.lineNumber < 1 || position.lineNumber > (model ? model.getLineCount() : 0)) { - label = strings.format(GoToLineNLS.gotoLineLabelEmptyWithLineLimit, model ? model.getLineCount() : 0); - } else { - label = strings.format(GoToLineNLS.gotoLineLabelEmptyWithLineAndColumnLimit, model ? model.getLineMaxColumn(position.lineNumber) : 0); - } - - return { - position: position, - isValid: isValid, - label: label - }; - } - - getLabel(): string { - return this.parseResult.label; - } - - getAriaLabel(): string { - const position = this.editor.getPosition(); - const currentLine = position ? position.lineNumber : 0; - return strings.format(GoToLineNLS.gotoLineAriaLabel, currentLine, this.parseResult.label); - } - - run(mode: Mode, _context: IEntryRunContext): boolean { - if (mode === Mode.OPEN) { - return this.runOpen(); - } - - return this.runPreview(); - } - - runOpen(): boolean { - - // No-op if range is not valid - if (!this.parseResult.isValid) { - return false; - } - - // Apply selection and focus - const range = this.toSelection(); - (this.editor).setSelection(range); - (this.editor).revealRangeInCenter(range, ScrollType.Smooth); - this.editor.focus(); - - return true; - } - - runPreview(): boolean { - - // No-op if range is not valid - if (!this.parseResult.isValid) { - this.decorator.clearDecorations(); - return false; - } - - // Select Line Position - const range = this.toSelection(); - this.editor.revealRangeInCenter(range, ScrollType.Smooth); - - // Decorate if possible - this.decorator.decorateLine(range, this.editor); - - return false; - } - - private toSelection(): Range { - return new Range( - this.parseResult.position.lineNumber, - this.parseResult.position.column, - this.parseResult.position.lineNumber, - this.parseResult.position.column - ); - } -} - -export class GotoLineAction extends BaseEditorQuickOpenAction { - - constructor() { - super(GoToLineNLS.gotoLineActionInput, { - id: 'editor.action.gotoLine', - label: GoToLineNLS.gotoLineActionLabel, - alias: 'Go to Line...', - precondition: undefined, - kbOpts: { - kbExpr: EditorContextKeys.focus, - primary: KeyMod.CtrlCmd | KeyCode.KEY_G, - mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_G }, - weight: KeybindingWeight.EditorContrib - } - }); - } - - run(accessor: ServicesAccessor, editor: ICodeEditor): void { - this._show(this.getController(editor), { - getModel: (value: string): QuickOpenModel => { - return new QuickOpenModel([new GotoLineEntry(value, editor, this.getController(editor))]); - }, - - getAutoFocus: (searchValue: string): IAutoFocus => { - return { - autoFocusFirstEntry: searchValue.length > 0 - }; - } - }); - } -} - -registerEditorAction(GotoLineAction); diff --git a/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts b/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts deleted file mode 100644 index d00c36ea973..00000000000 --- a/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts +++ /dev/null @@ -1,145 +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 strings from 'vs/base/common/strings'; -import * as browser from 'vs/base/browser/browser'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { matchesFuzzy } from 'vs/base/common/filters'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IHighlight, QuickOpenEntryGroup, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { IAutoFocus, Mode, IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions'; -import { IEditor, IEditorAction } from 'vs/editor/common/editorCommon'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { BaseEditorQuickOpenAction } from 'vs/editor/standalone/browser/quickOpen/editorQuickOpen'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { QuickCommandNLS } from 'vs/editor/common/standaloneStrings'; - -export class EditorActionCommandEntry extends QuickOpenEntryGroup { - private readonly key: string; - private readonly action: IEditorAction; - private readonly editor: IEditor; - private readonly keyAriaLabel: string; - - constructor(key: string, keyAriaLabel: string, highlights: IHighlight[], action: IEditorAction, editor: IEditor) { - super(); - - this.key = key; - this.keyAriaLabel = keyAriaLabel; - this.setHighlights(highlights); - this.action = action; - this.editor = editor; - } - - public getLabel(): string { - return this.action.label; - } - - public getAriaLabel(): string { - if (this.keyAriaLabel) { - return strings.format(QuickCommandNLS.ariaLabelEntryWithKey, this.getLabel(), this.keyAriaLabel); - } - - return strings.format(QuickCommandNLS.ariaLabelEntry, this.getLabel()); - } - - public getGroupLabel(): string { - return this.key; - } - - public run(mode: Mode, context: IEntryRunContext): boolean { - if (mode === Mode.OPEN) { - - // Use a timeout to give the quick open widget a chance to close itself first - setTimeout(() => { - - // Some actions are enabled only when editor has focus - this.editor.focus(); - - try { - let promise = this.action.run() || Promise.resolve(); - promise.then(undefined, onUnexpectedError); - } catch (error) { - onUnexpectedError(error); - } - }, 50); - - return true; - } - - return false; - } -} - -export class QuickCommandAction extends BaseEditorQuickOpenAction { - - constructor() { - super(QuickCommandNLS.quickCommandActionInput, { - id: 'editor.action.quickCommand', - label: QuickCommandNLS.quickCommandActionLabel, - alias: 'Command Palette', - precondition: undefined, - kbOpts: { - kbExpr: EditorContextKeys.focus, - primary: (browser.isIE ? KeyMod.Alt | KeyCode.F1 : KeyCode.F1), - weight: KeybindingWeight.EditorContrib - }, - contextMenuOpts: { - group: 'z_commands', - order: 1 - } - }); - } - - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { - const keybindingService = accessor.get(IKeybindingService); - - this._show(this.getController(editor), { - getModel: (value: string): QuickOpenModel => { - return new QuickOpenModel(this._editorActionsToEntries(keybindingService, editor, value)); - }, - - getAutoFocus: (searchValue: string): IAutoFocus => { - return { - autoFocusFirstEntry: true, - autoFocusPrefixMatch: searchValue - }; - } - }); - } - - private _sort(elementA: QuickOpenEntryGroup, elementB: QuickOpenEntryGroup): number { - let elementAName = (elementA.getLabel() || '').toLowerCase(); - let elementBName = (elementB.getLabel() || '').toLowerCase(); - - return elementAName.localeCompare(elementBName); - } - - private _editorActionsToEntries(keybindingService: IKeybindingService, editor: ICodeEditor, searchValue: string): EditorActionCommandEntry[] { - let actions: IEditorAction[] = editor.getSupportedActions(); - let entries: EditorActionCommandEntry[] = []; - - for (const action of actions) { - - let keybinding = keybindingService.lookupKeybinding(action.id); - - if (action.label) { - let highlights = matchesFuzzy(searchValue, action.label); - if (highlights) { - entries.push(new EditorActionCommandEntry(keybinding ? keybinding.getLabel() || '' : '', keybinding ? keybinding.getAriaLabel() || '' : '', highlights, action, editor)); - } - } - } - - // Sort by name - entries = entries.sort(this._sort); - - return entries; - } -} - -registerEditorAction(QuickCommandAction); diff --git a/src/vs/editor/standalone/browser/quickOpen/quickOpenEditorWidget.ts b/src/vs/editor/standalone/browser/quickOpen/quickOpenEditorWidget.ts deleted file mode 100644 index 17fcaf121bf..00000000000 --- a/src/vs/editor/standalone/browser/quickOpen/quickOpenEditorWidget.ts +++ /dev/null @@ -1,107 +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 { Dimension } from 'vs/base/browser/dom'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { QuickOpenWidget } from 'vs/base/parts/quickopen/browser/quickOpenWidget'; -import { IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen'; -import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; -import { foreground } from 'vs/platform/theme/common/colorRegistry'; -import { attachQuickOpenStyler } from 'vs/platform/theme/common/styler'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; - -export interface IQuickOpenEditorWidgetOptions { - inputAriaLabel: string; -} - -export class QuickOpenEditorWidget implements IOverlayWidget { - - private static readonly ID = 'editor.contrib.quickOpenEditorWidget'; - - private readonly codeEditor: ICodeEditor; - private readonly themeService: IThemeService; - private visible: boolean | undefined; - private quickOpenWidget: QuickOpenWidget; - private domNode: HTMLElement; - private styler: IDisposable; - - constructor(codeEditor: ICodeEditor, onOk: () => void, onCancel: () => void, onType: (value: string) => void, configuration: IQuickOpenEditorWidgetOptions, themeService: IThemeService) { - this.codeEditor = codeEditor; - this.themeService = themeService; - this.visible = false; - - this.domNode = document.createElement('div'); - - this.quickOpenWidget = new QuickOpenWidget( - this.domNode, - { - onOk: onOk, - onCancel: onCancel, - onType: onType - }, { - inputPlaceHolder: undefined, - inputAriaLabel: configuration.inputAriaLabel, - keyboardSupport: true - } - ); - this.styler = attachQuickOpenStyler(this.quickOpenWidget, this.themeService, { - pickerGroupForeground: foreground - }); - - this.quickOpenWidget.create(); - this.codeEditor.addOverlayWidget(this); - } - - setInput(model: QuickOpenModel, focus: IAutoFocus): void { - this.quickOpenWidget.setInput(model, focus); - } - - getId(): string { - return QuickOpenEditorWidget.ID; - } - - getDomNode(): HTMLElement { - return this.domNode; - } - - destroy(): void { - this.codeEditor.removeOverlayWidget(this); - this.quickOpenWidget.dispose(); - this.styler.dispose(); - } - - isVisible(): boolean { - return !!this.visible; - } - - show(value: string): void { - this.visible = true; - - const editorLayout = this.codeEditor.getLayoutInfo(); - if (editorLayout) { - this.quickOpenWidget.layout(new Dimension(editorLayout.width, editorLayout.height)); - } - - this.quickOpenWidget.show(value); - this.codeEditor.layoutOverlayWidget(this); - } - - hide(): void { - this.visible = false; - this.quickOpenWidget.hide(); - this.codeEditor.layoutOverlayWidget(this); - } - - getPosition(): IOverlayWidgetPosition | null { - if (this.visible) { - return { - preference: OverlayWidgetPositionPreference.TOP_CENTER - }; - } - - return null; - } -} diff --git a/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts b/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts deleted file mode 100644 index 772073939ef..00000000000 --- a/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts +++ /dev/null @@ -1,325 +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 'vs/css!./quickOutline'; -import 'vs/base/browser/ui/codiconLabel/codiconLabel'; // The codicon symbol styles are defined here and must be loaded -import 'vs/editor/contrib/documentSymbols/outlineTree'; // The codicon symbol colors are defined here and must be loaded -import { CancellationToken } from 'vs/base/common/cancellation'; -import { matchesFuzzy } from 'vs/base/common/filters'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import * as strings from 'vs/base/common/strings'; -import { IHighlight, QuickOpenEntryGroup, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { IAutoFocus, Mode, IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions'; -import { IRange, Range } from 'vs/editor/common/core/range'; -import { ScrollType } from 'vs/editor/common/editorCommon'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { DocumentSymbol, DocumentSymbolProviderRegistry, SymbolKinds } from 'vs/editor/common/modes'; -import { getDocumentSymbols } from 'vs/editor/contrib/quickOpen/quickOpen'; -import { BaseEditorQuickOpenAction, IDecorator } from 'vs/editor/standalone/browser/quickOpen/editorQuickOpen'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { QuickOutlineNLS } from 'vs/editor/common/standaloneStrings'; - -let SCOPE_PREFIX = ':'; - -export class SymbolEntry extends QuickOpenEntryGroup { - private readonly name: string; - private readonly type: string; - private readonly description: string | undefined; - private readonly range: Range; - private readonly editor: ICodeEditor; - private readonly decorator: IDecorator; - - constructor(name: string, type: string, description: string | undefined, range: Range, highlights: IHighlight[], editor: ICodeEditor, decorator: IDecorator) { - super(); - - this.name = name; - this.type = type; - this.description = description; - this.range = range; - this.setHighlights(highlights); - this.editor = editor; - this.decorator = decorator; - } - - public getLabel(): string { - return this.name; - } - - public getAriaLabel(): string { - return strings.format(QuickOutlineNLS.entryAriaLabel, this.name); - } - - public getIcon(): string { - return this.type; - } - - public getDescription(): string | undefined { - return this.description; - } - - public getType(): string { - return this.type; - } - - public getRange(): Range { - return this.range; - } - - public run(mode: Mode, context: IEntryRunContext): boolean { - if (mode === Mode.OPEN) { - return this.runOpen(context); - } - - return this.runPreview(); - } - - private runOpen(_context: IEntryRunContext): boolean { - - // Apply selection and focus - let range = this.toSelection(); - this.editor.setSelection(range); - this.editor.revealRangeInCenter(range, ScrollType.Smooth); - this.editor.focus(); - - return true; - } - - private runPreview(): boolean { - - // Select Outline Position - let range = this.toSelection(); - this.editor.revealRangeInCenter(range, ScrollType.Smooth); - - // Decorate if possible - this.decorator.decorateLine(this.range, this.editor); - - return false; - } - - private toSelection(): Range { - return new Range( - this.range.startLineNumber, - this.range.startColumn || 1, - this.range.startLineNumber, - this.range.startColumn || 1 - ); - } -} - -export class QuickOutlineAction extends BaseEditorQuickOpenAction { - - constructor() { - super(QuickOutlineNLS.quickOutlineActionInput, { - id: 'editor.action.quickOutline', - label: QuickOutlineNLS.quickOutlineActionLabel, - alias: 'Go to Symbol...', - precondition: EditorContextKeys.hasDocumentSymbolProvider, - kbOpts: { - kbExpr: EditorContextKeys.focus, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_O, - weight: KeybindingWeight.EditorContrib - }, - contextMenuOpts: { - group: 'navigation', - order: 3 - } - }); - } - - public run(accessor: ServicesAccessor, editor: ICodeEditor) { - if (!editor.hasModel()) { - return undefined; - } - - const model = editor.getModel(); - - if (!DocumentSymbolProviderRegistry.has(model)) { - return undefined; - } - - // Resolve outline - return getDocumentSymbols(model, true, CancellationToken.None).then((result: DocumentSymbol[]) => { - if (result.length === 0) { - return; - } - - this._run(editor, result); - }); - } - - private _run(editor: ICodeEditor, result: DocumentSymbol[]): void { - this._show(this.getController(editor), { - getModel: (value: string): QuickOpenModel => { - return new QuickOpenModel(this.toQuickOpenEntries(editor, result, value)); - }, - - getAutoFocus: (searchValue: string): IAutoFocus => { - // Remove any type pattern (:) from search value as needed - if (searchValue.indexOf(SCOPE_PREFIX) === 0) { - searchValue = searchValue.substr(SCOPE_PREFIX.length); - } - - return { - autoFocusPrefixMatch: searchValue, - autoFocusFirstEntry: !!searchValue - }; - } - }); - } - - private symbolEntry(name: string, type: string, description: string | undefined, range: IRange, highlights: IHighlight[], editor: ICodeEditor, decorator: IDecorator): SymbolEntry { - return new SymbolEntry(name, type, description, Range.lift(range), highlights, editor, decorator); - } - - private toQuickOpenEntries(editor: ICodeEditor, flattened: DocumentSymbol[], searchValue: string): SymbolEntry[] { - const controller = this.getController(editor); - - let results: SymbolEntry[] = []; - - // Convert to Entries - let normalizedSearchValue = searchValue; - if (searchValue.indexOf(SCOPE_PREFIX) === 0) { - normalizedSearchValue = normalizedSearchValue.substr(SCOPE_PREFIX.length); - } - - for (const element of flattened) { - let label = strings.trim(element.name); - - // Check for meatch - let highlights = matchesFuzzy(normalizedSearchValue, label); - if (highlights) { - - // Show parent scope as description - let description: string | undefined = undefined; - if (element.containerName) { - description = element.containerName; - } - - // Add - results.push(this.symbolEntry(label, SymbolKinds.toCssClassName(element.kind), description, element.range, highlights, editor, controller)); - } - } - - // Sort properly if actually searching - if (searchValue) { - if (searchValue.indexOf(SCOPE_PREFIX) === 0) { - results = results.sort(this.sortScoped.bind(this, searchValue.toLowerCase())); - } else { - results = results.sort(this.sortNormal.bind(this, searchValue.toLowerCase())); - } - } - - // Mark all type groups - if (results.length > 0 && searchValue.indexOf(SCOPE_PREFIX) === 0) { - let currentType: string | null = null; - let currentResult: SymbolEntry | null = null; - let typeCounter = 0; - - for (let i = 0; i < results.length; i++) { - let result = results[i]; - - // Found new type - if (currentType !== result.getType()) { - - // Update previous result with count - if (currentResult) { - currentResult.setGroupLabel(this.typeToLabel(currentType || '', typeCounter)); - } - - currentType = result.getType(); - currentResult = result; - typeCounter = 1; - - result.setShowBorder(i > 0); - } - - // Existing type, keep counting - else { - typeCounter++; - } - } - - // Update previous result with count - if (currentResult) { - currentResult.setGroupLabel(this.typeToLabel(currentType || '', typeCounter)); - } - } - - // Mark first entry as outline - else if (results.length > 0) { - results[0].setGroupLabel(strings.format(QuickOutlineNLS._symbols_, results.length)); - } - - return results; - } - - private typeToLabel(type: string, count: number): string { - switch (type) { - case 'module': return strings.format(QuickOutlineNLS._modules_, count); - case 'class': return strings.format(QuickOutlineNLS._class_, count); - case 'interface': return strings.format(QuickOutlineNLS._interface_, count); - case 'method': return strings.format(QuickOutlineNLS._method_, count); - case 'function': return strings.format(QuickOutlineNLS._function_, count); - case 'property': return strings.format(QuickOutlineNLS._property_, count); - case 'variable': return strings.format(QuickOutlineNLS._variable_, count); - case 'var': return strings.format(QuickOutlineNLS._variable2_, count); - case 'constructor': return strings.format(QuickOutlineNLS._constructor_, count); - case 'call': return strings.format(QuickOutlineNLS._call_, count); - } - - return type; - } - - private sortNormal(searchValue: string, elementA: SymbolEntry, elementB: SymbolEntry): number { - let elementAName = elementA.getLabel().toLowerCase(); - let elementBName = elementB.getLabel().toLowerCase(); - - // Compare by name - let r = elementAName.localeCompare(elementBName); - if (r !== 0) { - return r; - } - - // If name identical sort by range instead - let elementARange = elementA.getRange(); - let elementBRange = elementB.getRange(); - return elementARange.startLineNumber - elementBRange.startLineNumber; - } - - private sortScoped(searchValue: string, elementA: SymbolEntry, elementB: SymbolEntry): number { - - // Remove scope char - searchValue = searchValue.substr(SCOPE_PREFIX.length); - - // Sort by type first if scoped search - let elementAType = elementA.getType(); - let elementBType = elementB.getType(); - let r = elementAType.localeCompare(elementBType); - if (r !== 0) { - return r; - } - - // Special sort when searching in scoped mode - if (searchValue) { - let elementAName = elementA.getLabel().toLowerCase(); - let elementBName = elementB.getLabel().toLowerCase(); - - // Compare by name - let r = elementAName.localeCompare(elementBName); - if (r !== 0) { - return r; - } - } - - // Default to sort by range - let elementARange = elementA.getRange(); - let elementBRange = elementB.getRange(); - return elementARange.startLineNumber - elementBRange.startLineNumber; - } -} - -registerEditorAction(QuickOutlineAction); diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 2b7a163f047..37b97c11c88 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -24,7 +24,7 @@ import { TextEdit, WorkspaceEdit, WorkspaceTextEdit } from 'vs/editor/common/mod import { IModelService } from 'vs/editor/common/services/modelService'; import { IResolvedTextEditorModel, ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; import { ITextResourceConfigurationService, ITextResourcePropertiesService, ITextResourceConfigurationChangeEvent } from 'vs/editor/common/services/textResourceConfigurationService'; -import { CommandsRegistry, ICommand, ICommandEvent, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands'; +import { CommandsRegistry, ICommandEvent, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService, IConfigurationModel, IConfigurationValue, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { Configuration, ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; import { IContextKeyService, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; @@ -36,16 +36,17 @@ import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingReso import { IKeybindingItem, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; -import { ILabelService, ResourceLabelFormatter } from 'vs/platform/label/common/label'; +import { ILabelService, ResourceLabelFormatter, IFormatterChangeEvent } from 'vs/platform/label/common/label'; import { INotification, INotificationHandle, INotificationService, IPromptChoice, IPromptOptions, NoOpNotification, IStatusMessageOptions, NotificationsFilter } from 'vs/platform/notification/common/notification'; import { IProgressRunner, IEditorProgressService } from 'vs/platform/progress/common/progress'; import { ITelemetryInfo, ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, WorkbenchState, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { ILayoutService, IDimension } from 'vs/platform/layout/browser/layoutService'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { SimpleServicesNLS } from 'vs/editor/common/standaloneStrings'; import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; import { basename } from 'vs/base/common/resources'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; export class SimpleModel implements IResolvedTextEditorModel { @@ -254,7 +255,6 @@ export class StandaloneCommandService implements ICommandService { _serviceBrand: undefined; private readonly _instantiationService: IInstantiationService; - private readonly _dynamicCommands: { [id: string]: ICommand; }; private readonly _onWillExecuteCommand = new Emitter(); private readonly _onDidExecuteCommand = new Emitter(); @@ -263,19 +263,10 @@ export class StandaloneCommandService implements ICommandService { constructor(instantiationService: IInstantiationService) { this._instantiationService = instantiationService; - this._dynamicCommands = Object.create(null); - } - - public addCommand(command: ICommand): IDisposable { - const { id } = command; - this._dynamicCommands[id] = command; - return toDisposable(() => { - delete this._dynamicCommands[id]; - }); } public executeCommand(id: string, ...args: any[]): Promise { - const command = (CommandsRegistry.getCommand(id) || this._dynamicCommands[id]); + const command = CommandsRegistry.getCommand(id); if (!command) { return Promise.reject(new Error(`command '${id}' not found`)); } @@ -344,15 +335,8 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { })); } - let commandService = this._commandService; - if (commandService instanceof StandaloneCommandService) { - toDispose.add(commandService.addCommand({ - id: commandId, - handler: handler - })); - } else { - throw new Error('Unknown command service!'); - } + toDispose.add(CommandsRegistry.registerCommand(commandId, handler)); + this.updateResolver({ source: KeybindingSource.Default }); return toDispose; @@ -713,8 +697,7 @@ export class SimpleUriLabelService implements ILabelService { _serviceBrand: undefined; - private readonly _onDidRegisterFormatter = new Emitter(); - public readonly onDidChangeFormatters: Event = this._onDidRegisterFormatter.event; + public readonly onDidChangeFormatters: Event = Event.None; public getUriLabel(resource: URI, options?: { relative?: boolean, forceNoTildify?: boolean }): string { if (resource.scheme === 'file') { @@ -749,8 +732,8 @@ export class SimpleLayoutService implements ILayoutService { public onLayout = Event.None; - private _dimension?: IDimension; - get dimension(): IDimension { + private _dimension?: dom.IDimension; + get dimension(): dom.IDimension { if (!this._dimension) { this._dimension = dom.getClientArea(window.document.body); } @@ -762,5 +745,9 @@ export class SimpleLayoutService implements ILayoutService { return this._container; } - constructor(private _container: HTMLElement) { } + focus(): void { + this._codeEditorService.getFocusedCodeEditor()?.focus(); + } + + constructor(private _codeEditorService: ICodeEditorService, private _container: HTMLElement) { } } diff --git a/src/vs/editor/standalone/browser/standalone-tokens.css b/src/vs/editor/standalone/browser/standalone-tokens.css index f2d33b12b5c..6cc916c0aa2 100644 --- a/src/vs/editor/standalone/browser/standalone-tokens.css +++ b/src/vs/editor/standalone/browser/standalone-tokens.css @@ -134,26 +134,6 @@ box-sizing: border-box; } - /* tree */ - .monaco-editor.vs .monaco-tree .monaco-tree-row, - .monaco-editor.vs-dark .monaco-tree .monaco-tree-row { - -ms-high-contrast-adjust: none; - color: windowtext !important; - } - .monaco-editor.vs .monaco-tree .monaco-tree-row.selected, - .monaco-editor.vs-dark .monaco-tree .monaco-tree-row.selected, - .monaco-editor.vs .monaco-tree .monaco-tree-row.focused, - .monaco-editor.vs-dark .monaco-tree .monaco-tree-row.focused { - color: highlighttext !important; - background-color: highlight !important; - } - .monaco-editor.vs .monaco-tree .monaco-tree-row:hover, - .monaco-editor.vs-dark .monaco-tree .monaco-tree-row:hover { - background: transparent !important; - border: 1px solid highlight; - box-sizing: border-box; - } - /* scrollbars */ .monaco-editor.vs .monaco-scrollable-element > .scrollbar, .monaco-editor.vs-dark .monaco-scrollable-element > .scrollbar { diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index b9793e2a86b..6ff1f85c8f7 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as browser from 'vs/base/browser/browser'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { Disposable, IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; @@ -233,11 +232,7 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon ) { options = options || {}; options.ariaLabel = options.ariaLabel || StandaloneCodeEditorNLS.editorViewAccessibleLabel; - options.ariaLabel = options.ariaLabel + ';' + ( - browser.isIE - ? StandaloneCodeEditorNLS.accessibilityHelpMessageIE - : StandaloneCodeEditorNLS.accessibilityHelpMessage - ); + options.ariaLabel = options.ariaLabel + ';' + (StandaloneCodeEditorNLS.accessibilityHelpMessage); super(domElement, options, {}, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService, accessibilityService); if (keybindingService instanceof StandaloneKeybindingService) { diff --git a/src/vs/editor/standalone/browser/standaloneCodeServiceImpl.ts b/src/vs/editor/standalone/browser/standaloneCodeServiceImpl.ts index c52be42a833..cd631b58008 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeServiceImpl.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeServiceImpl.ts @@ -11,7 +11,7 @@ import { CodeEditorServiceImpl } from 'vs/editor/browser/services/codeEditorServ import { IRange } from 'vs/editor/common/core/range'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; export class StandaloneCodeEditorServiceImpl extends CodeEditorServiceImpl { @@ -19,7 +19,7 @@ export class StandaloneCodeEditorServiceImpl extends CodeEditorServiceImpl { return null; // not supported in the standalone case } - public openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { + public openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { if (!source) { return Promise.resolve(null); } @@ -27,7 +27,7 @@ export class StandaloneCodeEditorServiceImpl extends CodeEditorServiceImpl { return Promise.resolve(this.doOpenEditor(source, input)); } - private doOpenEditor(editor: ICodeEditor, input: IResourceInput): ICodeEditor | null { + private doOpenEditor(editor: ICodeEditor, input: IResourceEditorInput): ICodeEditor | null { const model = this.findModel(editor, input.resource); if (!model) { if (input.resource) { diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index 56a723a309e..b95826e9156 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -154,7 +154,7 @@ export class TokenizationSupport2Adapter implements modes.ITokenizationSupport { private _toBinaryTokens(tokens: IToken[], offsetDelta: number): Uint32Array { const languageId = this._languageIdentifier.id; - const tokenTheme = this._standaloneThemeService.getTheme().tokenTheme; + const tokenTheme = this._standaloneThemeService.getColorTheme().tokenTheme; let result: number[] = [], resultLen = 0; let previousStartIndex: number = 0; @@ -391,6 +391,13 @@ export function registerDocumentHighlightProvider(languageId: string, provider: return modes.DocumentHighlightProviderRegistry.register(languageId, provider); } +/** + * Register an on type rename provider. + */ +export function registerOnTypeRenameProvider(languageId: string, provider: modes.OnTypeRenameProvider): IDisposable { + return modes.OnTypeRenameProviderRegistry.register(languageId, provider); +} + /** * Register a definition provider (used by e.g. go to definition). */ @@ -559,6 +566,7 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages { registerHoverProvider: registerHoverProvider, registerDocumentSymbolProvider: registerDocumentSymbolProvider, registerDocumentHighlightProvider: registerDocumentHighlightProvider, + registerOnTypeRenameProvider: registerOnTypeRenameProvider, registerDefinitionProvider: registerDefinitionProvider, registerImplementationProvider: registerImplementationProvider, registerTypeDefinitionProvider: registerTypeDefinitionProvider, diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index b92c8ae812c..9864cf06a14 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -52,6 +52,9 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { BrowserClipboardService } from 'vs/platform/clipboard/browser/clipboardService'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; +import { StandaloneQuickInputServiceImpl } from 'vs/editor/standalone/browser/quickInput/standaloneQuickInputServiceImpl'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; export interface IEditorOverrideServices { [index: string]: any; @@ -154,7 +157,7 @@ export module StaticServices { export const undoRedoService = define(IUndoRedoService, (o) => new UndoRedoService(dialogService.get(o), notificationService.get(o))); - export const modelService = define(IModelService, (o) => new ModelServiceImpl(configurationService.get(o), resourcePropertiesService.get(o), standaloneThemeService.get(o), logService.get(o), undoRedoService.get(o))); + export const modelService = define(IModelService, (o) => new ModelServiceImpl(configurationService.get(o), resourcePropertiesService.get(o), standaloneThemeService.get(o), logService.get(o), undoRedoService.get(o), dialogService.get(o))); export const markerDecorationsService = define(IMarkerDecorationsService, (o) => new MarkerDecorationsService(modelService.get(o), markerService.get(o))); @@ -164,6 +167,8 @@ export module StaticServices { export const storageService = define(IStorageService, () => new InMemoryStorageService()); + export const storageSyncService = define(IStorageKeysSyncRegistryService, () => new StorageKeysSyncRegistryService()); + export const editorWorkerService = define(IEditorWorkerService, (o) => new EditorWorkerServiceImpl(modelService.get(o), resourceConfigurationService.get(o), logService.get(o))); } @@ -206,7 +211,9 @@ export class DynamicStandaloneServices extends Disposable { let keybindingService = ensure(IKeybindingService, () => this._register(new StandaloneKeybindingService(contextKeyService, commandService, telemetryService, notificationService, domElement))); - let layoutService = ensure(ILayoutService, () => new SimpleLayoutService(domElement)); + let layoutService = ensure(ILayoutService, () => new SimpleLayoutService(StaticServices.codeEditorService.get(ICodeEditorService), domElement)); + + ensure(IQuickInputService, () => new StandaloneQuickInputServiceImpl(_instantiationService, StaticServices.codeEditorService.get(ICodeEditorService))); let contextViewService = ensure(IContextViewService, () => this._register(new ContextViewService(layoutService))); diff --git a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts index 2469f2e3b93..3d4db53c943 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts @@ -13,7 +13,7 @@ import { hc_black, vs, vs_dark } from 'vs/editor/standalone/common/themes'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { Registry } from 'vs/platform/registry/common/platform'; import { ColorIdentifier, Extensions, IColorRegistry } from 'vs/platform/theme/common/colorRegistry'; -import { Extensions as ThemingExtensions, ICssStyleCollector, IIconTheme, IThemingRegistry, ITokenStyle } from 'vs/platform/theme/common/themeService'; +import { Extensions as ThemingExtensions, ICssStyleCollector, IFileIconTheme, IThemingRegistry, ITokenStyle } from 'vs/platform/theme/common/themeService'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; const VS_THEME_NAME = 'vs'; @@ -131,13 +131,15 @@ class StandaloneTheme implements IStandaloneTheme { return this._tokenTheme; } - public getTokenStyleMetadata(type: string, modifiers: string[]): ITokenStyle | undefined { + public getTokenStyleMetadata(type: string, modifiers: string[], modelLanguage: string): ITokenStyle | undefined { return undefined; } public get tokenColorMap(): string[] { return []; } + + public readonly semanticHighlighting = false; } function isBuiltinTheme(themeName: string): themeName is BuiltinTheme { @@ -168,11 +170,11 @@ export class StandaloneThemeServiceImpl extends Disposable implements IStandalon _serviceBrand: undefined; - private readonly _onThemeChange = this._register(new Emitter()); - public readonly onThemeChange = this._onThemeChange.event; + private readonly _onColorThemeChange = this._register(new Emitter()); + public readonly onDidColorThemeChange = this._onColorThemeChange.event; - private readonly _onIconThemeChange = this._register(new Emitter()); - public readonly onIconThemeChange = this._onIconThemeChange.event; + private readonly _onFileIconThemeChange = this._register(new Emitter()); + public readonly onDidFileIconThemeChange = this._onFileIconThemeChange.event; private readonly _environment: IEnvironmentService = Object.create(null); private readonly _knownThemes: Map; @@ -250,7 +252,7 @@ export class StandaloneThemeServiceImpl extends Disposable implements IStandalon } } - public getTheme(): IStandaloneTheme { + public getColorTheme(): IStandaloneTheme { return this._theme; } @@ -287,12 +289,12 @@ export class StandaloneThemeServiceImpl extends Disposable implements IStandalon this._styleElements.forEach(styleElement => styleElement.innerHTML = this._css); TokenizationRegistry.setColorMap(colorMap); - this._onThemeChange.fire(theme); + this._onColorThemeChange.fire(theme); return theme.id; } - public getIconTheme(): IIconTheme { + public getFileIconTheme(): IFileIconTheme { return { hasFileIcons: false, hasFolderIcons: false, diff --git a/src/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.ts b/src/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.ts index 22cb5063a94..8de8b606882 100644 --- a/src/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.ts +++ b/src/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.ts @@ -29,7 +29,7 @@ class ToggleHighContrast extends EditorAction { standaloneThemeService.setTheme(this._originalThemeName); this._originalThemeName = null; } else { - this._originalThemeName = standaloneThemeService.getTheme().themeName; + this._originalThemeName = standaloneThemeService.getColorTheme().themeName; standaloneThemeService.setTheme('hc-black'); } } diff --git a/src/vs/editor/standalone/common/monarch/monarchLexer.ts b/src/vs/editor/standalone/common/monarch/monarchLexer.ts index e9db863ee2c..d8bd45ac8de 100644 --- a/src/vs/editor/standalone/common/monarch/monarchLexer.ts +++ b/src/vs/editor/standalone/common/monarch/monarchLexer.ts @@ -463,7 +463,7 @@ export class MonarchTokenizer implements modes.ITokenizationSupport { } public tokenize2(line: string, lineState: modes.IState, offsetDelta: number): TokenizationResult2 { - let tokensCollector = new MonarchModernTokensCollector(this._modeService, this._standaloneThemeService.getTheme().tokenTheme); + let tokensCollector = new MonarchModernTokensCollector(this._modeService, this._standaloneThemeService.getColorTheme().tokenTheme); let endLineState = this._tokenize(line, lineState, offsetDelta, tokensCollector); return tokensCollector.finalize(endLineState); } diff --git a/src/vs/editor/standalone/common/standaloneThemeService.ts b/src/vs/editor/standalone/common/standaloneThemeService.ts index 0fca97422c1..6cef079a639 100644 --- a/src/vs/editor/standalone/common/standaloneThemeService.ts +++ b/src/vs/editor/standalone/common/standaloneThemeService.ts @@ -5,7 +5,7 @@ import { ITokenThemeRule, TokenTheme } from 'vs/editor/common/modes/supports/tokenization'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; export const IStandaloneThemeService = createDecorator('themeService'); @@ -20,7 +20,7 @@ export interface IStandaloneThemeData { colors: IColors; } -export interface IStandaloneTheme extends ITheme { +export interface IStandaloneTheme extends IColorTheme { tokenTheme: TokenTheme; themeName: string; } @@ -32,5 +32,5 @@ export interface IStandaloneThemeService extends IThemeService { defineTheme(themeName: string, themeData: IStandaloneThemeData): void; - getTheme(): IStandaloneTheme; + getColorTheme(): IStandaloneTheme; } diff --git a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts index ead974cac10..07bfa4b14bb 100644 --- a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts +++ b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts @@ -12,7 +12,7 @@ import { TokenTheme } from 'vs/editor/common/modes/supports/tokenization'; import { ILineTokens, IToken, TokenizationSupport2Adapter, TokensProvider } from 'vs/editor/standalone/browser/standaloneLanguages'; import { IStandaloneTheme, IStandaloneThemeData, IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneThemeService'; import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; -import { IIconTheme, ITheme, LIGHT, ITokenStyle } from 'vs/platform/theme/common/themeService'; +import { IFileIconTheme, IColorTheme, LIGHT, ITokenStyle } from 'vs/platform/theme/common/themeService'; suite('TokenizationSupport2Adapter', () => { @@ -40,7 +40,7 @@ suite('TokenizationSupport2Adapter', () => { public defineTheme(themeName: string, themeData: IStandaloneThemeData): void { throw new Error('Not implemented'); } - public getTheme(): IStandaloneTheme { + public getColorTheme(): IStandaloneTheme { return { tokenTheme: new MockTokenTheme(), @@ -56,23 +56,25 @@ suite('TokenizationSupport2Adapter', () => { throw new Error('Not implemented'); }, - getTokenStyleMetadata: (type: string, modifiers: string[]): ITokenStyle | undefined => { + getTokenStyleMetadata: (type: string, modifiers: string[], modelLanguage: string): ITokenStyle | undefined => { return undefined; }, + semanticHighlighting: false, + tokenColorMap: [] }; } - public getIconTheme(): IIconTheme { + public getFileIconTheme(): IFileIconTheme { return { hasFileIcons: false, hasFolderIcons: false, hidesExplorerArrows: false }; } - public readonly onThemeChange = new Emitter().event; - public readonly onIconThemeChange = new Emitter().event; + public readonly onDidColorThemeChange = new Emitter().event; + public readonly onDidFileIconThemeChange = new Emitter().event; } class MockState implements IState { diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index b6984401643..72fcf78f900 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -397,6 +397,25 @@ suite('Editor Controller - Cursor', () => { assertCursor(thisCursor, new Position(1, LINE1.length + 1)); }); + test('issue #44465: cursor position not correct when move', () => { + thisCursor.setSelections('test', [new Selection(1, 5, 1, 5)]); + // going once up on the first line remembers the offset visual columns + moveUp(thisCursor); + assertCursor(thisCursor, new Position(1, 1)); + moveDown(thisCursor); + assertCursor(thisCursor, new Position(2, 2)); + moveUp(thisCursor); + assertCursor(thisCursor, new Position(1, 5)); + + // going twice up on the first line discards the offset visual columns + moveUp(thisCursor); + assertCursor(thisCursor, new Position(1, 1)); + moveUp(thisCursor); + assertCursor(thisCursor, new Position(1, 1)); + moveDown(thisCursor); + assertCursor(thisCursor, new Position(2, 1)); + }); + // --------- move to beginning of line test('move to beginning of line', () => { @@ -1329,7 +1348,7 @@ suite('Editor Controller - Regression tests', () => { CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'Hello world '); - assertCursor(cursor, new Position(1, 13)); + assertCursor(cursor, new Selection(1, 12, 1, 13)); CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'Hello world'); @@ -5044,6 +5063,28 @@ suite('autoClosingPairs', () => { mode.dispose(); }); + test('issue #90016: allow accents on mac US intl keyboard to surround selection', () => { + let mode = new AutoClosingMode(); + usingCursor({ + text: [ + 'test' + ], + languageIdentifier: mode.getLanguageIdentifier() + }, (model, cursor) => { + cursor.setSelections('test', [new Selection(1, 1, 1, 5)]); + + // Typing ` + e on the mac US intl kb layout + cursorCommand(cursor, H.CompositionStart, null, 'keyboard'); + cursorCommand(cursor, H.Type, { text: '\'' }, 'keyboard'); + cursorCommand(cursor, H.ReplacePreviousChar, { replaceCharCnt: 1, text: '\'' }, 'keyboard'); + cursorCommand(cursor, H.ReplacePreviousChar, { replaceCharCnt: 1, text: '\'' }, 'keyboard'); + cursorCommand(cursor, H.CompositionEnd, null, 'keyboard'); + + assert.equal(model.getValue(), '\'test\''); + }); + mode.dispose(); + }); + test('issue #53357: Over typing ignores characters after backslash', () => { let mode = new AutoClosingMode(); usingCursor({ @@ -5575,4 +5616,24 @@ suite('Undo stops', () => { }); }); + test('issue #93585: Undo multi cursor edit corrupts document', () => { + let model = createTextModel( + [ + 'hello world', + 'hello world', + ].join('\n') + ); + + withTestCodeEditor(null, { model: model }, (editor, cursor) => { + cursor.setSelections('test', [ + new Selection(2, 7, 2, 12), + new Selection(1, 7, 1, 12), + ]); + cursorCommand(cursor, H.Type, { text: 'no' }, 'keyboard'); + assert.equal(model.getValue(), 'hello no\nhello no'); + + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); + assert.equal(model.getValue(), 'hello world\nhello world'); + }); + }); }); diff --git a/src/vs/editor/test/browser/editorTestServices.ts b/src/vs/editor/test/browser/editorTestServices.ts index f70b534d96e..747895a7a60 100644 --- a/src/vs/editor/test/browser/editorTestServices.ts +++ b/src/vs/editor/test/browser/editorTestServices.ts @@ -9,13 +9,13 @@ import { AbstractCodeEditorService } from 'vs/editor/browser/services/abstractCo import { IDecorationRenderOptions } from 'vs/editor/common/editorCommon'; import { IModelDecorationOptions } from 'vs/editor/common/model'; import { CommandsRegistry, ICommandEvent, ICommandService } from 'vs/platform/commands/common/commands'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export class TestCodeEditorService extends AbstractCodeEditorService { - public lastInput?: IResourceInput; + public lastInput?: IResourceEditorInput; public getActiveCodeEditor(): ICodeEditor | null { return null; } - public openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { + public openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { this.lastInput = input; return Promise.resolve(null); } diff --git a/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts b/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts index 1bb98526f71..faa5c581cc6 100644 --- a/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts +++ b/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts @@ -4,27 +4,52 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as dom from 'vs/base/browser/dom'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { CodeEditorServiceImpl } from 'vs/editor/browser/services/codeEditorServiceImpl'; +import { CodeEditorServiceImpl, GlobalStyleSheet } from 'vs/editor/browser/services/codeEditorServiceImpl'; import { IDecorationRenderOptions } from 'vs/editor/common/editorCommon'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; -import { TestTheme, TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; +import { TestColorTheme, TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; const themeServiceMock = new TestThemeService(); -export class TestCodeEditorServiceImpl extends CodeEditorServiceImpl { +class TestCodeEditorServiceImpl extends CodeEditorServiceImpl { getActiveCodeEditor(): ICodeEditor | null { return null; } - openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { + openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { return Promise.resolve(null); } } +class TestGlobalStyleSheet extends GlobalStyleSheet { + + public rules: string[] = []; + + constructor() { + super(null!); + } + + public insertRule(rule: string, index?: number): void { + this.rules.unshift(rule); + } + + public removeRulesContainingSelector(ruleName: string): void { + for (let i = 0; i < this.rules.length; i++) { + if (this.rules[i].indexOf(ruleName) >= 0) { + this.rules.splice(i, 1); + i--; + } + } + } + + public read(): string { + return this.rules.join('\n'); + } +} + suite('Decoration Render Options', () => { let options: IDecorationRenderOptions = { gutterIconPath: URI.parse('https://github.com/Microsoft/vscode/blob/master/resources/linux/code.png'), @@ -45,59 +70,45 @@ suite('Decoration Render Options', () => { assert.throws(() => s.resolveDecorationOptions('example', false)); }); - function readStyleSheet(styleSheet: HTMLStyleElement): string { - if ((styleSheet.sheet).rules) { - return Array.prototype.map.call((styleSheet.sheet).rules, (r: { cssText: string }) => r.cssText).join('\n'); - } - return styleSheet.sheet!.toString(); + function readStyleSheet(styleSheet: TestGlobalStyleSheet): string { + return styleSheet.read(); } test('css properties', () => { - let styleSheet = dom.createStyleSheet(); - let s = new TestCodeEditorServiceImpl(themeServiceMock, styleSheet); + const styleSheet = new TestGlobalStyleSheet(); + const s = new TestCodeEditorServiceImpl(themeServiceMock, styleSheet); s.registerDecorationType('example', options); - let sheet = readStyleSheet(styleSheet); - assert( - sheet.indexOf('background: url(\'https://github.com/Microsoft/vscode/blob/master/resources/linux/code.png\') center center no-repeat;') > 0 - || sheet.indexOf('background: url("https://github.com/Microsoft/vscode/blob/master/resources/linux/code.png") center center / contain no-repeat;') > 0 - || sheet.indexOf('background-image: url("https://github.com/Microsoft/vscode/blob/master/resources/linux/code.png"); background-size: contain; background-position: center center; background-repeat: no-repeat no-repeat;') > 0 - ); - assert(sheet.indexOf('border-color: yellow;') > 0); - assert(sheet.indexOf('background-color: red;') > 0); + const sheet = readStyleSheet(styleSheet); + assert(sheet.indexOf(`{background:url('https://github.com/Microsoft/vscode/blob/master/resources/linux/code.png') center center no-repeat;background-size:contain;}`) >= 0); + assert(sheet.indexOf(`{background-color:red;border-color:yellow;box-sizing: border-box;}`) >= 0); }); test('theme color', () => { - let options: IDecorationRenderOptions = { + const options: IDecorationRenderOptions = { backgroundColor: { id: 'editorBackground' }, borderColor: { id: 'editorBorder' }, }; - let colors: { [key: string]: string } = { + + const styleSheet = new TestGlobalStyleSheet(); + const themeService = new TestThemeService(new TestColorTheme({ editorBackground: '#FF0000' - }; - - let styleSheet = dom.createStyleSheet(); - let themeService = new TestThemeService(new TestTheme(colors)); - let s = new TestCodeEditorServiceImpl(themeService, styleSheet); + })); + const s = new TestCodeEditorServiceImpl(themeService, styleSheet); s.registerDecorationType('example', options); - let sheet = readStyleSheet(styleSheet); - assert.equal(sheet, '.monaco-editor .ced-example-0 { background-color: rgb(255, 0, 0); border-color: transparent; box-sizing: border-box; }'); + assert.equal(readStyleSheet(styleSheet), '.monaco-editor .ced-example-0 {background-color:#ff0000;border-color:transparent;box-sizing: border-box;}'); - colors = { + themeService.setTheme(new TestColorTheme({ editorBackground: '#EE0000', editorBorder: '#00FFFF' - }; - themeService.setTheme(new TestTheme(colors)); - sheet = readStyleSheet(styleSheet); - assert.equal(sheet, '.monaco-editor .ced-example-0 { background-color: rgb(238, 0, 0); border-color: rgb(0, 255, 255); box-sizing: border-box; }'); + })); + assert.equal(readStyleSheet(styleSheet), '.monaco-editor .ced-example-0 {background-color:#ee0000;border-color:#00ffff;box-sizing: border-box;}'); s.removeDecorationType('example'); - sheet = readStyleSheet(styleSheet); - assert.equal(sheet, ''); - + assert.equal(readStyleSheet(styleSheet), ''); }); test('theme overrides', () => { - let options: IDecorationRenderOptions = { + const options: IDecorationRenderOptions = { color: { id: 'editorBackground' }, light: { color: '#FF00FF' @@ -109,86 +120,59 @@ suite('Decoration Render Options', () => { } } }; - let colors: { [key: string]: string } = { + + const styleSheet = new TestGlobalStyleSheet(); + const themeService = new TestThemeService(new TestColorTheme({ editorBackground: '#FF0000', infoForeground: '#444444' - }; - - let styleSheet = dom.createStyleSheet(); - let themeService = new TestThemeService(new TestTheme(colors)); - let s = new TestCodeEditorServiceImpl(themeService, styleSheet); + })); + const s = new TestCodeEditorServiceImpl(themeService, styleSheet); s.registerDecorationType('example', options); - let sheet = readStyleSheet(styleSheet); - let expected = - '.vs-dark.monaco-editor .ced-example-4::after, .hc-black.monaco-editor .ced-example-4::after { color: rgb(68, 68, 68) !important; }\n' + - '.vs-dark.monaco-editor .ced-example-1, .hc-black.monaco-editor .ced-example-1 { color: rgb(0, 0, 0) !important; }\n' + - '.vs.monaco-editor .ced-example-1 { color: rgb(255, 0, 255) !important; }\n' + - '.monaco-editor .ced-example-1 { color: rgb(255, 0, 0) !important; }'; - assert.equal(sheet, expected); + const expected = [ + '.vs-dark.monaco-editor .ced-example-4::after, .hc-black.monaco-editor .ced-example-4::after {color:#444444 !important;}', + '.vs-dark.monaco-editor .ced-example-1, .hc-black.monaco-editor .ced-example-1 {color:#000000 !important;}', + '.vs.monaco-editor .ced-example-1 {color:#FF00FF !important;}', + '.monaco-editor .ced-example-1 {color:#ff0000 !important;}' + ].join('\n'); + assert.equal(readStyleSheet(styleSheet), expected); s.removeDecorationType('example'); - sheet = readStyleSheet(styleSheet); - assert.equal(sheet, ''); + assert.equal(readStyleSheet(styleSheet), ''); }); test('css properties, gutterIconPaths', () => { - let styleSheet = dom.createStyleSheet(); + const styleSheet = new TestGlobalStyleSheet(); + const s = new TestCodeEditorServiceImpl(themeServiceMock, styleSheet); - // unix file path (used as string) - let s = new TestCodeEditorServiceImpl(themeServiceMock, styleSheet); - s.registerDecorationType('example', { gutterIconPath: URI.file('/Users/foo/bar.png') }); - let sheet = readStyleSheet(styleSheet);//.innerHTML || styleSheet.sheet.toString(); - assert( - sheet.indexOf('background: url(\'file:///Users/foo/bar.png\') center center no-repeat;') > 0 - || sheet.indexOf('background: url("file:///Users/foo/bar.png") center center no-repeat;') > 0 - || sheet.indexOf('background-image: url("file:///Users/foo/bar.png"); background-position: center center; background-repeat: no-repeat no-repeat;') > 0 - ); + // URI, only minimal encoding + s.registerDecorationType('example', { gutterIconPath: URI.parse('data:image/svg+xml;base64,PHN2ZyB4b+') }); + assert(readStyleSheet(styleSheet).indexOf(`{background:url('data:image/svg+xml;base64,PHN2ZyB4b+') center center no-repeat;}`) > 0); s.removeDecorationType('example'); - // windows file path (used as string) if (platform.isWindows) { - s = new TestCodeEditorServiceImpl(themeServiceMock, styleSheet); + // windows file path (used as string) s.registerDecorationType('example', { gutterIconPath: URI.file('c:\\files\\miles\\more.png') }); - sheet = readStyleSheet(styleSheet); - assert( - sheet.indexOf('background: url(\'file:///c%3A/files/miles/more.png\') center center no-repeat;') > 0 - || sheet.indexOf('background: url("file:///c%3A/files/miles/more.png") center center no-repeat;') > 0 - || sheet.indexOf('background: url("file:///c:/files/miles/more.png") center center no-repeat;') > 0 - || sheet.indexOf('background-image: url("file:///c:/files/miles/more.png"); background-position: center center; background-repeat: no-repeat no-repeat;') > 0 - ); + assert(readStyleSheet(styleSheet).indexOf(`{background:url('file:///c:/files/miles/more.png') center center no-repeat;}`) > 0); + s.removeDecorationType('example'); + + // single quote must always be escaped/encoded + s.registerDecorationType('example', { gutterIconPath: URI.file('c:\\files\\foo\\b\'ar.png') }); + assert(readStyleSheet(styleSheet).indexOf(`{background:url('file:///c:/files/foo/b%27ar.png') center center no-repeat;}`) > 0); + s.removeDecorationType('example'); + } else { + // unix file path (used as string) + s.registerDecorationType('example', { gutterIconPath: URI.file('/Users/foo/bar.png') }); + assert(readStyleSheet(styleSheet).indexOf(`{background:url('file:///Users/foo/bar.png') center center no-repeat;}`) > 0); + s.removeDecorationType('example'); + + // single quote must always be escaped/encoded + s.registerDecorationType('example', { gutterIconPath: URI.file('/Users/foo/b\'ar.png') }); + assert(readStyleSheet(styleSheet).indexOf(`{background:url('file:///Users/foo/b%27ar.png') center center no-repeat;}`) > 0); s.removeDecorationType('example'); } - // URI, only minimal encoding - s = new TestCodeEditorServiceImpl(themeServiceMock, styleSheet); - s.registerDecorationType('example', { gutterIconPath: URI.parse('data:image/svg+xml;base64,PHN2ZyB4b+') }); - sheet = readStyleSheet(styleSheet); - assert( - sheet.indexOf('background: url(\'data:image/svg+xml;base64,PHN2ZyB4b+\') center center no-repeat;') > 0 - || sheet.indexOf('background: url("data:image/svg+xml;base64,PHN2ZyB4b+") center center no-repeat;') > 0 - || sheet.indexOf('background-image: url("data:image/svg+xml;base64,PHN2ZyB4b+"); background-position: center center; background-repeat: no-repeat no-repeat;') > 0 - ); - s.removeDecorationType('example'); - - // single quote must always be escaped/encoded - s = new TestCodeEditorServiceImpl(themeServiceMock, styleSheet); - s.registerDecorationType('example', { gutterIconPath: URI.file('/Users/foo/b\'ar.png') }); - sheet = readStyleSheet(styleSheet); - assert( - sheet.indexOf('background: url(\'file:///Users/foo/b%27ar.png\') center center no-repeat;') > 0 - || sheet.indexOf('background: url("file:///Users/foo/b%27ar.png") center center no-repeat;') > 0 - || sheet.indexOf('background-image: url("file:///Users/foo/b%27ar.png"); background-position: center center; background-repeat: no-repeat no-repeat;') > 0 - ); - s.removeDecorationType('example'); - - s = new TestCodeEditorServiceImpl(themeServiceMock, styleSheet); s.registerDecorationType('example', { gutterIconPath: URI.parse('http://test/pa\'th') }); - sheet = readStyleSheet(styleSheet); - assert( - sheet.indexOf('background: url(\'http://test/pa%27th\') center center no-repeat;') > 0 - || sheet.indexOf('background: url("http://test/pa%27th") center center no-repeat;') > 0 - || sheet.indexOf('background-image: url("http://test/pa%27th"); background-position: center center; background-repeat: no-repeat no-repeat;') > 0 - ); + assert(readStyleSheet(styleSheet).indexOf(`{background:url('http://test/pa%27th') center center no-repeat;}`) > 0); s.removeDecorationType('example'); }); }); diff --git a/src/vs/editor/test/browser/testCodeEditor.ts b/src/vs/editor/test/browser/testCodeEditor.ts index 5153fc082ec..e81e049553a 100644 --- a/src/vs/editor/test/browser/testCodeEditor.ts +++ b/src/vs/editor/test/browser/testCodeEditor.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IEditorContributionCtor } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { View } from 'vs/editor/browser/view/viewImpl'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; @@ -11,13 +12,13 @@ import * as editorOptions from 'vs/editor/common/config/editorOptions'; import { Cursor } from 'vs/editor/common/controller/cursor'; import { IConfiguration, IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; -import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { TestCodeEditorService, TestCommandService } from 'vs/editor/test/browser/editorTestServices'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IContextKeyService, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { BrandedService, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; @@ -42,8 +43,8 @@ export class TestCodeEditor extends CodeEditorWidget implements ICodeEditor { public getCursor(): Cursor | undefined { return this._modelData ? this._modelData.cursor : undefined; } - public registerAndInstantiateContribution(id: string, ctor: any): T { - let r = this._instantiationService.createInstance(ctor, this); + public registerAndInstantiateContribution(id: string, ctor: new (editor: ICodeEditor, ...services: Services) => T): T { + const r: T = this._instantiationService.createInstance(ctor as IEditorContributionCtor, this); this._contributions[id] = r; return r; } diff --git a/src/vs/editor/test/common/model/benchmark/operations.benchmark.ts b/src/vs/editor/test/common/model/benchmark/operations.benchmark.ts index 4ec19305751..ded9745ef09 100644 --- a/src/vs/editor/test/common/model/benchmark/operations.benchmark.ts +++ b/src/vs/editor/test/common/model/benchmark/operations.benchmark.ts @@ -54,7 +54,7 @@ for (let fileSize of fileSizes) { fn: (textBuffer) => { // for line model, this loop doesn't reflect the real situation. for (const edit of edits) { - textBuffer.applyEdits([edit], false); + textBuffer.applyEdits([edit], false, false); } } }); @@ -67,7 +67,7 @@ for (let fileSize of fileSizes) { }, preCycle: (textBuffer) => { for (const edit of edits) { - textBuffer.applyEdits([edit], false); + textBuffer.applyEdits([edit], false, false); } return textBuffer; }, @@ -91,7 +91,7 @@ for (let fileSize of fileSizes) { }, preCycle: (textBuffer) => { for (const edit of edits) { - textBuffer.applyEdits([edit], false); + textBuffer.applyEdits([edit], false, false); } return textBuffer; }, @@ -121,7 +121,7 @@ for (let fileSize of fileSizes) { }, preCycle: (textBuffer) => { for (const edit of edits) { - textBuffer.applyEdits([edit], false); + textBuffer.applyEdits([edit], false, false); } return textBuffer; }, @@ -134,4 +134,4 @@ for (let fileSize of fileSizes) { editsSuite.run(); } -} \ No newline at end of file +} diff --git a/src/vs/editor/test/common/model/benchmark/searchNReplace.benchmark.ts b/src/vs/editor/test/common/model/benchmark/searchNReplace.benchmark.ts index ab86d7e0a81..aecb8ad46ca 100644 --- a/src/vs/editor/test/common/model/benchmark/searchNReplace.benchmark.ts +++ b/src/vs/editor/test/common/model/benchmark/searchNReplace.benchmark.ts @@ -41,10 +41,10 @@ for (let fileSize of fileSizes) { return textBuffer; }, fn: (textBuffer) => { - textBuffer.applyEdits(edits.slice(0, i), false); + textBuffer.applyEdits(edits.slice(0, i), false, false); } }); } replaceSuite.run(); -} \ No newline at end of file +} diff --git a/src/vs/editor/test/common/model/editableTextModel.test.ts b/src/vs/editor/test/common/model/editableTextModel.test.ts index b37a3b9820e..67c8a119e30 100644 --- a/src/vs/editor/test/common/model/editableTextModel.test.ts +++ b/src/vs/editor/test/common/model/editableTextModel.test.ts @@ -1104,7 +1104,7 @@ suite('EditorModel - EditableTextModel.applyEdits', () => { { range: new Range(3, 1, 3, 6), text: null, }, { range: new Range(2, 1, 3, 1), text: null, }, { range: new Range(3, 6, 3, 6), text: '\nline2' } - ]); + ], true); model.applyEdits(undoEdits); diff --git a/src/vs/editor/test/common/model/editableTextModelTestUtils.ts b/src/vs/editor/test/common/model/editableTextModelTestUtils.ts index e1e4b5c999e..9531a8bb1af 100644 --- a/src/vs/editor/test/common/model/editableTextModelTestUtils.ts +++ b/src/vs/editor/test/common/model/editableTextModelTestUtils.ts @@ -17,7 +17,7 @@ export function testApplyEditsWithSyncedModels(original: string[], edits: IIdent assertSyncedModels(originalStr, (model, assertMirrorModels) => { // Apply edits & collect inverse edits - let inverseEdits = model.applyEdits(edits); + let inverseEdits = model.applyEdits(edits, true); // Assert edits produced expected result assert.deepEqual(model.getValue(EndOfLinePreference.LF), expectedStr); @@ -25,7 +25,7 @@ export function testApplyEditsWithSyncedModels(original: string[], edits: IIdent assertMirrorModels(); // Apply the inverse edits - let inverseInverseEdits = model.applyEdits(inverseEdits); + let inverseInverseEdits = model.applyEdits(inverseEdits, true); // Assert the inverse edits brought back model to original state assert.deepEqual(model.getValue(EndOfLinePreference.LF), originalStr); @@ -36,8 +36,8 @@ export function testApplyEditsWithSyncedModels(original: string[], edits: IIdent identifier: edit.identifier, range: edit.range, text: edit.text, - forceMoveMarkers: edit.forceMoveMarkers, - isAutoWhitespaceEdit: edit.isAutoWhitespaceEdit + forceMoveMarkers: edit.forceMoveMarkers || false, + isAutoWhitespaceEdit: edit.isAutoWhitespaceEdit || false }; }; // Assert the inverse of the inverse edits are the original edits diff --git a/src/vs/editor/test/common/model/linesTextBuffer/linesTextBuffer.test.ts b/src/vs/editor/test/common/model/linesTextBuffer/linesTextBuffer.test.ts index 5e18c394433..4c9bd0d183c 100644 --- a/src/vs/editor/test/common/model/linesTextBuffer/linesTextBuffer.test.ts +++ b/src/vs/editor/test/common/model/linesTextBuffer/linesTextBuffer.test.ts @@ -18,7 +18,10 @@ suite('PieceTreeTextBuffer._getInverseEdits', () => { range: new Range(startLineNumber, startColumn, endLineNumber, endColumn), rangeOffset: 0, rangeLength: 0, - lines: text, + text: text ? text.join('\n') : '', + eolCount: text ? text.length - 1 : 0, + firstLineLength: text ? text[0].length : 0, + lastLineLength: text ? text[text.length - 1].length : 0, forceMoveMarkers: false, isAutoWhitespaceEdit: false }; @@ -269,7 +272,10 @@ suite('PieceTreeTextBuffer._toSingleEditOperation', () => { range: new Range(startLineNumber, startColumn, endLineNumber, endColumn), rangeOffset: rangeOffset, rangeLength: rangeLength, - lines: text, + text: text ? text.join('\n') : '', + eolCount: text ? text.length - 1 : 0, + firstLineLength: text ? text[0].length : 0, + lastLineLength: text ? text[text.length - 1].length : 0, forceMoveMarkers: false, isAutoWhitespaceEdit: false }; diff --git a/src/vs/editor/test/common/model/model.test.ts b/src/vs/editor/test/common/model/model.test.ts index 77ce4133e46..ed6787bc528 100644 --- a/src/vs/editor/test/common/model/model.test.ts +++ b/src/vs/editor/test/common/model/model.test.ts @@ -330,7 +330,7 @@ suite('Editor Model - Model', () => { let res = thisModel.applyEdits([ { range: new Range(2, 1, 2, 1), text: 'a' }, { range: new Range(1, 1, 1, 1), text: 'b' }, - ]); + ], true); assert.deepEqual(res[0].range, new Range(2, 1, 2, 2)); assert.deepEqual(res[1].range, new Range(1, 1, 1, 2)); diff --git a/src/vs/editor/test/common/model/modelEditOperation.test.ts b/src/vs/editor/test/common/model/modelEditOperation.test.ts index 2fc1337cbe2..058c5d3d56a 100644 --- a/src/vs/editor/test/common/model/modelEditOperation.test.ts +++ b/src/vs/editor/test/common/model/modelEditOperation.test.ts @@ -50,14 +50,14 @@ suite('Editor Model - Model Edit Operation', () => { function assertSingleEditOp(singleEditOp: IIdentifiedSingleEditOperation, editedLines: string[]) { let editOp = [singleEditOp]; - let inverseEditOp = model.applyEdits(editOp); + let inverseEditOp = model.applyEdits(editOp, true); assert.equal(model.getLineCount(), editedLines.length); for (let i = 0; i < editedLines.length; i++) { assert.equal(model.getLineContent(i + 1), editedLines[i]); } - let originalOp = model.applyEdits(inverseEditOp); + let originalOp = model.applyEdits(inverseEditOp, true); assert.equal(model.getLineCount(), 5); assert.equal(model.getLineContent(1), LINE1); @@ -71,8 +71,8 @@ suite('Editor Model - Model Edit Operation', () => { identifier: edit.identifier, range: edit.range, text: edit.text, - forceMoveMarkers: edit.forceMoveMarkers, - isAutoWhitespaceEdit: edit.isAutoWhitespaceEdit + forceMoveMarkers: edit.forceMoveMarkers || false, + isAutoWhitespaceEdit: edit.isAutoWhitespaceEdit || false }; }; assert.deepEqual(originalOp.map(simplifyEdit), editOp.map(simplifyEdit)); diff --git a/src/vs/editor/test/common/model/textChange.test.ts b/src/vs/editor/test/common/model/textChange.test.ts new file mode 100644 index 00000000000..d6d42dfc683 --- /dev/null +++ b/src/vs/editor/test/common/model/textChange.test.ts @@ -0,0 +1,269 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { compressConsecutiveTextChanges, TextChange } from 'vs/editor/common/model/textChange'; + +const GENERATE_TESTS = false; + +interface IGeneratedEdit { + offset: number; + length: number; + text: string; +} + +suite('TextChangeCompressor', () => { + + function getResultingContent(initialContent: string, edits: IGeneratedEdit[]): string { + let content = initialContent; + for (let i = edits.length - 1; i >= 0; i--) { + content = ( + content.substring(0, edits[i].offset) + + edits[i].text + + content.substring(edits[i].offset + edits[i].length) + ); + } + return content; + } + + function getTextChanges(initialContent: string, edits: IGeneratedEdit[]): TextChange[] { + let content = initialContent; + let changes: TextChange[] = new Array(edits.length); + let deltaOffset = 0; + + for (let i = 0; i < edits.length; i++) { + let edit = edits[i]; + + let position = edit.offset + deltaOffset; + let length = edit.length; + let text = edit.text; + + let oldText = content.substr(position, length); + + content = ( + content.substr(0, position) + + text + + content.substr(position + length) + ); + + changes[i] = new TextChange(edit.offset, oldText, position, text); + + deltaOffset += text.length - length; + } + + return changes; + } + + function assertCompression(initialText: string, edit1: IGeneratedEdit[], edit2: IGeneratedEdit[]): void { + + let tmpText = getResultingContent(initialText, edit1); + let chg1 = getTextChanges(initialText, edit1); + + let finalText = getResultingContent(tmpText, edit2); + let chg2 = getTextChanges(tmpText, edit2); + + let compressedTextChanges = compressConsecutiveTextChanges(chg1, chg2); + + // Check that the compression was correct + let compressedDoTextEdits: IGeneratedEdit[] = compressedTextChanges.map((change) => { + return { + offset: change.oldPosition, + length: change.oldLength, + text: change.newText + }; + }); + let actualDoResult = getResultingContent(initialText, compressedDoTextEdits); + assert.equal(actualDoResult, finalText); + + let compressedUndoTextEdits: IGeneratedEdit[] = compressedTextChanges.map((change) => { + return { + offset: change.newPosition, + length: change.newLength, + text: change.oldText + }; + }); + let actualUndoResult = getResultingContent(finalText, compressedUndoTextEdits); + assert.equal(actualUndoResult, initialText); + } + + test('simple 1', () => { + assertCompression( + '', + [{ offset: 0, length: 0, text: 'h' }], + [{ offset: 1, length: 0, text: 'e' }] + ); + }); + + test('simple 2', () => { + assertCompression( + '|', + [{ offset: 0, length: 0, text: 'h' }], + [{ offset: 2, length: 0, text: 'e' }] + ); + }); + + test('complex1', () => { + assertCompression( + 'abcdefghij', + [ + { offset: 0, length: 3, text: 'qh' }, + { offset: 5, length: 0, text: '1' }, + { offset: 8, length: 2, text: 'X' } + ], + [ + { offset: 1, length: 0, text: 'Z' }, + { offset: 3, length: 3, text: 'Y' }, + ] + ); + }); + + test('gen1', () => { + assertCompression( + 'kxm', + [{ offset: 0, length: 1, text: 'tod_neu' }], + [{ offset: 1, length: 2, text: 'sag_e' }] + ); + }); + + test('gen2', () => { + assertCompression( + 'kpb_r_v', + [{ offset: 5, length: 2, text: 'a_jvf_l' }], + [{ offset: 10, length: 2, text: 'w' }] + ); + }); + + test('gen3', () => { + assertCompression( + 'slu_w', + [{ offset: 4, length: 1, text: '_wfw' }], + [{ offset: 3, length: 5, text: '' }] + ); + }); + + test('gen4', () => { + assertCompression( + '_e', + [{ offset: 2, length: 0, text: 'zo_b' }], + [{ offset: 1, length: 3, text: 'tra' }] + ); + }); + + test('gen5', () => { + assertCompression( + 'ssn_', + [{ offset: 0, length: 2, text: 'tat_nwe' }], + [{ offset: 2, length: 6, text: 'jm' }] + ); + }); + + test('gen6', () => { + assertCompression( + 'kl_nru', + [{ offset: 4, length: 1, text: '' }], + [{ offset: 1, length: 4, text: '__ut' }] + ); + }); + + const _a = 'a'.charCodeAt(0); + const _z = 'z'.charCodeAt(0); + + function getRandomInt(min: number, max: number): number { + return Math.floor(Math.random() * (max - min + 1)) + min; + } + + function getRandomString(minLength: number, maxLength: number): string { + const length = getRandomInt(minLength, maxLength); + let r = ''; + for (let i = 0; i < length; i++) { + r += String.fromCharCode(getRandomInt(_a, _z)); + } + return r; + } + + function getRandomEOL(): string { + switch (getRandomInt(1, 3)) { + case 1: return '\r'; + case 2: return '\n'; + case 3: return '\r\n'; + } + throw new Error(`not possible`); + } + + function getRandomBuffer(small: boolean): string { + let lineCount = getRandomInt(1, small ? 3 : 10); + let lines: string[] = []; + for (let i = 0; i < lineCount; i++) { + lines.push(getRandomString(0, small ? 3 : 10) + getRandomEOL()); + } + return lines.join(''); + } + + function getRandomEdits(content: string, min: number = 1, max: number = 5): IGeneratedEdit[] { + + let result: IGeneratedEdit[] = []; + let cnt = getRandomInt(min, max); + + let maxOffset = content.length; + + while (cnt > 0 && maxOffset > 0) { + + let offset = getRandomInt(0, maxOffset); + let length = getRandomInt(0, maxOffset - offset); + let text = getRandomBuffer(true); + + result.push({ + offset: offset, + length: length, + text: text + }); + + maxOffset = offset; + cnt--; + } + + result.reverse(); + + return result; + } + + class GeneratedTest { + + private readonly _content: string; + private readonly _edits1: IGeneratedEdit[]; + private readonly _edits2: IGeneratedEdit[]; + + constructor() { + this._content = getRandomBuffer(false).replace(/\n/g, '_'); + this._edits1 = getRandomEdits(this._content, 1, 5).map((e) => { return { offset: e.offset, length: e.length, text: e.text.replace(/\n/g, '_') }; }); + let tmp = getResultingContent(this._content, this._edits1); + this._edits2 = getRandomEdits(tmp, 1, 5).map((e) => { return { offset: e.offset, length: e.length, text: e.text.replace(/\n/g, '_') }; }); + } + + public print(): void { + console.log(`assertCompression(${JSON.stringify(this._content)}, ${JSON.stringify(this._edits1)}, ${JSON.stringify(this._edits2)});`); + } + + public assert(): void { + assertCompression(this._content, this._edits1, this._edits2); + } + } + + if (GENERATE_TESTS) { + let testNumber = 0; + while (true) { + testNumber++; + console.log(`------RUNNING TextChangeCompressor TEST ${testNumber}`); + let test = new GeneratedTest(); + try { + test.assert(); + } catch (err) { + console.log(err); + test.print(); + break; + } + } + } +}); diff --git a/src/vs/editor/test/common/model/tokensStore.test.ts b/src/vs/editor/test/common/model/tokensStore.test.ts index 51d85e382f8..ff653c2c1d7 100644 --- a/src/vs/editor/test/common/model/tokensStore.test.ts +++ b/src/vs/editor/test/common/model/tokensStore.test.ts @@ -4,12 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { MultilineTokens2, SparseEncodedTokens } from 'vs/editor/common/model/tokensStore'; +import { MultilineTokens2, SparseEncodedTokens, TokensStore2 } from 'vs/editor/common/model/tokensStore'; import { Range } from 'vs/editor/common/core/range'; import { TextModel } from 'vs/editor/common/model/textModel'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { MetadataConsts, TokenMetadata } from 'vs/editor/common/modes'; import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; +import { LineTokens } from 'vs/editor/common/core/lineTokens'; suite('TokensStore', () => { @@ -98,7 +99,7 @@ suite('TokensStore', () => { function testTokensAdjustment(rawInitialState: string[], edits: IIdentifiedSingleEditOperation[], rawFinalState: string[]) { const initialState = parseTokensState(rawInitialState); const model = createTextModel(initialState.text); - model.setSemanticTokens([initialState.tokens]); + model.setSemanticTokens([initialState.tokens], true); model.applyEdits(edits); @@ -169,4 +170,221 @@ suite('TokensStore', () => { ); }); + test('issue #91936: Semantic token color highlighting fails on line with selected text', () => { + const model = createTextModel(' else if ($s = 08) then \'\\b\''); + model.setSemanticTokens([ + new MultilineTokens2(1, new SparseEncodedTokens(new Uint32Array([ + 0, 20, 24, 245768, + 0, 25, 27, 245768, + 0, 28, 29, 16392, + 0, 29, 31, 262152, + 0, 32, 33, 16392, + 0, 34, 36, 98312, + 0, 36, 37, 16392, + 0, 38, 42, 245768, + 0, 43, 47, 180232, + ]))) + ], true); + const lineTokens = model.getLineTokens(1); + let decodedTokens: number[] = []; + for (let i = 0, len = lineTokens.getCount(); i < len; i++) { + decodedTokens.push(lineTokens.getEndOffset(i), lineTokens.getMetadata(i)); + } + + assert.deepEqual(decodedTokens, [ + 20, 16793600, + 24, 17022976, + 25, 16793600, + 27, 17022976, + 28, 16793600, + 29, 16793600, + 31, 17039360, + 32, 16793600, + 33, 16793600, + 34, 16793600, + 36, 16875520, + 37, 16793600, + 38, 16793600, + 42, 17022976, + 43, 16793600, + 47, 16957440 + ]); + + model.dispose(); + }); + + test('partial tokens 1', () => { + const store = new TokensStore2(); + + // setPartial: [1,1 -> 31,2], [(5,5-10),(10,5-10),(15,5-10),(20,5-10),(25,5-10),(30,5-10)] + store.setPartial(new Range(1, 1, 31, 2), [ + new MultilineTokens2(5, new SparseEncodedTokens(new Uint32Array([ + 0, 5, 10, 1, + 5, 5, 10, 2, + 10, 5, 10, 3, + 15, 5, 10, 4, + 20, 5, 10, 5, + 25, 5, 10, 6, + ]))) + ]); + + // setPartial: [18,1 -> 42,1], [(20,5-10),(25,5-10),(30,5-10),(35,5-10),(40,5-10)] + store.setPartial(new Range(18, 1, 42, 1), [ + new MultilineTokens2(20, new SparseEncodedTokens(new Uint32Array([ + 0, 5, 10, 4, + 5, 5, 10, 5, + 10, 5, 10, 6, + 15, 5, 10, 7, + 20, 5, 10, 8, + ]))) + ]); + + // setPartial: [1,1 -> 31,2], [(5,5-10),(10,5-10),(15,5-10),(20,5-10),(25,5-10),(30,5-10)] + store.setPartial(new Range(1, 1, 31, 2), [ + new MultilineTokens2(5, new SparseEncodedTokens(new Uint32Array([ + 0, 5, 10, 1, + 5, 5, 10, 2, + 10, 5, 10, 3, + 15, 5, 10, 4, + 20, 5, 10, 5, + 25, 5, 10, 6, + ]))) + ]); + + const lineTokens = store.addSemanticTokens(10, new LineTokens(new Uint32Array([12, 1]), `enum Enum1 {`)); + assert.equal(lineTokens.getCount(), 3); + }); + + test('partial tokens 2', () => { + const store = new TokensStore2(); + + // setPartial: [1,1 -> 31,2], [(5,5-10),(10,5-10),(15,5-10),(20,5-10),(25,5-10),(30,5-10)] + store.setPartial(new Range(1, 1, 31, 2), [ + new MultilineTokens2(5, new SparseEncodedTokens(new Uint32Array([ + 0, 5, 10, 1, + 5, 5, 10, 2, + 10, 5, 10, 3, + 15, 5, 10, 4, + 20, 5, 10, 5, + 25, 5, 10, 6, + ]))) + ]); + + // setPartial: [6,1 -> 36,2], [(10,5-10),(15,5-10),(20,5-10),(25,5-10),(30,5-10),(35,5-10)] + store.setPartial(new Range(6, 1, 36, 2), [ + new MultilineTokens2(10, new SparseEncodedTokens(new Uint32Array([ + 0, 5, 10, 2, + 5, 5, 10, 3, + 10, 5, 10, 4, + 15, 5, 10, 5, + 20, 5, 10, 6, + ]))) + ]); + + // setPartial: [17,1 -> 42,1], [(20,5-10),(25,5-10),(30,5-10),(35,5-10),(40,5-10)] + store.setPartial(new Range(17, 1, 42, 1), [ + new MultilineTokens2(20, new SparseEncodedTokens(new Uint32Array([ + 0, 5, 10, 4, + 5, 5, 10, 5, + 10, 5, 10, 6, + 15, 5, 10, 7, + 20, 5, 10, 8, + ]))) + ]); + + const lineTokens = store.addSemanticTokens(20, new LineTokens(new Uint32Array([12, 1]), `enum Enum1 {`)); + assert.equal(lineTokens.getCount(), 3); + }); + + test('partial tokens 3', () => { + const store = new TokensStore2(); + + // setPartial: [1,1 -> 31,2], [(5,5-10),(10,5-10),(15,5-10),(20,5-10),(25,5-10),(30,5-10)] + store.setPartial(new Range(1, 1, 31, 2), [ + new MultilineTokens2(5, new SparseEncodedTokens(new Uint32Array([ + 0, 5, 10, 1, + 5, 5, 10, 2, + 10, 5, 10, 3, + 15, 5, 10, 4, + 20, 5, 10, 5, + 25, 5, 10, 6, + ]))) + ]); + + // setPartial: [11,1 -> 16,2], [(15,5-10),(20,5-10)] + store.setPartial(new Range(11, 1, 16, 2), [ + new MultilineTokens2(10, new SparseEncodedTokens(new Uint32Array([ + 0, 5, 10, 3, + 5, 5, 10, 4, + ]))) + ]); + + const lineTokens = store.addSemanticTokens(5, new LineTokens(new Uint32Array([12, 1]), `enum Enum1 {`)); + assert.equal(lineTokens.getCount(), 3); + }); + + test('issue #94133: Semantic colors stick around when using (only) range provider', () => { + const store = new TokensStore2(); + + // setPartial: [1,1 -> 1,20] [(1,9-11)] + store.setPartial(new Range(1, 1, 1, 20), [ + new MultilineTokens2(1, new SparseEncodedTokens(new Uint32Array([ + 0, 9, 11, 1, + ]))) + ]); + + // setPartial: [1,1 -> 1,20], [] + store.setPartial(new Range(1, 1, 1, 20), []); + + const lineTokens = store.addSemanticTokens(1, new LineTokens(new Uint32Array([12, 1]), `enum Enum1 {`)); + assert.equal(lineTokens.getCount(), 1); + }); + + test('bug', () => { + function createTokens(str: string): MultilineTokens2 { + str = str.replace(/^\[\(/, ''); + str = str.replace(/\)\]$/, ''); + const strTokens = str.split('),('); + let result: number[] = []; + let firstLineNumber = 0; + for (const strToken of strTokens) { + const pieces = strToken.split(','); + const chars = pieces[1].split('-'); + const lineNumber = parseInt(pieces[0], 10); + const startChar = parseInt(chars[0], 10); + const endChar = parseInt(chars[1], 10); + if (firstLineNumber === 0) { + // this is the first line + firstLineNumber = lineNumber; + } + result.push(lineNumber - firstLineNumber, startChar, endChar, (lineNumber + startChar) % 13); + } + return new MultilineTokens2(firstLineNumber, new SparseEncodedTokens(new Uint32Array(result))); + } + + const store = new TokensStore2(); + // setPartial [36446,1 -> 36475,115] [(36448,24-29),(36448,33-46),(36448,47-54),(36450,25-35),(36450,36-50),(36451,28-33),(36451,36-49),(36451,50-57),(36452,35-53),(36452,54-62),(36454,33-38),(36454,41-54),(36454,55-60),(36455,35-53),(36455,54-62),(36457,33-44),(36457,45-49),(36457,50-56),(36457,62-83),(36457,84-88),(36458,35-53),(36458,54-62),(36460,33-37),(36460,38-42),(36460,47-57),(36460,58-67),(36461,35-53),(36461,54-62),(36463,34-38),(36463,39-45),(36463,46-51),(36463,54-63),(36463,64-71),(36463,76-80),(36463,81-87),(36463,88-92),(36463,97-107),(36463,108-119),(36464,35-53),(36464,54-62),(36466,33-71),(36466,72-76),(36467,35-53),(36467,54-62),(36469,24-29),(36469,33-46),(36469,47-54),(36470,24-35),(36470,38-46),(36473,25-35),(36473,36-51),(36474,28-33),(36474,36-49),(36474,50-58),(36475,35-53),(36475,54-62)] + store.setPartial( + new Range(36446, 1, 36475, 115), + [createTokens('[(36448,24-29),(36448,33-46),(36448,47-54),(36450,25-35),(36450,36-50),(36451,28-33),(36451,36-49),(36451,50-57),(36452,35-53),(36452,54-62),(36454,33-38),(36454,41-54),(36454,55-60),(36455,35-53),(36455,54-62),(36457,33-44),(36457,45-49),(36457,50-56),(36457,62-83),(36457,84-88),(36458,35-53),(36458,54-62),(36460,33-37),(36460,38-42),(36460,47-57),(36460,58-67),(36461,35-53),(36461,54-62),(36463,34-38),(36463,39-45),(36463,46-51),(36463,54-63),(36463,64-71),(36463,76-80),(36463,81-87),(36463,88-92),(36463,97-107),(36463,108-119),(36464,35-53),(36464,54-62),(36466,33-71),(36466,72-76),(36467,35-53),(36467,54-62),(36469,24-29),(36469,33-46),(36469,47-54),(36470,24-35),(36470,38-46),(36473,25-35),(36473,36-51),(36474,28-33),(36474,36-49),(36474,50-58),(36475,35-53),(36475,54-62)]')] + ); + // setPartial [36436,1 -> 36464,142] [(36437,33-37),(36437,38-42),(36437,47-57),(36437,58-67),(36438,35-53),(36438,54-62),(36440,24-29),(36440,33-46),(36440,47-53),(36442,25-35),(36442,36-50),(36443,30-39),(36443,42-46),(36443,47-53),(36443,54-58),(36443,63-73),(36443,74-84),(36443,87-91),(36443,92-98),(36443,101-105),(36443,106-112),(36443,113-119),(36444,28-37),(36444,38-42),(36444,47-57),(36444,58-75),(36444,80-95),(36444,96-105),(36445,35-53),(36445,54-62),(36448,24-29),(36448,33-46),(36448,47-54),(36450,25-35),(36450,36-50),(36451,28-33),(36451,36-49),(36451,50-57),(36452,35-53),(36452,54-62),(36454,33-38),(36454,41-54),(36454,55-60),(36455,35-53),(36455,54-62),(36457,33-44),(36457,45-49),(36457,50-56),(36457,62-83),(36457,84-88),(36458,35-53),(36458,54-62),(36460,33-37),(36460,38-42),(36460,47-57),(36460,58-67),(36461,35-53),(36461,54-62),(36463,34-38),(36463,39-45),(36463,46-51),(36463,54-63),(36463,64-71),(36463,76-80),(36463,81-87),(36463,88-92),(36463,97-107),(36463,108-119),(36464,35-53),(36464,54-62)] + store.setPartial( + new Range(36436, 1, 36464, 142), + [createTokens('[(36437,33-37),(36437,38-42),(36437,47-57),(36437,58-67),(36438,35-53),(36438,54-62),(36440,24-29),(36440,33-46),(36440,47-53),(36442,25-35),(36442,36-50),(36443,30-39),(36443,42-46),(36443,47-53),(36443,54-58),(36443,63-73),(36443,74-84),(36443,87-91),(36443,92-98),(36443,101-105),(36443,106-112),(36443,113-119),(36444,28-37),(36444,38-42),(36444,47-57),(36444,58-75),(36444,80-95),(36444,96-105),(36445,35-53),(36445,54-62),(36448,24-29),(36448,33-46),(36448,47-54),(36450,25-35),(36450,36-50),(36451,28-33),(36451,36-49),(36451,50-57),(36452,35-53),(36452,54-62),(36454,33-38),(36454,41-54),(36454,55-60),(36455,35-53),(36455,54-62),(36457,33-44),(36457,45-49),(36457,50-56),(36457,62-83),(36457,84-88),(36458,35-53),(36458,54-62),(36460,33-37),(36460,38-42),(36460,47-57),(36460,58-67),(36461,35-53),(36461,54-62),(36463,34-38),(36463,39-45),(36463,46-51),(36463,54-63),(36463,64-71),(36463,76-80),(36463,81-87),(36463,88-92),(36463,97-107),(36463,108-119),(36464,35-53),(36464,54-62)]')] + ); + // setPartial [36457,1 -> 36485,140] [(36457,33-44),(36457,45-49),(36457,50-56),(36457,62-83),(36457,84-88),(36458,35-53),(36458,54-62),(36460,33-37),(36460,38-42),(36460,47-57),(36460,58-67),(36461,35-53),(36461,54-62),(36463,34-38),(36463,39-45),(36463,46-51),(36463,54-63),(36463,64-71),(36463,76-80),(36463,81-87),(36463,88-92),(36463,97-107),(36463,108-119),(36464,35-53),(36464,54-62),(36466,33-71),(36466,72-76),(36467,35-53),(36467,54-62),(36469,24-29),(36469,33-46),(36469,47-54),(36470,24-35),(36470,38-46),(36473,25-35),(36473,36-51),(36474,28-33),(36474,36-49),(36474,50-58),(36475,35-53),(36475,54-62),(36477,28-32),(36477,33-37),(36477,42-52),(36477,53-69),(36478,32-36),(36478,37-41),(36478,46-56),(36478,57-74),(36479,32-36),(36479,37-41),(36479,46-56),(36479,57-76),(36480,32-36),(36480,37-41),(36480,46-56),(36480,57-68),(36481,32-36),(36481,37-41),(36481,46-56),(36481,57-68),(36482,39-57),(36482,58-66),(36484,34-38),(36484,39-45),(36484,46-50),(36484,55-65),(36484,66-82),(36484,86-97),(36484,98-102),(36484,103-109),(36484,111-124),(36484,125-133),(36485,39-57),(36485,58-66)] + store.setPartial( + new Range(36457, 1, 36485, 140), + [createTokens('[(36457,33-44),(36457,45-49),(36457,50-56),(36457,62-83),(36457,84-88),(36458,35-53),(36458,54-62),(36460,33-37),(36460,38-42),(36460,47-57),(36460,58-67),(36461,35-53),(36461,54-62),(36463,34-38),(36463,39-45),(36463,46-51),(36463,54-63),(36463,64-71),(36463,76-80),(36463,81-87),(36463,88-92),(36463,97-107),(36463,108-119),(36464,35-53),(36464,54-62),(36466,33-71),(36466,72-76),(36467,35-53),(36467,54-62),(36469,24-29),(36469,33-46),(36469,47-54),(36470,24-35),(36470,38-46),(36473,25-35),(36473,36-51),(36474,28-33),(36474,36-49),(36474,50-58),(36475,35-53),(36475,54-62),(36477,28-32),(36477,33-37),(36477,42-52),(36477,53-69),(36478,32-36),(36478,37-41),(36478,46-56),(36478,57-74),(36479,32-36),(36479,37-41),(36479,46-56),(36479,57-76),(36480,32-36),(36480,37-41),(36480,46-56),(36480,57-68),(36481,32-36),(36481,37-41),(36481,46-56),(36481,57-68),(36482,39-57),(36482,58-66),(36484,34-38),(36484,39-45),(36484,46-50),(36484,55-65),(36484,66-82),(36484,86-97),(36484,98-102),(36484,103-109),(36484,111-124),(36484,125-133),(36485,39-57),(36485,58-66)]')] + ); + // setPartial [36441,1 -> 36469,56] [(36442,25-35),(36442,36-50),(36443,30-39),(36443,42-46),(36443,47-53),(36443,54-58),(36443,63-73),(36443,74-84),(36443,87-91),(36443,92-98),(36443,101-105),(36443,106-112),(36443,113-119),(36444,28-37),(36444,38-42),(36444,47-57),(36444,58-75),(36444,80-95),(36444,96-105),(36445,35-53),(36445,54-62),(36448,24-29),(36448,33-46),(36448,47-54),(36450,25-35),(36450,36-50),(36451,28-33),(36451,36-49),(36451,50-57),(36452,35-53),(36452,54-62),(36454,33-38),(36454,41-54),(36454,55-60),(36455,35-53),(36455,54-62),(36457,33-44),(36457,45-49),(36457,50-56),(36457,62-83),(36457,84-88),(36458,35-53),(36458,54-62),(36460,33-37),(36460,38-42),(36460,47-57),(36460,58-67),(36461,35-53),(36461,54-62),(36463,34-38),(36463,39-45),(36463,46-51),(36463,54-63),(36463,64-71),(36463,76-80),(36463,81-87),(36463,88-92),(36463,97-107),(36463,108-119),(36464,35-53),(36464,54-62),(36466,33-71),(36466,72-76),(36467,35-53),(36467,54-62),(36469,24-29),(36469,33-46),(36469,47-54),(36470,24-35)] + store.setPartial( + new Range(36441, 1, 36469, 56), + [createTokens('[(36442,25-35),(36442,36-50),(36443,30-39),(36443,42-46),(36443,47-53),(36443,54-58),(36443,63-73),(36443,74-84),(36443,87-91),(36443,92-98),(36443,101-105),(36443,106-112),(36443,113-119),(36444,28-37),(36444,38-42),(36444,47-57),(36444,58-75),(36444,80-95),(36444,96-105),(36445,35-53),(36445,54-62),(36448,24-29),(36448,33-46),(36448,47-54),(36450,25-35),(36450,36-50),(36451,28-33),(36451,36-49),(36451,50-57),(36452,35-53),(36452,54-62),(36454,33-38),(36454,41-54),(36454,55-60),(36455,35-53),(36455,54-62),(36457,33-44),(36457,45-49),(36457,50-56),(36457,62-83),(36457,84-88),(36458,35-53),(36458,54-62),(36460,33-37),(36460,38-42),(36460,47-57),(36460,58-67),(36461,35-53),(36461,54-62),(36463,34-38),(36463,39-45),(36463,46-51),(36463,54-63),(36463,64-71),(36463,76-80),(36463,81-87),(36463,88-92),(36463,97-107),(36463,108-119),(36464,35-53),(36464,54-62),(36466,33-71),(36466,72-76),(36467,35-53),(36467,54-62),(36469,24-29),(36469,33-46),(36469,47-54),(36470,24-35)]')] + ); + + const lineTokens = store.addSemanticTokens(36451, new LineTokens(new Uint32Array([60, 1]), ` if (flags & ModifierFlags.Ambient) {`)); + assert.equal(lineTokens.getCount(), 7); + }); }); diff --git a/src/vs/editor/test/common/services/editorSimpleWorker.test.ts b/src/vs/editor/test/common/services/editorSimpleWorker.test.ts index d2aa3675d36..da25cab90cb 100644 --- a/src/vs/editor/test/common/services/editorSimpleWorker.test.ts +++ b/src/vs/editor/test/common/services/editorSimpleWorker.test.ts @@ -184,11 +184,7 @@ suite('EditorSimpleWorker', () => { 'and now we are done' ]); - let words: string[] = []; - - for (let iter = model.createWordIterator(/[a-z]+/img), e = iter.next(); !e.done; e = iter.next()) { - words.push(e.value); - } + let words: string[] = [...model.words(/[a-z]+/img)]; assert.deepEqual(words, ['one', 'line', 'two', 'line', 'past', 'empty', 'single', 'and', 'now', 'we', 'are', 'done']); }); diff --git a/src/vs/editor/test/common/services/modelService.test.ts b/src/vs/editor/test/common/services/modelService.test.ts index 84ebc8f2e0e..f9a53739bdb 100644 --- a/src/vs/editor/test/common/services/modelService.test.ts +++ b/src/vs/editor/test/common/services/modelService.test.ts @@ -9,10 +9,11 @@ import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; import { createStringBuilder } from 'vs/editor/common/core/stringBuilder'; import { DefaultEndOfLine } from 'vs/editor/common/model'; import { createTextBuffer } from 'vs/editor/common/model/textModel'; -import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; +import { ModelServiceImpl, MAINTAIN_UNDO_REDO_STACK } from 'vs/editor/common/services/modelServiceImpl'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; @@ -33,7 +34,8 @@ suite('ModelService', () => { configService.setUserConfiguration('files', { 'eol': '\n' }); configService.setUserConfiguration('files', { 'eol': '\r\n' }, URI.file(platform.isWindows ? 'c:\\myroot' : '/myroot')); - modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), new UndoRedoService(new TestDialogService(), new TestNotificationService())); + const dialogService = new TestDialogService(); + modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), new UndoRedoService(dialogService, new TestNotificationService()), dialogService); }); teardown(() => { @@ -307,6 +309,75 @@ suite('ModelService', () => { ]; assertComputeEdits(file1, file2); }); + + if (MAINTAIN_UNDO_REDO_STACK) { + test('maintains undo for same resource and same content', () => { + const resource = URI.parse('file://test.txt'); + + // create a model + const model1 = modelService.createModel('text', null, resource); + // make an edit + model1.pushEditOperations(null, [{ range: new Range(1, 5, 1, 5), text: '1' }], () => [new Selection(1, 5, 1, 5)]); + assert.equal(model1.getValue(), 'text1'); + // dispose it + modelService.destroyModel(resource); + + // create a new model with the same content + const model2 = modelService.createModel('text1', null, resource); + // undo + model2.undo(); + assert.equal(model2.getValue(), 'text'); + }); + + test('maintains version id and alternative version id for same resource and same content', () => { + const resource = URI.parse('file://test.txt'); + + // create a model + const model1 = modelService.createModel('text', null, resource); + // make an edit + model1.pushEditOperations(null, [{ range: new Range(1, 5, 1, 5), text: '1' }], () => [new Selection(1, 5, 1, 5)]); + assert.equal(model1.getValue(), 'text1'); + const versionId = model1.getVersionId(); + const alternativeVersionId = model1.getAlternativeVersionId(); + // dispose it + modelService.destroyModel(resource); + + // create a new model with the same content + const model2 = modelService.createModel('text1', null, resource); + assert.equal(model2.getVersionId(), versionId); + assert.equal(model2.getAlternativeVersionId(), alternativeVersionId); + }); + } + + test('does not maintain undo for same resource and different content', () => { + const resource = URI.parse('file://test.txt'); + + // create a model + const model1 = modelService.createModel('text', null, resource); + // make an edit + model1.pushEditOperations(null, [{ range: new Range(1, 5, 1, 5), text: '1' }], () => [new Selection(1, 5, 1, 5)]); + assert.equal(model1.getValue(), 'text1'); + // dispose it + modelService.destroyModel(resource); + + // create a new model with the same content + const model2 = modelService.createModel('text2', null, resource); + // undo + model2.undo(); + assert.equal(model2.getValue(), 'text2'); + }); + + test('setValue should clear undo stack', () => { + const resource = URI.parse('file://test.txt'); + + const model = modelService.createModel('text', null, resource); + model.pushEditOperations(null, [{ range: new Range(1, 5, 1, 5), text: '1' }], () => [new Selection(1, 5, 1, 5)]); + assert.equal(model.getValue(), 'text1'); + + model.setValue('text2'); + model.undo(); + assert.equal(model.getValue(), 'text2'); + }); }); function assertComputeEdits(lines1: string[], lines2: string[]): void { diff --git a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index f5e4acbc157..7124aba04e5 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -459,10 +459,10 @@ suite('viewLineRenderer.renderLine', () => { ]); let expectedOutput = [ - 'var', - '\u00a0קודמות\u00a0=\u00a0', - '"מיותר\u00a0קודמות\u00a0צ\'ט\u00a0של,\u00a0אם\u00a0לשון\u00a0העברית\u00a0שינויים\u00a0ויש,\u00a0אם"', - ';' + 'var', + '\u00a0קודמות\u00a0=\u00a0', + '"מיותר\u00a0קודמות\u00a0צ\'ט\u00a0של,\u00a0אם\u00a0לשון\u00a0העברית\u00a0שינויים\u00a0ויש,\u00a0אם"', + ';' ].join(''); let _actual = renderViewLine(new RenderLineInput( @@ -487,7 +487,7 @@ suite('viewLineRenderer.renderLine', () => { null )); - assert.equal(_actual.html, '' + expectedOutput + ''); + assert.equal(_actual.html, '' + expectedOutput + ''); assert.equal(_actual.containsRTL, true); }); @@ -676,7 +676,7 @@ suite('viewLineRenderer.renderLine', () => { let lineText = 'את גרמנית בהתייחסות שמו, שנתי המשפט אל חפש, אם כתב אחרים ולחבר. של התוכן אודות בויקיפדיה כלל, של עזרה כימיה היא. על עמוד יוצרים מיתולוגיה סדר, אם שכל שתפו לעברית שינויים, אם שאלות אנגלית עזה. שמות בקלות מה סדר.'; let lineParts = createViewLineTokens([createPart(lineText.length, 1)]); let expectedOutput = [ - 'את\u00a0גרמנית\u00a0בהתייחסות\u00a0שמו,\u00a0שנתי\u00a0המשפט\u00a0אל\u00a0חפש,\u00a0אם\u00a0כתב\u00a0אחרים\u00a0ולחבר.\u00a0של\u00a0התוכן\u00a0אודות\u00a0בויקיפדיה\u00a0כלל,\u00a0של\u00a0עזרה\u00a0כימיה\u00a0היא.\u00a0על\u00a0עמוד\u00a0יוצרים\u00a0מיתולוגיה\u00a0סדר,\u00a0אם\u00a0שכל\u00a0שתפו\u00a0לעברית\u00a0שינויים,\u00a0אם\u00a0שאלות\u00a0אנגלית\u00a0עזה.\u00a0שמות\u00a0בקלות\u00a0מה\u00a0סדר.' + 'את\u00a0גרמנית\u00a0בהתייחסות\u00a0שמו,\u00a0שנתי\u00a0המשפט\u00a0אל\u00a0חפש,\u00a0אם\u00a0כתב\u00a0אחרים\u00a0ולחבר.\u00a0של\u00a0התוכן\u00a0אודות\u00a0בויקיפדיה\u00a0כלל,\u00a0של\u00a0עזרה\u00a0כימיה\u00a0היא.\u00a0על\u00a0עמוד\u00a0יוצרים\u00a0מיתולוגיה\u00a0סדר,\u00a0אם\u00a0שכל\u00a0שתפו\u00a0לעברית\u00a0שינויים,\u00a0אם\u00a0שאלות\u00a0אנגלית\u00a0עזה.\u00a0שמות\u00a0בקלות\u00a0מה\u00a0סדר.' ]; let actual = renderViewLine(new RenderLineInput( false, @@ -699,7 +699,7 @@ suite('viewLineRenderer.renderLine', () => { false, null )); - assert.equal(actual.html, '' + expectedOutput.join('') + ''); + assert.equal(actual.html, '' + expectedOutput.join('') + ''); assert.equal(actual.containsRTL, true); }); @@ -1861,6 +1861,92 @@ suite('viewLineRenderer.renderLine 2', () => { assert.deepEqual(actual.html, expected); }); + test('issue #91936: Semantic token color highlighting fails on line with selected text', () => { + let actual = renderViewLine(new RenderLineInput( + false, + true, + ' else if ($s = 08) then \'\\b\'', + false, + true, + false, + 0, + createViewLineTokens([ + createPart(20, 1), + createPart(24, 15), + createPart(25, 1), + createPart(27, 15), + createPart(28, 1), + createPart(29, 1), + createPart(29, 1), + createPart(31, 16), + createPart(32, 1), + createPart(33, 1), + createPart(34, 1), + createPart(36, 6), + createPart(36, 1), + createPart(37, 1), + createPart(38, 1), + createPart(42, 15), + createPart(43, 1), + createPart(47, 11) + ]), + [], + 4, + 0, + 10, + 11, + 11, + 10000, + 'selection', + false, + false, + [new LineRange(0, 47)] + )); + + let expected = [ + '', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + 'else', + '\u00b7', + 'if', + '\u00b7', + '(', + '$s', + '\u00b7', + '=', + '\u00b7', + '08', + ')', + '\u00b7', + 'then', + '\u00b7', + '\'\\b\'', + '' + ].join(''); + + assert.deepEqual(actual.html, expected); + }); + + function createTestGetColumnOfLinePartOffset(lineContent: string, tabSize: number, parts: ViewLineToken[], expectedPartLengths: number[]): (partIndex: number, partLength: number, offset: number, expected: number) => void { let renderLineOutput = renderViewLine(new RenderLineInput( false, diff --git a/src/vs/editor/test/node/classification/typescript.test.ts b/src/vs/editor/test/node/classification/typescript.test.ts index b5290f22550..5e399061a7b 100644 --- a/src/vs/editor/test/node/classification/typescript.test.ts +++ b/src/vs/editor/test/node/classification/typescript.test.ts @@ -107,7 +107,7 @@ function parseTest(fileName: string): ITest { return { content, assertions }; } -// @ts-ignore +// @ts-expect-error function executeTest(fileName: string, parseFunc: IParseFunc): void { const { content, assertions } = parseTest(fileName); const actual = parseFunc(content); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 543ac0ca086..ddfad98c59e 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -74,6 +74,7 @@ declare namespace monaco { * (http://tools.ietf.org/html/rfc3986#section-3) with minimal validation * and encoding. * + * ```txt * foo://example.com:8042/over/there?name=ferret#nose * \_/ \______________/\_________/ \_________/ \__/ * | | | | | @@ -81,6 +82,7 @@ declare namespace monaco { * | _____________________|__ * / \ / \ * urn:example:animal:ferret:nose + * ``` */ export class Uri implements UriComponents { static isUri(thing: any): thing is Uri; @@ -174,6 +176,14 @@ declare namespace monaco { query?: string; fragment?: string; }): Uri; + /** + * Join a Uri path with path fragments and normalizes the resulting path. + * + * @param uri The input Uri. + * @param pathFragment The path fragment to add to the Uri path. + * @returns The resulting Uri. + */ + static joinPath(uri: Uri, ...pathFragment: string[]): Uri; /** * Creates a string representation for this Uri. It's guaranteed that calling * `Uri.parse` with the result of this function creates an Uri which is equal @@ -381,7 +391,6 @@ declare namespace monaco { */ MAX_VALUE = 112 } - export class KeyMod { static readonly CtrlCmd: number; static readonly Shift: number; @@ -1370,6 +1379,10 @@ declare namespace monaco.editor { * If set, the decoration will be rendered in the lines decorations with this CSS class name. */ linesDecorationsClassName?: string | null; + /** + * If set, the decoration will be rendered in the lines decorations with this CSS class name, but only for the first line in case of line wrapping. + */ + firstLineDecorationClassName?: string | null; /** * If set, the decoration will be rendered in the margin (covering its full width) with this CSS class name. */ @@ -1539,14 +1552,9 @@ declare namespace monaco.editor { */ range: Range; /** - * The text to replace with. This can be null to emulate a simple delete. + * The text to replace with. This can be empty to emulate a simple delete. */ - text: string | null; - /** - * This indicates that this operation has "insert" semantics. - * i.e. forceMoveMarkers = true => if `range` is collapsed, all markers at the position will be moved. - */ - forceMoveMarkers: boolean; + text: string; } /** @@ -1894,9 +1902,11 @@ declare namespace monaco.editor { * Edit the model without adding the edits to the undo stack. * This can have dire consequences on the undo stack! See @pushEditOperations for the preferred way. * @param operations The edit operations. - * @return The inverse edit operations, that, when applied, will bring the model back to the previous state. + * @return If desired, the inverse edit operations, that, when applied, will bring the model back to the previous state. */ - applyEdits(operations: IIdentifiedSingleEditOperation[]): IValidEditOperation[]; + applyEdits(operations: IIdentifiedSingleEditOperation[]): void; + applyEdits(operations: IIdentifiedSingleEditOperation[], computeUndoEdits: false): void; + applyEdits(operations: IIdentifiedSingleEditOperation[], computeUndoEdits: true): IValidEditOperation[]; /** * Change the end of line sequence without recording in the undo stack. * This can have dire consequences on the undo stack! See @pushEOL for the preferred way. @@ -2660,6 +2670,11 @@ declare namespace monaco.editor { * Defaults to false. */ readOnly?: boolean; + /** + * Rename matching regions on type. + * Defaults to false. + */ + renameOnType?: boolean; /** * Should the editor render validation decorations. * Defaults to editable. @@ -3066,6 +3081,11 @@ declare namespace monaco.editor { * Defaults to all. */ renderLineHighlight?: 'none' | 'gutter' | 'line' | 'all'; + /** + * Control if the current line highlight should be rendered only the editor is focused. + * Defaults to false. + */ + renderLineHighlightOnlyWhenFocus?: boolean; /** * Inserting and deleting whitespace follows tab stops. */ @@ -3625,10 +3645,6 @@ declare namespace monaco.editor { * Overwrite word ends on accept. Default to false. */ insertMode?: 'insert' | 'replace'; - /** - * Show a highlight when suggestion replaces or keep text after the cursor. Defaults to false. - */ - insertHighlight?: boolean; /** * Enable graceful matching. Defaults to true. */ @@ -3749,6 +3765,14 @@ declare namespace monaco.editor { * Show typeParameter-suggestions. */ showTypeParameters?: boolean; + /** + * Show issue-suggestions. + */ + showIssues?: boolean; + /** + * Show user-suggestions. + */ + showUsers?: boolean; /** * Show snippet-suggestions. */ @@ -3869,47 +3893,49 @@ declare namespace monaco.editor { quickSuggestions = 70, quickSuggestionsDelay = 71, readOnly = 72, - renderControlCharacters = 73, - renderIndentGuides = 74, - renderFinalNewline = 75, - renderLineHighlight = 76, - renderValidationDecorations = 77, - renderWhitespace = 78, - revealHorizontalRightPadding = 79, - roundedSelection = 80, - rulers = 81, - scrollbar = 82, - scrollBeyondLastColumn = 83, - scrollBeyondLastLine = 84, - scrollPredominantAxis = 85, - selectionClipboard = 86, - selectionHighlight = 87, - selectOnLineNumbers = 88, - showFoldingControls = 89, - showUnused = 90, - snippetSuggestions = 91, - smoothScrolling = 92, - stopRenderingLineAfter = 93, - suggest = 94, - suggestFontSize = 95, - suggestLineHeight = 96, - suggestOnTriggerCharacters = 97, - suggestSelection = 98, - tabCompletion = 99, - useTabStops = 100, - wordSeparators = 101, - wordWrap = 102, - wordWrapBreakAfterCharacters = 103, - wordWrapBreakBeforeCharacters = 104, - wordWrapColumn = 105, - wordWrapMinified = 106, - wrappingIndent = 107, - wrappingStrategy = 108, - editorClassName = 109, - pixelRatio = 110, - tabFocusMode = 111, - layoutInfo = 112, - wrappingInfo = 113 + renameOnType = 73, + renderControlCharacters = 74, + renderIndentGuides = 75, + renderFinalNewline = 76, + renderLineHighlight = 77, + renderLineHighlightOnlyWhenFocus = 78, + renderValidationDecorations = 79, + renderWhitespace = 80, + revealHorizontalRightPadding = 81, + roundedSelection = 82, + rulers = 83, + scrollbar = 84, + scrollBeyondLastColumn = 85, + scrollBeyondLastLine = 86, + scrollPredominantAxis = 87, + selectionClipboard = 88, + selectionHighlight = 89, + selectOnLineNumbers = 90, + showFoldingControls = 91, + showUnused = 92, + snippetSuggestions = 93, + smoothScrolling = 94, + stopRenderingLineAfter = 95, + suggest = 96, + suggestFontSize = 97, + suggestLineHeight = 98, + suggestOnTriggerCharacters = 99, + suggestSelection = 100, + tabCompletion = 101, + useTabStops = 102, + wordSeparators = 103, + wordWrap = 104, + wordWrapBreakAfterCharacters = 105, + wordWrapBreakBeforeCharacters = 106, + wordWrapColumn = 107, + wordWrapMinified = 108, + wrappingIndent = 109, + wrappingStrategy = 110, + editorClassName = 111, + pixelRatio = 112, + tabFocusMode = 113, + layoutInfo = 114, + wrappingInfo = 115 } export const EditorOptions: { acceptSuggestionOnCommitCharacter: IEditorOption; @@ -3985,10 +4011,12 @@ declare namespace monaco.editor { quickSuggestions: IEditorOption; quickSuggestionsDelay: IEditorOption; readOnly: IEditorOption; + renameOnType: IEditorOption; renderControlCharacters: IEditorOption; renderIndentGuides: IEditorOption; renderFinalNewline: IEditorOption; renderLineHighlight: IEditorOption; + renderLineHighlightOnlyWhenFocus: IEditorOption; renderValidationDecorations: IEditorOption; renderWhitespace: IEditorOption; revealHorizontalRightPadding: IEditorOption; @@ -4941,6 +4969,11 @@ declare namespace monaco.languages { */ export function registerDocumentHighlightProvider(languageId: string, provider: DocumentHighlightProvider): IDisposable; + /** + * Register an on type rename provider. + */ + export function registerOnTypeRenameProvider(languageId: string, provider: OnTypeRenameProvider): IDisposable; + /** * Register a definition provider (used by e.g. go to definition). */ @@ -5364,7 +5397,9 @@ declare namespace monaco.languages { Customcolor = 22, Folder = 23, TypeParameter = 24, - Snippet = 25 + User = 25, + Issue = 26, + Snippet = 27 } export interface CompletionItemLabel { @@ -5373,9 +5408,9 @@ declare namespace monaco.languages { */ name: string; /** - * The signature without the return type. Render after `name`. + * The parameters without the return type. Render after `name`. */ - signature?: string; + parameters?: string; /** * The fully qualified name, like package name or file path. Rendered after `signature`. */ @@ -5699,6 +5734,18 @@ declare namespace monaco.languages { provideDocumentHighlights(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult; } + /** + * The rename provider interface defines the contract between extensions and + * the live-rename feature. + */ + export interface OnTypeRenameProvider { + stopPattern?: RegExp; + /** + * Provide a list of ranges that can be live-renamed together. + */ + provideOnTypeRenameRanges(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult; + } + /** * Value-object that contains additional information when * requesting references. @@ -6036,11 +6083,11 @@ declare namespace monaco.languages { } /** - * A provider of colors for editor models. + * A provider of folding ranges for editor models. */ export interface FoldingRangeProvider { /** - * Provides the color ranges for a specific model. + * Provides the folding ranges for a specific model. */ provideFoldingRanges(model: editor.ITextModel, context: FoldingContext, token: CancellationToken): ProviderResult; } @@ -6183,6 +6230,7 @@ declare namespace monaco.languages { } export interface DocumentSemanticTokensProvider { + onDidChange?: IEvent; getLegend(): SemanticTokensLegend; provideDocumentSemanticTokens(model: editor.ITextModel, lastResultId: string | null, token: CancellationToken): ProviderResult; releaseDocumentSemanticTokens(resultId: string | undefined): void; diff --git a/src/vs/nls.js b/src/vs/nls.js index 8e178e8d0ea..54b63cfb921 100644 --- a/src/vs/nls.js +++ b/src/vs/nls.js @@ -14,6 +14,13 @@ *--------------------------------------------------------------------------------------------- *--------------------------------------------------------------------------------------------*/ 'use strict'; +var __spreadArrays = (this && this.__spreadArrays) || function () { + for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; + for (var r = Array(s), k = 0, i = 0; i < il; i++) + for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) + r[k] = a[j]; + return r; +}; var NLSLoaderPlugin; (function (NLSLoaderPlugin) { var Environment = /** @class */ (function () { @@ -94,7 +101,7 @@ var NLSLoaderPlugin; for (var _i = 2; _i < arguments.length; _i++) { args[_i - 2] = arguments[_i]; } - return localize.apply(void 0, [_this._env, data, message].concat(args)); + return localize.apply(void 0, __spreadArrays([_this._env, data, message], args)); }; } NLSPlugin.prototype.setPseudoTranslation = function (value) { diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index 6b561aaef76..4e3e1dec7a2 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -12,7 +12,7 @@ import { IdGenerator } from 'vs/base/common/idGenerator'; import { IDisposable, toDisposable, MutableDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { isLinux, isWindows } from 'vs/base/common/platform'; import { localize } from 'vs/nls'; -import { ICommandAction, IMenu, IMenuActionOptions, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { ICommandAction, IMenu, IMenuActionOptions, MenuItemAction, SubmenuItemAction, Icon } from 'vs/platform/actions/common/actions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -216,9 +216,10 @@ export class MenuEntryActionViewItem extends ActionViewItem { const keybinding = this._keybindingService.lookupKeybinding(this._commandAction.id); const keybindingLabel = keybinding && keybinding.getLabel(); + const tooltip = this._commandAction.tooltip || this._commandAction.label; this.label.title = keybindingLabel - ? localize('titleAndKb', "{0} ({1})", this._commandAction.label, keybindingLabel) - : this._commandAction.label; + ? localize('titleAndKb', "{0} ({1})", tooltip, keybindingLabel) + : tooltip; } } @@ -237,9 +238,11 @@ export class MenuEntryActionViewItem extends ActionViewItem { _updateItemClass(item: ICommandAction): void { this._itemClassDispose.value = undefined; - if (ThemeIcon.isThemeIcon(item.icon)) { + const icon = this._commandAction.checked && (item.toggled as { icon?: Icon })?.icon ? (item.toggled as { icon: Icon }).icon : item.icon; + + if (ThemeIcon.isThemeIcon(icon)) { // theme icons - const iconClass = ThemeIcon.asClassName(item.icon); + const iconClass = ThemeIcon.asClassName(icon); if (this.label && iconClass) { addClasses(this.label, iconClass); this._itemClassDispose.value = toDisposable(() => { @@ -249,20 +252,20 @@ export class MenuEntryActionViewItem extends ActionViewItem { }); } - } else if (item.icon) { + } else if (icon) { // icon path let iconClass: string; - if (item.icon?.dark?.scheme) { + if (icon?.dark?.scheme) { - const iconPathMapKey = item.icon.dark.toString(); + const iconPathMapKey = icon.dark.toString(); if (MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) { iconClass = MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!; } else { iconClass = ids.nextId(); - createCSSRule(`.icon.${iconClass}`, `background-image: ${asCSSUrl(item.icon.light || item.icon.dark)}`); - createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: ${asCSSUrl(item.icon.dark)}`); + createCSSRule(`.icon.${iconClass}`, `background-image: ${asCSSUrl(icon.light || icon.dark)}`); + createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: ${asCSSUrl(icon.dark)}`); MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass); } diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 72dc61aa5af..b8109ba8e32 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -20,13 +20,16 @@ export interface ILocalizedString { original: string; } +export type Icon = { dark?: URI; light?: URI; } | ThemeIcon; + export interface ICommandAction { id: string; title: string | ILocalizedString; category?: string | ILocalizedString; - icon?: { dark?: URI; light?: URI; } | ThemeIcon; + tooltip?: string | ILocalizedString; + icon?: Icon; precondition?: ContextKeyExpression; - toggled?: ContextKeyExpression; + toggled?: ContextKeyExpression | { condition: ContextKeyExpression, icon?: Icon, tooltip?: string | ILocalizedString }; } export type ISerializableCommandAction = UriDto; @@ -89,6 +92,7 @@ export class MenuId { static readonly MenubarSwitchGroupMenu = new MenuId('MenubarSwitchGroupMenu'); static readonly MenubarTerminalMenu = new MenuId('MenubarTerminalMenu'); static readonly MenubarViewMenu = new MenuId('MenubarViewMenu'); + static readonly MenubarWebNavigationMenu = new MenuId('MenubarWebNavigationMenu'); static readonly OpenEditorsContext = new MenuId('OpenEditorsContext'); static readonly ProblemsPanelContext = new MenuId('ProblemsPanelContext'); static readonly SCMChangeContext = new MenuId('SCMChangeContext'); @@ -111,11 +115,13 @@ export class MenuId { static readonly CommentThreadActions = new MenuId('CommentThreadActions'); static readonly CommentTitle = new MenuId('CommentTitle'); static readonly CommentActions = new MenuId('CommentActions'); + static readonly NotebookCellTitle = new MenuId('NotebookCellTitle'); static readonly BulkEditTitle = new MenuId('BulkEditTitle'); static readonly BulkEditContext = new MenuId('BulkEditContext'); static readonly TimelineItemContext = new MenuId('TimelineItemContext'); static readonly TimelineTitle = new MenuId('TimelineTitle'); static readonly TimelineTitleContext = new MenuId('TimelineTitleContext'); + static readonly AccountsContext = new MenuId('AccountsContext'); readonly id: number; readonly _debugName: string; @@ -275,9 +281,20 @@ export class MenuItemAction extends ExecuteCommandAction { @ICommandService commandService: ICommandService ) { typeof item.title === 'string' ? super(item.id, item.title, commandService) : super(item.id, item.title.value, commandService); + this._cssClass = undefined; this._enabled = !item.precondition || contextKeyService.contextMatchesRules(item.precondition); - this._checked = Boolean(item.toggled && contextKeyService.contextMatchesRules(item.toggled)); + this._tooltip = item.tooltip ? typeof item.tooltip === 'string' ? item.tooltip : item.tooltip.value : undefined; + + if (item.toggled) { + const toggled = ((item.toggled as { condition: ContextKeyExpression }).condition ? item.toggled : { condition: item.toggled }) as { + condition: ContextKeyExpression, icon?: Icon, tooltip?: string | ILocalizedString + }; + this._checked = contextKeyService.contextMatchesRules(toggled.condition); + if (this._checked && toggled.tooltip) { + this._tooltip = typeof toggled.tooltip === 'string' ? toggled.tooltip : toggled.tooltip.value; + } + } this._options = options || {}; @@ -396,39 +413,41 @@ export function registerAction2(ctor: { new(): Action2 }): IDisposable { const disposables = new DisposableStore(); const action = new ctor(); + const { f1, menu, keybinding, description, ...command } = action.desc; + // command disposables.add(CommandsRegistry.registerCommand({ - id: action.desc.id, + id: command.id, handler: (accessor, ...args) => action.run(accessor, ...args), - description: action.desc.description, + description: description, })); // menu - if (Array.isArray(action.desc.menu)) { - for (let item of action.desc.menu) { - disposables.add(MenuRegistry.appendMenuItem(item.id, { command: action.desc, ...item })); + if (Array.isArray(menu)) { + for (let item of menu) { + disposables.add(MenuRegistry.appendMenuItem(item.id, { command: { ...command }, ...item })); } - } else if (action.desc.menu) { - disposables.add(MenuRegistry.appendMenuItem(action.desc.menu.id, { command: action.desc, ...action.desc.menu })); + } else if (menu) { + disposables.add(MenuRegistry.appendMenuItem(menu.id, { command: { ...command }, ...menu })); } - if (action.desc.f1) { - disposables.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: action.desc, ...action.desc })); + if (f1) { + disposables.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: command })); } // keybinding - if (Array.isArray(action.desc.keybinding)) { - for (let item of action.desc.keybinding) { + if (Array.isArray(keybinding)) { + for (let item of keybinding) { KeybindingsRegistry.registerKeybindingRule({ ...item, - id: action.desc.id, - when: ContextKeyExpr.and(action.desc.precondition, item.when) + id: command.id, + when: command.precondition ? ContextKeyExpr.and(command.precondition, item.when) : item.when }); } - } else if (action.desc.keybinding) { + } else if (keybinding) { KeybindingsRegistry.registerKeybindingRule({ - ...action.desc.keybinding, - id: action.desc.id, - when: ContextKeyExpr.and(action.desc.precondition, action.desc.keybinding.when) + ...keybinding, + id: command.id, + when: command.precondition ? ContextKeyExpr.and(command.precondition, keybinding.when) : keybinding.when }); } diff --git a/src/vs/platform/actions/common/menuService.ts b/src/vs/platform/actions/common/menuService.ts index 2bfa84d15eb..96987a4c911 100644 --- a/src/vs/platform/actions/common/menuService.ts +++ b/src/vs/platform/actions/common/menuService.ts @@ -94,7 +94,8 @@ class Menu implements IMenu { // keep toggled keys for event if applicable if (isIMenuItem(item) && item.command.toggled) { - Menu._fillInKbExprKeys(item.command.toggled, this._contextKeys); + const toggledExpression: ContextKeyExpression = (item.command.toggled as { condition: ContextKeyExpression }).condition || item.command.toggled; + Menu._fillInKbExprKeys(toggledExpression, this._contextKeys); } } this._onDidChange.fire(this); diff --git a/src/vs/platform/backup/electron-main/backup.ts b/src/vs/platform/backup/electron-main/backup.ts index 2b471039ae0..3c314a3b61c 100644 --- a/src/vs/platform/backup/electron-main/backup.ts +++ b/src/vs/platform/backup/electron-main/backup.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { URI } from 'vs/base/common/uri'; import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; @@ -15,6 +15,12 @@ export interface IWorkspaceBackupInfo { remoteAuthority?: string; } +export function isWorkspaceBackupInfo(obj: unknown): obj is IWorkspaceBackupInfo { + const candidate = obj as IWorkspaceBackupInfo; + + return candidate && isWorkspaceIdentifier(candidate.workspace); +} + export interface IBackupMainService { _serviceBrand: undefined; @@ -31,4 +37,12 @@ export interface IBackupMainService { unregisterWorkspaceBackupSync(workspace: IWorkspaceIdentifier): void; unregisterFolderBackupSync(folderUri: URI): void; unregisterEmptyWindowBackupSync(backupFolder: string): void; + + /** + * All folders or workspaces that are known to have + * backups stored. This call is long running because + * it checks for each backup location if any backups + * are stored. + */ + getDirtyWorkspaces(): Promise>; } diff --git a/src/vs/platform/backup/electron-main/backupMainService.ts b/src/vs/platform/backup/electron-main/backupMainService.ts index 910457adf19..74f841ef6f7 100644 --- a/src/vs/platform/backup/electron-main/backupMainService.ts +++ b/src/vs/platform/backup/electron-main/backupMainService.ts @@ -8,8 +8,7 @@ import * as crypto from 'crypto'; import * as path from 'vs/base/common/path'; import * as platform from 'vs/base/common/platform'; import { writeFileSync, writeFile, readFile, readdir, exists, rimraf, rename, RimRafMode } from 'vs/base/node/pfs'; -import * as arrays from 'vs/base/common/arrays'; -import { IBackupMainService, IWorkspaceBackupInfo } from 'vs/platform/backup/electron-main/backup'; +import { IBackupMainService, IWorkspaceBackupInfo, isWorkspaceBackupInfo } from 'vs/platform/backup/electron-main/backup'; import { IBackupWorkspacesFormat, IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -28,9 +27,9 @@ export class BackupMainService implements IBackupMainService { protected backupHome: string; protected workspacesJsonPath: string; - private rootWorkspaces: IWorkspaceBackupInfo[] = []; - private folderWorkspaces: URI[] = []; - private emptyWorkspaces: IEmptyWindowBackupInfo[] = []; + private workspaces: IWorkspaceBackupInfo[] = []; + private folders: URI[] = []; + private emptyWindows: IEmptyWindowBackupInfo[] = []; constructor( @IEnvironmentService environmentService: IEnvironmentService, @@ -51,31 +50,31 @@ export class BackupMainService implements IBackupMainService { // read empty workspaces backups first if (backups.emptyWorkspaceInfos) { - this.emptyWorkspaces = await this.validateEmptyWorkspaces(backups.emptyWorkspaceInfos); + this.emptyWindows = await this.validateEmptyWorkspaces(backups.emptyWorkspaceInfos); } else if (Array.isArray(backups.emptyWorkspaces)) { // read legacy entries - this.emptyWorkspaces = await this.validateEmptyWorkspaces(backups.emptyWorkspaces.map(backupFolder => ({ backupFolder }))); + this.emptyWindows = await this.validateEmptyWorkspaces(backups.emptyWorkspaces.map(emptyWindow => ({ backupFolder: emptyWindow }))); } // read workspace backups let rootWorkspaces: IWorkspaceBackupInfo[] = []; try { if (Array.isArray(backups.rootURIWorkspaces)) { - rootWorkspaces = backups.rootURIWorkspaces.map(f => ({ workspace: { id: f.id, configPath: URI.parse(f.configURIPath) }, remoteAuthority: f.remoteAuthority })); + rootWorkspaces = backups.rootURIWorkspaces.map(workspace => ({ workspace: { id: workspace.id, configPath: URI.parse(workspace.configURIPath) }, remoteAuthority: workspace.remoteAuthority })); } else if (Array.isArray(backups.rootWorkspaces)) { - rootWorkspaces = backups.rootWorkspaces.map(f => ({ workspace: { id: f.id, configPath: URI.file(f.configPath) } })); + rootWorkspaces = backups.rootWorkspaces.map(workspace => ({ workspace: { id: workspace.id, configPath: URI.file(workspace.configPath) } })); } } catch (e) { // ignore URI parsing exceptions } - this.rootWorkspaces = await this.validateWorkspaces(rootWorkspaces); + this.workspaces = await this.validateWorkspaces(rootWorkspaces); // read folder backups let workspaceFolders: URI[] = []; try { if (Array.isArray(backups.folderURIWorkspaces)) { - workspaceFolders = backups.folderURIWorkspaces.map(f => URI.parse(f)); + workspaceFolders = backups.folderURIWorkspaces.map(folder => URI.parse(folder)); } else if (Array.isArray(backups.folderWorkspaces)) { // migrate legacy folder paths workspaceFolders = []; @@ -93,7 +92,7 @@ export class BackupMainService implements IBackupMainService { // ignore URI parsing exceptions } - this.folderWorkspaces = await this.validateFolders(workspaceFolders); + this.folders = await this.validateFolders(workspaceFolders); // save again in case some workspaces or folders have been removed await this.save(); @@ -106,7 +105,7 @@ export class BackupMainService implements IBackupMainService { return []; } - return this.rootWorkspaces.slice(0); // return a copy + return this.workspaces.slice(0); // return a copy } getFolderBackupPaths(): URI[] { @@ -116,7 +115,7 @@ export class BackupMainService implements IBackupMainService { return []; } - return this.folderWorkspaces.slice(0); // return a copy + return this.folders.slice(0); // return a copy } isHotExitEnabled(): boolean { @@ -134,12 +133,12 @@ export class BackupMainService implements IBackupMainService { } getEmptyWindowBackupPaths(): IEmptyWindowBackupInfo[] { - return this.emptyWorkspaces.slice(0); // return a copy + return this.emptyWindows.slice(0); // return a copy } registerWorkspaceBackupSync(workspaceInfo: IWorkspaceBackupInfo, migrateFrom?: string): string { - if (!this.rootWorkspaces.some(window => workspaceInfo.workspace.id === window.workspace.id)) { - this.rootWorkspaces.push(workspaceInfo); + if (!this.workspaces.some(workspace => workspaceInfo.workspace.id === workspace.workspace.id)) { + this.workspaces.push(workspaceInfo); this.saveSync(); } @@ -188,16 +187,16 @@ export class BackupMainService implements IBackupMainService { unregisterWorkspaceBackupSync(workspace: IWorkspaceIdentifier): void { const id = workspace.id; - let index = arrays.firstIndex(this.rootWorkspaces, w => w.workspace.id === id); + const index = this.workspaces.findIndex(workspace => workspace.workspace.id === id); if (index !== -1) { - this.rootWorkspaces.splice(index, 1); + this.workspaces.splice(index, 1); this.saveSync(); } } registerFolderBackupSync(folderUri: URI): string { - if (!this.folderWorkspaces.some(uri => areResourcesEquals(folderUri, uri))) { - this.folderWorkspaces.push(folderUri); + if (!this.folders.some(folder => areResourcesEquals(folderUri, folder))) { + this.folders.push(folderUri); this.saveSync(); } @@ -205,9 +204,9 @@ export class BackupMainService implements IBackupMainService { } unregisterFolderBackupSync(folderUri: URI): void { - let index = arrays.firstIndex(this.folderWorkspaces, uri => areResourcesEquals(folderUri, uri)); + const index = this.folders.findIndex(folder => areResourcesEquals(folderUri, folder)); if (index !== -1) { - this.folderWorkspaces.splice(index, 1); + this.folders.splice(index, 1); this.saveSync(); } } @@ -216,8 +215,8 @@ export class BackupMainService implements IBackupMainService { // Generate a new folder if this is a new empty workspace const backupFolder = backupFolderCandidate || this.getRandomEmptyWindowId(); - if (!this.emptyWorkspaces.some(window => !!window.backupFolder && isEqual(window.backupFolder, backupFolder, !platform.isLinux))) { - this.emptyWorkspaces.push({ backupFolder, remoteAuthority }); + if (!this.emptyWindows.some(emptyWindow => !!emptyWindow.backupFolder && isEqual(emptyWindow.backupFolder, backupFolder, !platform.isLinux))) { + this.emptyWindows.push({ backupFolder, remoteAuthority }); this.saveSync(); } @@ -225,9 +224,9 @@ export class BackupMainService implements IBackupMainService { } unregisterEmptyWindowBackupSync(backupFolder: string): void { - let index = arrays.firstIndex(this.emptyWorkspaces, w => !!w.backupFolder && isEqual(w.backupFolder, backupFolder, !platform.isLinux)); + const index = this.emptyWindows.findIndex(emptyWindow => !!emptyWindow.backupFolder && isEqual(emptyWindow.backupFolder, backupFolder, !platform.isLinux)); if (index !== -1) { - this.emptyWorkspaces.splice(index, 1); + this.emptyWindows.splice(index, 1); this.saveSync(); } } @@ -255,7 +254,7 @@ export class BackupMainService implements IBackupMainService { seenIds.add(workspace.id); const backupPath = this.getBackupPath(workspace.id); - const hasBackups = await this.hasBackups(backupPath); + const hasBackups = await this.doHasBackups(backupPath); // If the workspace has no backups, ignore it if (hasBackups) { @@ -287,7 +286,7 @@ export class BackupMainService implements IBackupMainService { seenIds.add(key); const backupPath = this.getBackupPath(this.getFolderHash(folderURI)); - const hasBackups = await this.hasBackups(backupPath); + const hasBackups = await this.doHasBackups(backupPath); // If the folder has no backups, ignore it if (hasBackups) { @@ -325,7 +324,7 @@ export class BackupMainService implements IBackupMainService { seenIds.add(backupFolder); const backupPath = this.getBackupPath(backupFolder); - if (await this.hasBackups(backupPath)) { + if (await this.doHasBackups(backupPath)) { result.push(backupInfo); } else { await this.deleteStaleBackup(backupPath); @@ -350,7 +349,7 @@ export class BackupMainService implements IBackupMainService { // New empty window backup let newBackupFolder = this.getRandomEmptyWindowId(); - while (this.emptyWorkspaces.some(window => !!window.backupFolder && isEqual(window.backupFolder, newBackupFolder, platform.isLinux))) { + while (this.emptyWindows.some(emptyWindow => !!emptyWindow.backupFolder && isEqual(emptyWindow.backupFolder, newBackupFolder, platform.isLinux))) { newBackupFolder = this.getRandomEmptyWindowId(); } @@ -362,7 +361,7 @@ export class BackupMainService implements IBackupMainService { this.logService.error(`Backup: Could not rename backup folder: ${ex.toString()}`); return false; } - this.emptyWorkspaces.push({ backupFolder: newBackupFolder }); + this.emptyWindows.push({ backupFolder: newBackupFolder }); return true; } @@ -371,7 +370,7 @@ export class BackupMainService implements IBackupMainService { // New empty window backup let newBackupFolder = this.getRandomEmptyWindowId(); - while (this.emptyWorkspaces.some(window => !!window.backupFolder && isEqual(window.backupFolder, newBackupFolder, platform.isLinux))) { + while (this.emptyWindows.some(emptyWindow => !!emptyWindow.backupFolder && isEqual(emptyWindow.backupFolder, newBackupFolder, platform.isLinux))) { newBackupFolder = this.getRandomEmptyWindowId(); } @@ -383,12 +382,53 @@ export class BackupMainService implements IBackupMainService { this.logService.error(`Backup: Could not rename backup folder: ${ex.toString()}`); return false; } - this.emptyWorkspaces.push({ backupFolder: newBackupFolder }); + this.emptyWindows.push({ backupFolder: newBackupFolder }); return true; } - private async hasBackups(backupPath: string): Promise { + async getDirtyWorkspaces(): Promise> { + const dirtyWorkspaces: Array = []; + + // Workspaces with backups + for (const workspace of this.workspaces) { + if ((await this.hasBackups(workspace))) { + dirtyWorkspaces.push(workspace.workspace); + } + } + + // Folders with backups + for (const folder of this.folders) { + if ((await this.hasBackups(folder))) { + dirtyWorkspaces.push(folder); + } + } + + return dirtyWorkspaces; + } + + private hasBackups(backupLocation: IWorkspaceBackupInfo | IEmptyWindowBackupInfo | URI): Promise { + let backupPath: string; + + // Folder + if (URI.isUri(backupLocation)) { + backupPath = this.getBackupPath(this.getFolderHash(backupLocation)); + } + + // Workspace + else if (isWorkspaceBackupInfo(backupLocation)) { + backupPath = this.getBackupPath(backupLocation.workspace.id); + } + + // Empty + else { + backupPath = backupLocation.backupFolder; + } + + return this.doHasBackups(backupPath); + } + + private async doHasBackups(backupPath: string): Promise { try { const backupSchemas = await readdir(backupPath); @@ -427,10 +467,10 @@ export class BackupMainService implements IBackupMainService { private serializeBackups(): IBackupWorkspacesFormat { return { - rootURIWorkspaces: this.rootWorkspaces.map(f => ({ id: f.workspace.id, configURIPath: f.workspace.configPath.toString(), remoteAuthority: f.remoteAuthority })), - folderURIWorkspaces: this.folderWorkspaces.map(f => f.toString()), - emptyWorkspaceInfos: this.emptyWorkspaces, - emptyWorkspaces: this.emptyWorkspaces.map(info => info.backupFolder) + rootURIWorkspaces: this.workspaces.map(workspace => ({ id: workspace.workspace.id, configURIPath: workspace.workspace.configPath.toString(), remoteAuthority: workspace.remoteAuthority })), + folderURIWorkspaces: this.folders.map(folder => folder.toString()), + emptyWorkspaceInfos: this.emptyWindows, + emptyWorkspaces: this.emptyWindows.map(emptyWindow => emptyWindow.backupFolder) }; } diff --git a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts index 0bffaa82992..14d6bf48f10 100644 --- a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts +++ b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts @@ -22,6 +22,7 @@ import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { createHash } from 'crypto'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { Schemas } from 'vs/base/common/network'; +import { isEqual } from 'vs/base/common/resources'; suite('BackupMainService', () => { @@ -731,4 +732,45 @@ suite('BackupMainService', () => { } }); }); + + suite('getDirtyWorkspaces', () => { + test('should report if a workspace or folder has backups', async () => { + const folderBackupPath = service.registerFolderBackupSync(fooFile); + + const backupWorkspaceInfo = toWorkspaceBackupInfo(fooFile.fsPath); + const workspaceBackupPath = service.registerWorkspaceBackupSync(backupWorkspaceInfo); + + assert.equal(((await service.getDirtyWorkspaces()).length), 0); + + try { + await pfs.mkdirp(path.join(folderBackupPath, Schemas.file)); + await pfs.mkdirp(path.join(workspaceBackupPath, Schemas.untitled)); + } catch (error) { + // ignore - folder might exist already + } + + assert.equal(((await service.getDirtyWorkspaces()).length), 0); + + fs.writeFileSync(path.join(folderBackupPath, Schemas.file, '594a4a9d82a277a899d4713a5b08f504'), ''); + fs.writeFileSync(path.join(workspaceBackupPath, Schemas.untitled, '594a4a9d82a277a899d4713a5b08f504'), ''); + + const dirtyWorkspaces = await service.getDirtyWorkspaces(); + assert.equal(dirtyWorkspaces.length, 2); + + let found = 0; + for (const dirtyWorkpspace of dirtyWorkspaces) { + if (URI.isUri(dirtyWorkpspace)) { + if (isEqual(fooFile, dirtyWorkpspace)) { + found++; + } + } else { + if (isEqual(backupWorkspaceInfo.workspace.configPath, dirtyWorkpspace.configPath)) { + found++; + } + } + } + + assert.equal(found, 2); + }); + }); }); diff --git a/src/vs/platform/clipboard/browser/clipboardService.ts b/src/vs/platform/clipboard/browser/clipboardService.ts index aaaf41477ba..42c52b3b6ef 100644 --- a/src/vs/platform/clipboard/browser/clipboardService.ts +++ b/src/vs/platform/clipboard/browser/clipboardService.ts @@ -54,7 +54,7 @@ export class BrowserClipboardService implements IClipboardService { } readFindText(): string { - // @ts-ignore + // @ts-expect-error return undefined; } diff --git a/src/vs/platform/commands/common/commands.ts b/src/vs/platform/commands/common/commands.ts index 2110663021f..8f327300c1e 100644 --- a/src/vs/platform/commands/common/commands.ts +++ b/src/vs/platform/commands/common/commands.ts @@ -10,6 +10,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { LinkedList } from 'vs/base/common/linkedList'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { keys } from 'vs/base/common/map'; +import { Iterable } from 'vs/base/common/iterator'; export const ICommandService = createDecorator('commandService'); @@ -119,7 +120,7 @@ export const CommandsRegistry: ICommandRegistry = new class implements ICommandR if (!list || list.isEmpty()) { return undefined; } - return list.iterator().next().value; + return Iterable.first(list); } getCommands(): ICommandsMap { diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 2937bca8f1f..16bf8c384f9 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -265,8 +265,12 @@ export function addToValueTree(settingsTreeRoot: any, key: string, value: any, c curr = obj; } - if (typeof curr === 'object') { - curr[last] = value; // workaround https://github.com/Microsoft/vscode/issues/13606 + if (typeof curr === 'object' && curr !== null) { + try { + curr[last] = value; // workaround https://github.com/Microsoft/vscode/issues/13606 + } catch (e) { + conflictReporter(`Ignoring ${key} as ${segments.join('.')} is ${JSON.stringify(curr)}`); + } } else { conflictReporter(`Ignoring ${key} as ${segments.join('.')} is ${JSON.stringify(curr)}`); } diff --git a/src/vs/platform/configuration/test/node/configurationService.test.ts b/src/vs/platform/configuration/test/node/configurationService.test.ts index 15b3d915ed6..f5fb471fcdb 100644 --- a/src/vs/platform/configuration/test/node/configurationService.test.ts +++ b/src/vs/platform/configuration/test/node/configurationService.test.ts @@ -22,6 +22,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { Schemas } from 'vs/base/common/network'; import { IFileService } from 'vs/platform/files/common/files'; +import { VSBuffer } from 'vs/base/common/buffer'; suite('ConfigurationService - Node', () => { @@ -110,10 +111,10 @@ suite('ConfigurationService - Node', () => { test('trigger configuration change event when file does not exist', async () => { const res = await testFile('config', 'config.json'); - - const service = new ConfigurationService(URI.file(res.testFile), fileService); + const settingsFile = URI.file(res.testFile); + const service = new ConfigurationService(settingsFile, fileService); await service.initialize(); - return new Promise((c, e) => { + return new Promise(async (c, e) => { const disposable = Event.filter(service.onDidChangeConfiguration, e => e.source === ConfigurationTarget.USER)(async (e) => { disposable.dispose(); assert.equal(service.getValue('foo'), 'bar'); @@ -121,7 +122,7 @@ suite('ConfigurationService - Node', () => { await res.cleanUp(); c(); }); - fs.writeFileSync(res.testFile, '{ "foo": "bar" }'); + await fileService.writeFile(settingsFile, VSBuffer.fromString('{ "foo": "bar" }')); }); }); diff --git a/src/vs/platform/contextkey/browser/contextKeyService.ts b/src/vs/platform/contextkey/browser/contextKeyService.ts index 58d6e846e52..78f7843ea46 100644 --- a/src/vs/platform/contextkey/browser/contextKeyService.ts +++ b/src/vs/platform/contextkey/browser/contextKeyService.ts @@ -142,6 +142,10 @@ class ConfigAwareContextValuesContainer extends Context { case 'string': value = configValue; break; + default: + if (Array.isArray(configValue)) { + value = JSON.stringify(configValue); + } } this._values.set(key, value); diff --git a/src/vs/platform/contextkey/common/contextkey.ts b/src/vs/platform/contextkey/common/contextkey.ts index 6f7ca973cc9..414b6a3d3c0 100644 --- a/src/vs/platform/contextkey/common/contextkey.ts +++ b/src/vs/platform/contextkey/common/contextkey.ts @@ -6,7 +6,7 @@ import { Event } from 'vs/base/common/event'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { isMacintosh, isLinux, isWindows } from 'vs/base/common/platform'; +import { isMacintosh, isLinux, isWindows, isWeb } from 'vs/base/common/platform'; const STATIC_VALUES = new Map(); STATIC_VALUES.set('false', false); @@ -14,6 +14,8 @@ STATIC_VALUES.set('true', true); STATIC_VALUES.set('isMac', isMacintosh); STATIC_VALUES.set('isLinux', isLinux); STATIC_VALUES.set('isWindows', isWindows); +STATIC_VALUES.set('isWeb', isWeb); +STATIC_VALUES.set('isMacNative', isMacintosh && !isWeb); export const enum ContextKeyExprType { False = 0, diff --git a/src/vs/platform/contextkey/common/contextkeys.ts b/src/vs/platform/contextkey/common/contextkeys.ts index 4f8959e04ff..8c17906ab67 100644 --- a/src/vs/platform/contextkey/common/contextkeys.ts +++ b/src/vs/platform/contextkey/common/contextkeys.ts @@ -4,8 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { isMacintosh, isLinux, isWindows, isWeb } from 'vs/base/common/platform'; + +export const IsMacContext = new RawContextKey('isMac', isMacintosh); +export const IsLinuxContext = new RawContextKey('isLinux', isLinux); +export const IsWindowsContext = new RawContextKey('isWindows', isWindows); + +export const IsWebContext = new RawContextKey('isWeb', isWeb); +export const IsMacNativeContext = new RawContextKey('isMacNative', isMacintosh && !isWeb); + +export const IsDevelopmentContext = new RawContextKey('isDevelopment', false); export const InputFocusedContextKey = 'inputFocus'; export const InputFocusedContext = new RawContextKey(InputFocusedContextKey, false); - -export const FalseContext = new RawContextKey('__false', false); diff --git a/src/vs/platform/editor/common/editor.ts b/src/vs/platform/editor/common/editor.ts index 11e627666d7..723ab723a32 100644 --- a/src/vs/platform/editor/common/editor.ts +++ b/src/vs/platform/editor/common/editor.ts @@ -24,7 +24,7 @@ export interface IEditorModel { dispose(): void; } -export interface IBaseResourceInput { +export interface IBaseResourceEditorInput { /** * Optional options to use when opening the text input. @@ -60,12 +60,12 @@ export interface IBaseResourceInput { readonly forceUntitled?: boolean; } -export interface IResourceInput extends IBaseResourceInput { +export interface IResourceEditorInput extends IBaseResourceEditorInput { /** * The resource URI of the resource to open. */ - resource: URI; + readonly resource: URI; /** * The encoding of the text input if known. diff --git a/src/vs/platform/electron/electron-main/electronMainService.ts b/src/vs/platform/electron/electron-main/electronMainService.ts index 27ef89f17e2..4d593ec7495 100644 --- a/src/vs/platform/electron/electron-main/electronMainService.ts +++ b/src/vs/platform/electron/electron-main/electronMainService.ts @@ -20,6 +20,7 @@ import { dirExists } from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; import { ITelemetryData, ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; export interface IElectronMainService extends AddFirstParameterToFunctions /* only methods, not events */, number | undefined /* window ID */> { } @@ -34,7 +35,8 @@ export class ElectronMainService implements IElectronMainService { @IDialogMainService private readonly dialogMainService: IDialogMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @ITelemetryService private readonly telemetryService: ITelemetryService + @ITelemetryService private readonly telemetryService: ITelemetryService, + @ILogService private readonly logService: ILogService ) { } @@ -63,7 +65,8 @@ export class ElectronMainService implements IElectronMainService { workspace: window.openedWorkspace, folderUri: window.openedFolderUri, title: window.win.getTitle(), - filename: window.getRepresentedFilename() + filename: window.getRepresentedFilename(), + dirty: window.isDocumentEdited() })); } @@ -269,7 +272,7 @@ export class ElectronMainService implements IElectronMainService { async setDocumentEdited(windowId: number | undefined, edited: boolean): Promise { const window = this.windowById(windowId); if (window) { - window.win.setDocumentEdited(edited); + window.setDocumentEdited(edited); } } @@ -330,7 +333,11 @@ export class ElectronMainService implements IElectronMainService { } async closeWindow(windowId: number | undefined): Promise { - const window = this.windowById(windowId); + this.closeWindowById(windowId, windowId); + } + + async closeWindowById(currentWindowId: number | undefined, targetWindowId?: number | undefined): Promise { + const window = this.windowById(targetWindowId); if (window) { return window.win.close(); } @@ -392,6 +399,7 @@ export class ElectronMainService implements IElectronMainService { async startCrashReporter(windowId: number | undefined, options: CrashReporterStartOptions): Promise { crashReporter.start(options); + this.logService.trace('ElectronMainService#crashReporter', JSON.stringify(options)); } //#endregion diff --git a/src/vs/platform/electron/node/electron.ts b/src/vs/platform/electron/node/electron.ts index 8803fd16b39..2316d70dafc 100644 --- a/src/vs/platform/electron/node/electron.ts +++ b/src/vs/platform/electron/node/electron.ts @@ -74,6 +74,7 @@ export interface IElectronService { relaunch(options?: { addArgs?: string[], removeArgs?: string[] }): Promise; reload(options?: { disableExtensions?: boolean }): Promise; closeWindow(): Promise; + closeWindowById(windowId: number): Promise; quit(): Promise; // Development diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index abd1e33b185..aa44ee75d77 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -39,7 +39,6 @@ export interface ParsedArgs { 'builtin-extensions-dir'?: string; extensionDevelopmentPath?: string[]; // // undefined or array of 1 or more local paths or URIs extensionTestsPath?: string; // either a local path or a URI - 'extension-development-confirm-save'?: boolean; 'inspect-extensions'?: string; 'inspect-brk-extensions'?: string; debugId?: string; @@ -57,7 +56,7 @@ export interface ParsedArgs { 'open-url'?: boolean; 'skip-getting-started'?: boolean; 'skip-release-notes'?: boolean; - 'sticky-quickopen'?: boolean; + 'sticky-quickinput'?: boolean; 'disable-restore-windows'?: boolean; 'disable-telemetry'?: boolean; 'export-default-configuration'?: string; @@ -74,6 +73,7 @@ export interface ParsedArgs { 'disable-user-env-probe'?: boolean; 'force'?: boolean; 'force-user-env'?: boolean; + 'sync'?: 'on' | 'off'; // chromium command line args: https://electronjs.org/docs/all#supported-chrome-command-line-switches 'no-proxy-server'?: boolean; @@ -87,7 +87,7 @@ export interface ParsedArgs { 'nolazy'?: boolean; 'force-device-scale-factor'?: string; 'force-renderer-accessibility'?: boolean; - 'ignore-certificate-error'?: boolean; + 'ignore-certificate-errors'?: boolean; 'allow-insecure-localhost'?: boolean; } @@ -111,7 +111,6 @@ export interface IEnvironmentService extends IUserHomeProvider { args: ParsedArgs; execPath: string; - cliPath: string; appRoot: string; userHome: string; @@ -125,14 +124,12 @@ export interface IEnvironmentService extends IUserHomeProvider { keybindingsResource: URI; keyboardLayoutResource: URI; argvResource: URI; + snippetsHome: URI; // sync resources userDataSyncLogResource: URI; userDataSyncHome: URI; - settingsSyncPreviewResource: URI; - keybindingsSyncPreviewResource: URI; - machineSettingsHome: URI; machineSettingsResource: URI; globalStorageHome: string; @@ -149,15 +146,13 @@ export interface IEnvironmentService extends IUserHomeProvider { extensionsPath?: string; extensionDevelopmentLocationURI?: URI[]; extensionTestsLocationURI?: URI; + extensionEnabledProposedApi?: string[] | undefined; logExtensionHostCommunication?: boolean; debugExtensionHost: IExtensionHostDebugParams; isBuilt: boolean; - wait: boolean; - status: boolean; - log?: string; logsPath: string; verbose: boolean; @@ -168,10 +163,9 @@ export interface IEnvironmentService extends IUserHomeProvider { installSourcePath: string; disableUpdates: boolean; - disableCrashReporter: boolean; driverHandle?: string; driverVerbose: boolean; - galleryMachineIdResource?: URI; + serviceMachineIdResource?: URI; } diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index e68e0647c32..c43ccfd997b 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as minimist from 'vscode-minimist'; +import * as minimist from 'minimist'; import * as os from 'os'; import { localize } from 'vs/nls'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; @@ -43,15 +43,13 @@ export const OPTIONS: OptionDescriptions> = { 'goto': { type: 'boolean', cat: 'o', alias: 'g', args: 'file:line[:character]', description: localize('goto', "Open a file at the path on the specified line and character position.") }, 'new-window': { type: 'boolean', cat: 'o', alias: 'n', description: localize('newWindow', "Force to open a new window.") }, 'reuse-window': { type: 'boolean', cat: 'o', alias: 'r', description: localize('reuseWindow', "Force to open a file or folder in an already opened window.") }, + 'folder-uri': { type: 'string[]', cat: 'o', args: 'uri', description: localize('folderUri', "Opens a window with given folder uri(s)") }, + 'file-uri': { type: 'string[]', cat: 'o', args: 'uri', description: localize('fileUri', "Opens a window with given file uri(s)") }, 'wait': { type: 'boolean', cat: 'o', alias: 'w', description: localize('wait', "Wait for the files to be closed before returning.") }, 'waitMarkerFilePath': { type: 'string' }, 'locale': { type: 'string', cat: 'o', args: 'locale', description: localize('locale', "The locale to use (e.g. en-US or zh-TW).") }, 'user-data-dir': { type: 'string', cat: 'o', args: 'dir', description: localize('userDataDir', "Specifies the directory that user data is kept in. Can be used to open multiple distinct instances of Code.") }, - 'version': { type: 'boolean', cat: 'o', alias: 'v', description: localize('version', "Print version.") }, 'help': { type: 'boolean', cat: 'o', alias: 'h', description: localize('help', "Print usage.") }, - 'telemetry': { type: 'boolean', cat: 'o', description: localize('telemetry', "Shows all telemetry events which VS code collects.") }, - 'folder-uri': { type: 'string[]', cat: 'o', args: 'uri', description: localize('folderUri', "Opens a window with given folder uri(s)") }, - 'file-uri': { type: 'string[]', cat: 'o', args: 'uri', description: localize('fileUri', "Opens a window with given file uri(s)") }, 'extensions-dir': { type: 'string', deprecates: 'extensionHomePath', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") }, 'builtin-extensions-dir': { type: 'string' }, @@ -62,6 +60,7 @@ export const OPTIONS: OptionDescriptions> = { 'uninstall-extension': { type: 'string[]', cat: 'e', args: 'extension-id', description: localize('uninstallExtension', "Uninstalls an extension.") }, 'enable-proposed-api': { type: 'string[]', cat: 'e', args: 'extension-id', description: localize('experimentalApis', "Enables proposed API features for extensions. Can receive one or more extension IDs to enable individually.") }, + 'version': { type: 'boolean', cat: 't', alias: 'v', description: localize('version', "Print version.") }, 'verbose': { type: 'boolean', cat: 't', description: localize('verbose', "Print verbose output (implies --wait).") }, 'log': { type: 'string', cat: 't', args: 'level', description: localize('log', "Log level to use. Default is 'info'. Allowed values are 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'.") }, 'status': { type: 'boolean', alias: 's', cat: 't', description: localize('status', "Print process usage and diagnostics information.") }, @@ -70,17 +69,18 @@ export const OPTIONS: OptionDescriptions> = { 'prof-startup-prefix': { type: 'string' }, 'disable-extensions': { type: 'boolean', deprecates: 'disableExtensions', cat: 't', description: localize('disableExtensions', "Disable all installed extensions.") }, 'disable-extension': { type: 'string[]', cat: 't', args: 'extension-id', description: localize('disableExtension', "Disable an extension.") }, + 'sync': { type: 'string', cat: 't', description: localize('turn sync', "Turn sync on or off"), args: ['on', 'off'] }, 'inspect-extensions': { type: 'string', deprecates: 'debugPluginHost', args: 'port', cat: 't', description: localize('inspect-extensions', "Allow debugging and profiling of extensions. Check the developer tools for the connection URI.") }, 'inspect-brk-extensions': { type: 'string', deprecates: 'debugBrkPluginHost', args: 'port', cat: 't', description: localize('inspect-brk-extensions', "Allow debugging and profiling of extensions with the extension host being paused after start. Check the developer tools for the connection URI.") }, 'disable-gpu': { type: 'boolean', cat: 't', description: localize('disableGPU', "Disable GPU hardware acceleration.") }, 'max-memory': { type: 'string', cat: 't', description: localize('maxMemory', "Max memory size for a window (in Mbytes).") }, + 'telemetry': { type: 'boolean', cat: 't', description: localize('telemetry', "Shows all telemetry events which VS code collects.") }, 'remote': { type: 'string' }, 'locate-extension': { type: 'string[]' }, 'extensionDevelopmentPath': { type: 'string[]' }, 'extensionTestsPath': { type: 'string' }, - 'extension-development-confirm-save': { type: 'boolean' }, 'debugId': { type: 'string' }, 'inspect-search': { type: 'string', deprecates: 'debugSearch' }, 'inspect-brk-search': { type: 'string', deprecates: 'debugBrkSearch' }, @@ -90,7 +90,7 @@ export const OPTIONS: OptionDescriptions> = { 'logExtensionHostCommunication': { type: 'boolean' }, 'skip-getting-started': { type: 'boolean' }, 'skip-release-notes': { type: 'boolean' }, - 'sticky-quickopen': { type: 'boolean' }, + 'sticky-quickinput': { type: 'boolean' }, 'disable-restore-windows': { type: 'boolean' }, 'disable-telemetry': { type: 'boolean' }, 'disable-updates': { type: 'boolean' }, @@ -119,7 +119,7 @@ export const OPTIONS: OptionDescriptions> = { 'nolazy': { type: 'boolean' }, // node inspect 'force-device-scale-factor': { type: 'string' }, 'force-renderer-accessibility': { type: 'boolean' }, - 'ignore-certificate-error': { type: 'boolean' }, + 'ignore-certificate-errors': { type: 'boolean' }, 'allow-insecure-localhost': { type: 'boolean' }, '_urls': { type: 'string[]' }, diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index 15b5c20cbbf..9f518b9729d 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -114,20 +114,11 @@ export class EnvironmentService implements IEnvironmentService { @memoize get userDataSyncHome(): URI { return resources.joinPath(this.userRoamingDataHome, 'sync'); } - @memoize - get settingsSyncPreviewResource(): URI { return resources.joinPath(this.userDataSyncHome, 'settings.json'); } - - @memoize - get keybindingsSyncPreviewResource(): URI { return resources.joinPath(this.userDataSyncHome, 'keybindings.json'); } - @memoize get userDataSyncLogResource(): URI { return URI.file(path.join(this.logsPath, 'userDataSync.log')); } @memoize - get machineSettingsHome(): URI { return URI.file(path.join(this.userDataPath, 'Machine')); } - - @memoize - get machineSettingsResource(): URI { return resources.joinPath(this.machineSettingsHome, 'settings.json'); } + get machineSettingsResource(): URI { return resources.joinPath(URI.file(path.join(this.userDataPath, 'Machine')), 'settings.json'); } @memoize get globalStorageHome(): string { return path.join(this.appSettingsHome.fsPath, 'globalStorage'); } @@ -151,6 +142,9 @@ export class EnvironmentService implements IEnvironmentService { return URI.file(path.join(this.userHome, product.dataFolderName, 'argv.json')); } + @memoize + get snippetsHome(): URI { return resources.joinPath(this.userRoamingDataHome, 'snippets'); } + @memoize get isExtensionDevelopment(): boolean { return !!this._args.extensionDevelopmentPath; } @@ -239,6 +233,18 @@ export class EnvironmentService implements IEnvironmentService { return false; } + get extensionEnabledProposedApi(): string[] | undefined { + if (Array.isArray(this.args['enable-proposed-api'])) { + return this.args['enable-proposed-api']; + } + + if ('enable-proposed-api' in this.args) { + return []; + } + + return undefined; + } + @memoize get debugExtensionHost(): IExtensionHostDebugParams { return parseExtensionHostPort(this._args, this.isBuilt); } @memoize @@ -248,10 +254,6 @@ export class EnvironmentService implements IEnvironmentService { get verbose(): boolean { return !!this._args.verbose; } get log(): string | undefined { return this._args.log; } - get wait(): boolean { return !!this._args.wait; } - - get status(): boolean { return !!this._args.status; } - @memoize get mainIPCHandle(): string { return getIPCHandle(this.userDataPath, 'main'); } @@ -262,7 +264,7 @@ export class EnvironmentService implements IEnvironmentService { get nodeCachedDataDir(): string | undefined { return process.env['VSCODE_NODE_CACHED_DATA_DIR'] || undefined; } @memoize - get galleryMachineIdResource(): URI { return resources.joinPath(URI.file(this.userDataPath), 'machineid'); } + get serviceMachineIdResource(): URI { return resources.joinPath(URI.file(this.userDataPath), 'machineid'); } get disableUpdates(): boolean { return !!this._args['disable-updates']; } get disableCrashReporter(): boolean { return !!this._args['disable-crash-reporter']; } diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index f3fb5ecca84..2366c8ba7d6 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -13,7 +13,7 @@ import { IRequestService, asJson, asText } from 'vs/platform/request/common/requ import { IRequestOptions, IRequestContext, IHeaders } from 'vs/base/parts/request/common/request'; import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { generateUuid, isUUID } from 'vs/base/common/uuid'; +import { generateUuid } from 'vs/base/common/uuid'; import { values } from 'vs/base/common/map'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ILogService } from 'vs/platform/log/common/log'; @@ -21,9 +21,9 @@ import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; -import { VSBuffer } from 'vs/base/common/buffer'; import { IProductService } from 'vs/platform/product/common/productService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId'; import { optional } from 'vs/platform/instantiation/common/instantiation'; interface IRawGalleryExtensionFile { @@ -341,12 +341,12 @@ export class ExtensionGalleryService implements IExtensionGalleryService { @ITelemetryService private readonly telemetryService: ITelemetryService, @IFileService private readonly fileService: IFileService, @IProductService private readonly productService: IProductService, - @optional(IStorageService) private readonly storageService: IStorageService, + @optional(IStorageService) storageService: IStorageService, ) { const config = productService.extensionsGallery; this.extensionsGalleryUrl = config && config.serviceUrl; this.extensionsControlUrl = config && config.controlUrl; - this.commonHeadersPromise = resolveMarketplaceHeaders(productService.version, this.environmentService, this.fileService, this.storageService); + this.commonHeadersPromise = resolveMarketplaceHeaders(productService.version, this.environmentService, this.fileService, storageService); } private api(path = ''): string { @@ -760,43 +760,17 @@ export class ExtensionGalleryService implements IExtensionGalleryService { } } -export async function resolveMarketplaceHeaders(version: string, environmentService: IEnvironmentService, fileService: IFileService, storageService?: IStorageService): Promise<{ [key: string]: string; }> { +export async function resolveMarketplaceHeaders(version: string, environmentService: IEnvironmentService, fileService: IFileService, storageService: { + get: (key: string, scope: StorageScope) => string | undefined, + store: (key: string, value: string, scope: StorageScope) => void +} | undefined): Promise<{ [key: string]: string; }> { const headers: IHeaders = { 'X-Market-Client-Id': `VSCode ${version}`, 'User-Agent': `VSCode ${version}` }; - let uuid: string | null = null; - if (environmentService.galleryMachineIdResource) { - try { - const contents = await fileService.readFile(environmentService.galleryMachineIdResource); - const value = contents.value.toString(); - uuid = isUUID(value) ? value : null; - } catch (e) { - uuid = null; - } - - if (!uuid) { - uuid = generateUuid(); - try { - await fileService.writeFile(environmentService.galleryMachineIdResource, VSBuffer.fromString(uuid)); - } catch (error) { - //noop - } - } - } - - if (storageService) { - uuid = storageService.get('marketplace.userid', StorageScope.GLOBAL) || null; - if (!uuid) { - uuid = generateUuid(); - storageService.store('marketplace.userid', uuid, StorageScope.GLOBAL); - } - } - + const uuid = await getServiceMachineId(environmentService, fileService, storageService); if (uuid) { headers['X-Market-User-Id'] = uuid; } - return headers; - } diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 5b056505913..ceab231b9f6 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -86,7 +86,7 @@ function readManifest(extensionPath: string): Promise<{ manifest: IExtensionMani .then(raw => JSON.parse(raw)) ]; - return Promise.all(promises).then(([{ manifest, metadata }, translations]) => { + return Promise.all(promises).then(([{ manifest, metadata }, translations]) => { return { manifest: localizeManifest(manifest, translations), metadata @@ -331,7 +331,7 @@ export class ExtensionManagementService extends Disposable implements IExtension this.downloadInstallableExtension(extension, operation) .then(installableExtension => this.installExtension(installableExtension, ExtensionType.User, cancellationToken) - .then(local => pfs.rimraf(installableExtension.zipPath).finally(() => null).then(() => local))) + .then(local => pfs.rimraf(installableExtension.zipPath).finally(() => { }).then(() => local))) .then(local => this.installDependenciesAndPackExtensions(local, existingExtension) .then(() => local, error => this.uninstall(local, true).then(() => Promise.reject(error), () => Promise.reject(error)))) .then( @@ -486,7 +486,7 @@ export class ExtensionManagementService extends Disposable implements IExtension () => this.logService.info('Renamed to', renamePath), e => { this.logService.info('Rename failed. Deleting from extracted location', extractPath); - return pfs.rimraf(extractPath).finally(() => null).then(() => Promise.reject(e)); + return pfs.rimraf(extractPath).finally(() => { }).then(() => Promise.reject(e)); })); } @@ -497,7 +497,7 @@ export class ExtensionManagementService extends Disposable implements IExtension () => extract(zipPath, extractPath, { sourcePath: 'extension', overwrite: true }, token) .then( () => this.logService.info(`Extracted extension to ${extractPath}:`, identifier.id), - e => pfs.rimraf(extractPath).finally(() => null) + e => pfs.rimraf(extractPath).finally(() => { }) .then(() => Promise.reject(new ExtensionManagementError(e.message, e instanceof ExtractError && e.type ? e.type : INSTALL_ERROR_EXTRACTING)))), e => Promise.reject(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_DELETING))); } diff --git a/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts b/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts index 07bb0a2e00f..377a92abbe9 100644 --- a/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts +++ b/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts @@ -19,6 +19,8 @@ import { NullLogService } from 'vs/platform/log/common/log'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { Schemas } from 'vs/base/common/network'; import product from 'vs/platform/product/common/product'; +import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; +import { IStorageService } from 'vs/platform/storage/common/storage'; suite('Extension Gallery Service', () => { const parentDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'extensiongalleryservice'); @@ -52,11 +54,12 @@ suite('Extension Gallery Service', () => { test('marketplace machine id', () => { const args = ['--user-data-dir', marketplaceHome]; const environmentService = new EnvironmentService(parseArgs(args, OPTIONS), process.execPath); + const storageService: IStorageService = new TestStorageService(); - return resolveMarketplaceHeaders(product.version, environmentService, fileService).then(headers => { + return resolveMarketplaceHeaders(product.version, environmentService, fileService, storageService).then(headers => { assert.ok(isUUID(headers['X-Market-User-Id'])); - return resolveMarketplaceHeaders(product.version, environmentService, fileService).then(headers2 => { + return resolveMarketplaceHeaders(product.version, environmentService, fileService, storageService).then(headers2 => { assert.equal(headers['X-Market-User-Id'], headers2['X-Market-User-Id']); }); }); diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index 87d90b14746..ac6ec196a03 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -38,7 +38,7 @@ export interface IGrammar { } export interface IJSONValidation { - fileMatch: string; + fileMatch: string | string[]; url: string; } diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 8d6e50b5d2e..7438ec84efe 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -395,6 +395,8 @@ export function toFileOperationResult(error: Error): FileOperationResult { return FileOperationResult.FILE_NOT_FOUND; case FileSystemProviderErrorCode.FileIsADirectory: return FileOperationResult.FILE_IS_DIRECTORY; + case FileSystemProviderErrorCode.FileNotADirectory: + return FileOperationResult.FILE_NOT_DIRECTORY; case FileSystemProviderErrorCode.NoPermissions: return FileOperationResult.FILE_PERMISSION_DENIED; case FileSystemProviderErrorCode.FileExists: @@ -763,6 +765,7 @@ export const enum FileOperationResult { FILE_TOO_LARGE, FILE_INVALID_PATH, FILE_EXCEEDS_MEMORY_LIMIT, + FILE_NOT_DIRECTORY, FILE_OTHER_ERROR } diff --git a/src/vs/platform/files/node/diskFileSystemProvider.ts b/src/vs/platform/files/node/diskFileSystemProvider.ts index 9382fd31225..1524e074b99 100644 --- a/src/vs/platform/files/node/diskFileSystemProvider.ts +++ b/src/vs/platform/files/node/diskFileSystemProvider.ts @@ -26,6 +26,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ReadableStreamEvents, transform } from 'vs/base/common/stream'; import { createReadStream } from 'vs/platform/files/common/io'; +import { insert } from 'vs/base/common/arrays'; export interface IWatcherOptions { pollingInterval?: number; @@ -524,7 +525,7 @@ export class DiskFileSystemProvider extends Disposable implements // Add to list of folders to watch recursively const folderToWatch = { path: this.toFilePath(resource), excludes }; - this.recursiveFoldersToWatch.push(folderToWatch); + const remove = insert(this.recursiveFoldersToWatch, folderToWatch); // Trigger update this.refreshRecursiveWatchers(); @@ -532,7 +533,7 @@ export class DiskFileSystemProvider extends Disposable implements return toDisposable(() => { // Remove from list of folders to watch recursively - this.recursiveFoldersToWatch.splice(this.recursiveFoldersToWatch.indexOf(folderToWatch), 1); + remove(); // Trigger update this.refreshRecursiveWatchers(); @@ -543,10 +544,8 @@ export class DiskFileSystemProvider extends Disposable implements // Buffer requests for recursive watching to decide on right watcher // that supports potentially watching more than one folder at once - this.recursiveWatchRequestDelayer.trigger(() => { + this.recursiveWatchRequestDelayer.trigger(async () => { this.doRefreshRecursiveWatchers(); - - return Promise.resolve(); }); } @@ -668,6 +667,9 @@ export class DiskFileSystemProvider extends Disposable implements case 'EISDIR': code = FileSystemProviderErrorCode.FileIsADirectory; break; + case 'ENOTDIR': + code = FileSystemProviderErrorCode.FileNotADirectory; + break; case 'EEXIST': code = FileSystemProviderErrorCode.FileExists; break; diff --git a/src/vs/platform/files/node/watcher/win32/watcherService.ts b/src/vs/platform/files/node/watcher/win32/watcherService.ts index 704dc9adc50..d062e34ae9f 100644 --- a/src/vs/platform/files/node/watcher/win32/watcherService.ts +++ b/src/vs/platform/files/node/watcher/win32/watcherService.ts @@ -6,7 +6,7 @@ import { IDiskFileChange, ILogMessage } from 'vs/platform/files/node/watcher/watcher'; import { OutOfProcessWin32FolderWatcher } from 'vs/platform/files/node/watcher/win32/csharpWatcherService'; import { posix } from 'vs/base/common/path'; -import { rtrim, endsWith } from 'vs/base/common/strings'; +import { rtrim } from 'vs/base/common/strings'; import { IDisposable } from 'vs/base/common/lifecycle'; export class FileWatcher implements IDisposable { @@ -22,7 +22,7 @@ export class FileWatcher implements IDisposable { ) { this.folder = folders[0]; - if (this.folder.path.indexOf('\\\\') === 0 && endsWith(this.folder.path, posix.sep)) { + if (this.folder.path.indexOf('\\\\') === 0 && this.folder.path.endsWith(posix.sep)) { // for some weird reason, node adds a trailing slash to UNC paths // we never ever want trailing slashes as our base path unless // someone opens root ("/"). diff --git a/src/vs/platform/files/test/electron-browser/diskFileService.test.ts b/src/vs/platform/files/test/electron-browser/diskFileService.test.ts index 1eff57b01fd..12bcc8428be 100644 --- a/src/vs/platform/files/test/electron-browser/diskFileService.test.ts +++ b/src/vs/platform/files/test/electron-browser/diskFileService.test.ts @@ -132,10 +132,12 @@ suite('Disk File Service', function () { const disposables = new DisposableStore(); // Given issues such as https://github.com/microsoft/vscode/issues/78602 - // we see random test failures when accessing the native file system. To - // diagnose further, we retry node.js file access tests up to 3 times to - // rule out any random disk issue. + // and https://github.com/microsoft/vscode/issues/92334 we see random test + // failures when accessing the native file system. To diagnose further, we + // retry node.js file access tests up to 3 times to rule out any random disk + // issue and increase the timeout. this.retries(3); + this.timeout(1000 * 10); setup(async () => { const logService = new NullLogService(); @@ -463,7 +465,7 @@ suite('Disk File Service', function () { return testDeleteFile(false); }); - test('deleteFile (useTrash)', async () => { + (isLinux /* trash is unreliable on Linux */ ? test.skip : test)('deleteFile (useTrash)', async () => { return testDeleteFile(true); }); @@ -543,7 +545,7 @@ suite('Disk File Service', function () { return testDeleteFolderRecursive(false); }); - test('deleteFolder (recursive, useTrash)', async () => { + (isLinux /* trash is unreliable on Linux */ ? test.skip : test)('deleteFolder (recursive, useTrash)', async () => { return testDeleteFolderRecursive(true); }); @@ -1407,6 +1409,24 @@ suite('Disk File Service', function () { assert.equal(error!.fileOperationResult, FileOperationResult.FILE_IS_DIRECTORY); }); + test('readFile - FILE_NOT_DIRECTORY', async () => { + if (isWindows) { + return; // error code does not seem to be supported on windows + } + + const resource = URI.file(join(testDir, 'lorem.txt', 'file.txt')); + + let error: FileOperationError | undefined = undefined; + try { + await service.readFile(resource); + } catch (err) { + error = err; + } + + assert.ok(error); + assert.equal(error!.fileOperationResult, FileOperationResult.FILE_NOT_DIRECTORY); + }); + test('readFile - FILE_NOT_FOUND', async () => { const resource = URI.file(join(testDir, '404.html')); diff --git a/src/vs/platform/instantiation/common/instantiation.ts b/src/vs/platform/instantiation/common/instantiation.ts index 7e130cc3963..6d72a93db53 100644 --- a/src/vs/platform/instantiation/common/instantiation.ts +++ b/src/vs/platform/instantiation/common/instantiation.ts @@ -102,7 +102,6 @@ export interface IInstantiationService { createInstance(descriptor: descriptors.SyncDescriptor8, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8): T; createInstance any, R extends InstanceType>(t: Ctor, ...args: GetLeadingNonServiceArgs>): R; - createInstance any, R extends InstanceType>(t: Ctor): R; /** * @@ -135,7 +134,7 @@ function storeServiceDependency(id: Function, target: Function, index: number, o } /** - * A *only* valid way to create a {{ServiceIdentifier}}. + * The *only* valid way to create a {{ServiceIdentifier}}. */ export function createDecorator(serviceId: string): ServiceIdentifier { diff --git a/src/vs/platform/instantiation/common/instantiationService.ts b/src/vs/platform/instantiation/common/instantiationService.ts index bb33f2e1195..cf9492900e9 100644 --- a/src/vs/platform/instantiation/common/instantiationService.ts +++ b/src/vs/platform/instantiation/common/instantiationService.ts @@ -13,12 +13,6 @@ import { IdleValue } from 'vs/base/common/async'; // TRACING const _enableTracing = false; -// PROXY -// Ghetto-declare of the global Proxy object. This isn't the proper way -// but allows us to run this code in the browser without IE11. -declare const Proxy: any; -const _canUseProxy = typeof Proxy === 'function'; - class CyclicDependencyError extends Error { constructor(graph: Graph) { super('cyclic dependency between services'); @@ -157,7 +151,7 @@ export class InstantiationService implements IInstantiationService { graph.lookupOrInsertNode(item); // a weak but working heuristic for cycle checks - if (cycleCount++ > 200) { + if (cycleCount++ > 1000) { throw new CyclicDependencyError(graph); } @@ -211,8 +205,8 @@ export class InstantiationService implements IInstantiationService { } private _createServiceInstance(ctor: any, args: any[] = [], _supportsDelayedInstantiation: boolean, _trace: Trace): T { - if (!_supportsDelayedInstantiation || !_canUseProxy) { - // eager instantiation or no support JS proxies (e.g. IE11) + if (!_supportsDelayedInstantiation) { + // eager instantiation return this._createInstance(ctor, args, _trace); } else { diff --git a/src/vs/platform/issue/node/issue.ts b/src/vs/platform/issue/node/issue.ts index 75fb71f9817..08a9c5d456f 100644 --- a/src/vs/platform/issue/node/issue.ts +++ b/src/vs/platform/issue/node/issue.ts @@ -49,6 +49,7 @@ export interface IssueReporterExtensionData { version: string; id: string; isTheme: boolean; + isBuiltin: boolean; displayName: string | undefined; repositoryUrl: string | undefined; bugsUrl: string | undefined; diff --git a/src/vs/platform/label/common/label.ts b/src/vs/platform/label/common/label.ts index b2422cfa473..080f1ff0994 100644 --- a/src/vs/platform/label/common/label.ts +++ b/src/vs/platform/label/common/label.ts @@ -11,7 +11,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; import { localize } from 'vs/nls'; import { isEqualOrParent, basename } from 'vs/base/common/resources'; -import { endsWith } from 'vs/base/common/strings'; export interface ILabelService { _serviceBrand: undefined; @@ -26,7 +25,11 @@ export interface ILabelService { getHostLabel(scheme: string, authority?: string): string; getSeparator(scheme: string, authority?: string): '/' | '\\'; registerFormatter(formatter: ResourceLabelFormatter): IDisposable; - onDidChangeFormatters: Event; + onDidChangeFormatters: Event; +} + +export interface IFormatterChangeEvent { + scheme: string; } export interface ResourceLabelFormatter { @@ -57,7 +60,7 @@ export function getSimpleWorkspaceLabel(workspace: IWorkspaceIdentifier | URI, w } let filename = basename(workspace.configPath); - if (endsWith(filename, WORKSPACE_EXTENSION)) { + if (filename.endsWith(WORKSPACE_EXTENSION)) { filename = filename.substr(0, filename.length - WORKSPACE_EXTENSION.length - 1); } return localize('workspaceName', "{0} (Workspace)", filename); diff --git a/src/vs/platform/layout/browser/layoutService.ts b/src/vs/platform/layout/browser/layoutService.ts index 5b0e5b35b95..39f3f1da8eb 100644 --- a/src/vs/platform/layout/browser/layoutService.ts +++ b/src/vs/platform/layout/browser/layoutService.ts @@ -5,14 +5,10 @@ import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IDimension } from 'vs/base/browser/dom'; export const ILayoutService = createDecorator('layoutService'); -export interface IDimension { - readonly width: number; - readonly height: number; -} - export interface ILayoutService { _serviceBrand: undefined; @@ -27,9 +23,19 @@ export interface ILayoutService { */ readonly container: HTMLElement; + /** + * An offset to use for positioning elements inside the container. + */ + readonly offset?: { top: number }; + /** * An event that is emitted when the container is layed out. The * event carries the dimensions of the container as part of it. */ readonly onLayout: Event; -} \ No newline at end of file + + /** + * Focus the primary component of the container. + */ + focus(): void; +} diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 73f5282c138..301812d07d1 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -9,30 +9,25 @@ import { IPagedRenderer, PagedList } from 'vs/base/browser/ui/list/listPaging'; import { DefaultStyleController, IListOptions, IMultipleSelectionController, IOpenController, isSelectionRangeChangeEvent, isSelectionSingleChangeEvent, List } from 'vs/base/browser/ui/list/listWidget'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, dispose, IDisposable, toDisposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; -import { ScrollbarVisibility } from 'vs/base/common/scrollable'; -import { isUndefinedOrNull } from 'vs/base/common/types'; -import { ITree, ITreeConfiguration, ITreeOptions } from 'vs/base/parts/tree/browser/tree'; -import { ClickBehavior, DefaultController, DefaultTreestyler, IControllerOptions, OpenMode } from 'vs/base/parts/tree/browser/treeDefaults'; -import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; import { localize } from 'vs/nls'; import { IConfigurationService, getMigratedSettingValue } from 'vs/platform/configuration/common/configuration'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; -import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Registry } from 'vs/platform/registry/common/platform'; import { attachListStyler, computeStyles, defaultListStyles, IColorMapping } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { ObjectTree, IObjectTreeOptions, ICompressibleTreeRenderer, CompressibleObjectTree, ICompressibleObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree'; -import { ITreeEvent, ITreeRenderer, IAsyncDataSource, IDataSource, ITreeMouseEvent } from 'vs/base/browser/ui/tree/tree'; +import { ITreeRenderer, IAsyncDataSource, IDataSource } from 'vs/base/browser/ui/tree/tree'; import { AsyncDataTree, IAsyncDataTreeOptions, CompressibleAsyncDataTree, ITreeCompressionDelegate, ICompressibleAsyncDataTreeOptions } from 'vs/base/browser/ui/tree/asyncDataTree'; import { DataTree, IDataTreeOptions } from 'vs/base/browser/ui/tree/dataTree'; import { IKeyboardNavigationEventFilter, IAbstractTreeOptions, RenderIndentGuides } from 'vs/base/browser/ui/tree/abstractTree'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -export type ListWidget = List | PagedList | ITree | ObjectTree | DataTree | AsyncDataTree; +export type ListWidget = List | PagedList | ObjectTree | DataTree | AsyncDataTree; export const IListService = createDecorator('listService'); @@ -270,7 +265,7 @@ export class WorkbenchList extends List { super(user, container, delegate, renderers, { keyboardSupport: false, - ...computeStyles(themeService.getTheme(), defaultListStyles), + ...computeStyles(themeService.getColorTheme(), defaultListStyles), ...workbenchListOptions, horizontalScrolling } @@ -298,7 +293,7 @@ export class WorkbenchList extends List { this.updateStyles(options.overrideStyles); } - this.disposables.add(this.onSelectionChange(() => { + this.disposables.add(this.onDidChangeSelection(() => { const selection = this.getSelection(); const focus = this.getFocus(); @@ -306,7 +301,7 @@ export class WorkbenchList extends List { this.listMultiSelection.set(selection.length > 1); this.listDoubleSelection.set(selection.length === 2); })); - this.disposables.add(this.onFocusChange(() => { + this.disposables.add(this.onDidChangeFocus(() => { const selection = this.getSelection(); const focus = this.getFocus(); @@ -378,7 +373,7 @@ export class WorkbenchPagedList extends PagedList { super(user, container, delegate, renderers, { keyboardSupport: false, - ...computeStyles(themeService.getTheme(), defaultListStyles), + ...computeStyles(themeService.getColorTheme(), defaultListStyles), ...workbenchListOptions, horizontalScrolling } @@ -424,182 +419,6 @@ export class WorkbenchPagedList extends PagedList { } } -/** - * @deprecated - */ -let sharedTreeStyleSheet: HTMLStyleElement; -function getSharedTreeStyleSheet(): HTMLStyleElement { - if (!sharedTreeStyleSheet) { - sharedTreeStyleSheet = createStyleSheet(); - } - - return sharedTreeStyleSheet; -} - -/** - * @deprecated - */ -function handleTreeController(configuration: ITreeConfiguration, instantiationService: IInstantiationService): ITreeConfiguration { - if (!configuration.controller) { - configuration.controller = instantiationService.createInstance(WorkbenchTreeController, {}); - } - - if (!configuration.styler) { - configuration.styler = new DefaultTreestyler(getSharedTreeStyleSheet()); - } - - return configuration; -} - -/** - * @deprecated - */ -export class WorkbenchTree extends Tree { - - readonly contextKeyService: IContextKeyService; - - protected disposables: IDisposable[]; - - private listHasSelectionOrFocus: IContextKey; - private listDoubleSelection: IContextKey; - private listMultiSelection: IContextKey; - - private _openOnSingleClick: boolean; - private _useAltAsMultipleSelectionModifier: boolean; - - constructor( - container: HTMLElement, - configuration: ITreeConfiguration, - options: ITreeOptions | undefined, - @IContextKeyService contextKeyService: IContextKeyService, - @IListService listService: IListService, - @IThemeService themeService: IThemeService, - @IInstantiationService instantiationService: IInstantiationService, - @IConfigurationService configurationService: IConfigurationService - ) { - const config = handleTreeController(configuration, instantiationService); - const horizontalScrollMode = configurationService.getValue(horizontalScrollingKey) ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden; - const opts = { - horizontalScrollMode, - keyboardSupport: false, - ...computeStyles(themeService.getTheme(), defaultListStyles), - ...options - }; - - super(container, config, opts); - - this.disposables = []; - this.contextKeyService = createScopedContextKeyService(contextKeyService, this); - - WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService); - - this.listHasSelectionOrFocus = WorkbenchListHasSelectionOrFocus.bindTo(this.contextKeyService); - this.listDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService); - this.listMultiSelection = WorkbenchListMultiSelection.bindTo(this.contextKeyService); - - this._openOnSingleClick = useSingleClickToOpen(configurationService); - this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService); - - this.disposables.push( - this.contextKeyService, - (listService as ListService).register(this), - attachListStyler(this, themeService) - ); - - this.disposables.push(this.onDidChangeSelection(() => { - const selection = this.getSelection(); - const focus = this.getFocus(); - - this.listHasSelectionOrFocus.set((selection && selection.length > 0) || !!focus); - this.listDoubleSelection.set(selection && selection.length === 2); - this.listMultiSelection.set(selection && selection.length > 1); - })); - - this.disposables.push(this.onDidChangeFocus(() => { - const selection = this.getSelection(); - const focus = this.getFocus(); - - this.listHasSelectionOrFocus.set((selection && selection.length > 0) || !!focus); - })); - - this.disposables.push(configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(openModeSettingKey)) { - this._openOnSingleClick = useSingleClickToOpen(configurationService); - } - - if (e.affectsConfiguration(multiSelectModifierSettingKey)) { - this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService); - } - })); - } - - get openOnSingleClick(): boolean { - return this._openOnSingleClick; - } - - get useAltAsMultipleSelectionModifier(): boolean { - return this._useAltAsMultipleSelectionModifier; - } - - dispose(): void { - super.dispose(); - - this.disposables = dispose(this.disposables); - } -} - -/** - * @deprecated - */ -function massageControllerOptions(options: IControllerOptions): IControllerOptions { - if (typeof options.keyboardSupport !== 'boolean') { - options.keyboardSupport = false; - } - - if (typeof options.clickBehavior !== 'number') { - options.clickBehavior = ClickBehavior.ON_MOUSE_DOWN; - } - - return options; -} - -/** - * @deprecated - */ -export class WorkbenchTreeController extends DefaultController { - - protected readonly disposables = new DisposableStore(); - - constructor( - options: IControllerOptions, - @IConfigurationService private readonly configurationService: IConfigurationService - ) { - super(massageControllerOptions(options)); - - // if the open mode is not set, we configure it based on settings - if (isUndefinedOrNull(options.openMode)) { - this.setOpenMode(this.getOpenModeSetting()); - this.registerListeners(); - } - } - - private registerListeners(): void { - this.disposables.add(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(openModeSettingKey)) { - this.setOpenMode(this.getOpenModeSetting()); - } - })); - } - - private getOpenModeSetting(): OpenMode { - return useSingleClickToOpen(this.configurationService) ? OpenMode.SINGLE_CLICK : OpenMode.DOUBLE_CLICK; - } - - dispose(): void { - this.disposables.dispose(); - } -} - export interface IOpenResourceOptions { editorOptions: IEditorOptions; sideBySide: boolean; @@ -618,9 +437,10 @@ export interface IOpenEvent { browserEvent?: UIEvent; } -export interface ITreeResourceNavigatorOptions { +export interface IResourceNavigatorOptions { readonly openOnFocus?: boolean; readonly openOnSelection?: boolean; + readonly openOnSingleClick?: boolean; } export interface SelectionKeyboardEvent extends KeyboardEvent { @@ -634,16 +454,45 @@ export function getSelectionKeyboardEvent(typeArg = 'keydown', preserveFocus?: b return e; } -export class TreeResourceNavigator extends Disposable { +export abstract class ResourceNavigator extends Disposable { - private options: ITreeResourceNavigatorOptions; + static createListResourceNavigator(list: WorkbenchList | WorkbenchPagedList, options?: IResourceNavigatorOptions): ResourceNavigator { + return new class extends ResourceNavigator { + constructor() { + super(list, options); + } + }(); + } + + static createTreeResourceNavigator(tree: WorkbenchObjectTree | WorkbenchCompressibleObjectTree | WorkbenchDataTree | WorkbenchAsyncDataTree | WorkbenchCompressibleAsyncDataTree, + options?: IResourceNavigatorOptions): ResourceNavigator { + return new class extends ResourceNavigator { + constructor() { + super(tree, { + ...{ + openOnSingleClick: tree.openOnSingleClick + }, + ...(options || {}) + }); + } + }(); + } + + private readonly options: IResourceNavigatorOptions; private readonly _onDidOpenResource = new Emitter>(); readonly onDidOpenResource: Event> = this._onDidOpenResource.event; constructor( - private tree: WorkbenchObjectTree | WorkbenchCompressibleObjectTree | WorkbenchDataTree | WorkbenchAsyncDataTree | WorkbenchCompressibleAsyncDataTree, - options?: ITreeResourceNavigatorOptions + private readonly treeOrList: { + getFocus(): (T | null)[], + getSelection(): (T | null)[], + setSelection(elements: (T | null)[], browserEvent?: UIEvent): void, + onDidChangeFocus: Event<{ browserEvent?: UIEvent }>, + onDidChangeSelection: Event<{ browserEvent?: UIEvent }>, + onDidOpen: Event<{ browserEvent?: UIEvent }>, + }, + options?: IResourceNavigatorOptions ) { super(); @@ -659,50 +508,50 @@ export class TreeResourceNavigator extends Disposable { private registerListeners(): void { if (this.options && this.options.openOnFocus) { - this._register(this.tree.onDidChangeFocus(e => this.onFocus(e))); + this._register(this.treeOrList.onDidChangeFocus(e => this.onFocus(e.browserEvent))); } if (this.options && this.options.openOnSelection) { - this._register(this.tree.onDidChangeSelection(e => this.onSelection(e))); + this._register(this.treeOrList.onDidChangeSelection(e => this.onSelection(e.browserEvent))); } - this._register(this.tree.onDidOpen(e => this.onSelection(e))); + this._register(this.treeOrList.onDidOpen(e => this.onSelection(e.browserEvent))); } - private onFocus(e: ITreeEvent): void { - const focus = this.tree.getFocus(); - this.tree.setSelection(focus as T[], e.browserEvent); + private onFocus(browserEvent?: UIEvent): void { + const focus = this.treeOrList.getFocus(); + this.treeOrList.setSelection(focus, browserEvent); - if (!e.browserEvent) { + if (!browserEvent) { return; } - const isMouseEvent = e.browserEvent && e.browserEvent instanceof MouseEvent; + const isMouseEvent = browserEvent && browserEvent instanceof MouseEvent; if (!isMouseEvent) { - const preserveFocus = (e.browserEvent instanceof KeyboardEvent && typeof (e.browserEvent).preserveFocus === 'boolean') ? - !!(e.browserEvent).preserveFocus : + const preserveFocus = (browserEvent instanceof KeyboardEvent && typeof (browserEvent).preserveFocus === 'boolean') ? + !!(browserEvent).preserveFocus : true; - this.open(preserveFocus, false, false, e.browserEvent); + this.open(preserveFocus, false, false, browserEvent); } } - private onSelection(e: ITreeEvent | ITreeMouseEvent, doubleClick = false): void { - if (!e.browserEvent || e.browserEvent.type === 'contextmenu') { + private onSelection(browserEvent?: MouseEvent | UIEvent): void { + if (!browserEvent || browserEvent.type === 'contextmenu') { return; } - const isKeyboardEvent = e.browserEvent instanceof KeyboardEvent; - const isMiddleClick = e.browserEvent instanceof MouseEvent ? e.browserEvent.button === 1 : false; - const isDoubleClick = e.browserEvent.detail === 2; - const preserveFocus = (e.browserEvent instanceof KeyboardEvent && typeof (e.browserEvent).preserveFocus === 'boolean') ? - !!(e.browserEvent).preserveFocus : + const isKeyboardEvent = browserEvent instanceof KeyboardEvent; + const isMiddleClick = browserEvent instanceof MouseEvent ? browserEvent.button === 1 : false; + const isDoubleClick = browserEvent.detail === 2; + const preserveFocus = (browserEvent instanceof KeyboardEvent && typeof (browserEvent).preserveFocus === 'boolean') ? + !!(browserEvent).preserveFocus : !isDoubleClick; - if (this.tree.openOnSingleClick || isDoubleClick || isKeyboardEvent) { - const sideBySide = e.browserEvent instanceof MouseEvent && (e.browserEvent.ctrlKey || e.browserEvent.metaKey || e.browserEvent.altKey); - this.open(preserveFocus, isDoubleClick || isMiddleClick, sideBySide, e.browserEvent); + if (this.options.openOnSingleClick || isDoubleClick || isKeyboardEvent) { + const sideBySide = browserEvent instanceof MouseEvent && (browserEvent.ctrlKey || browserEvent.metaKey || browserEvent.altKey); + this.open(preserveFocus, isDoubleClick || isMiddleClick, sideBySide, browserEvent); } } @@ -714,7 +563,7 @@ export class TreeResourceNavigator extends Disposable { revealIfVisible: true }, sideBySide, - element: this.tree.getSelection()[0], + element: this.treeOrList.getSelection()[0], browserEvent }); } @@ -1123,7 +972,7 @@ configurationRegistry.registerConfiguration({ [horizontalScrollingKey]: { 'type': 'boolean', 'default': false, - 'description': localize('horizontalScrolling setting', "Controls whether lists and trees support horizontal scrolling in the workbench.") + 'description': localize('horizontalScrolling setting', "Controls whether lists and trees support horizontal scrolling in the workbench. Warning: turning on this setting has a performance implication.") }, 'workbench.tree.horizontalScrolling': { 'type': 'boolean', diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index 18c243a56ad..ee048dbe20a 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -532,6 +532,7 @@ export class Menubar { [ minimize, zoom, + __separator__(), switchWindow, ...nativeTabMenuItems, __separator__(), diff --git a/src/vs/platform/notification/common/notification.ts b/src/vs/platform/notification/common/notification.ts index f7f2b5a3d9e..4a7adc958ac 100644 --- a/src/vs/platform/notification/common/notification.ts +++ b/src/vs/platform/notification/common/notification.ts @@ -21,20 +21,20 @@ export interface INotificationProperties { * Sticky notifications are not automatically removed after a certain timeout. By * default, notifications with primary actions and severity error are always sticky. */ - sticky?: boolean; + readonly sticky?: boolean; /** * Silent notifications are not shown to the user unless the notification center * is opened. The status bar will still indicate all number of notifications to * catch some attention. */ - silent?: boolean; + readonly silent?: boolean; /** * Adds an action to never show the notification again. The choice will be persisted * such as future requests will not cause the notification to show again. */ - neverShowAgain?: INeverShowAgainOptions; + readonly neverShowAgain?: INeverShowAgainOptions; } export enum NeverShowAgainScope { @@ -55,19 +55,19 @@ export interface INeverShowAgainOptions { /** * The id is used to persist the selection of not showing the notification again. */ - id: string; + readonly id: string; /** * By default the action will show up as primary action. Setting this to true will * make it a secondary action instead. */ - isSecondary?: boolean; + readonly isSecondary?: boolean; /** * Whether to persist the choice in the current workspace or for all workspaces. By - * default it will be persisted for all workspaces. + * default it will be persisted for all workspaces (= `NeverShowAgainScope.GLOBAL`). */ - scope?: NeverShowAgainScope; + readonly scope?: NeverShowAgainScope; } export interface INotification extends INotificationProperties { @@ -75,18 +75,18 @@ export interface INotification extends INotificationProperties { /** * The severity of the notification. Either `Info`, `Warning` or `Error`. */ - severity: Severity; + readonly severity: Severity; /** * The message of the notification. This can either be a `string` or `Error`. Messages * can optionally include links in the format: `[text](link)` */ - message: NotificationMessage; + readonly message: NotificationMessage; /** * The source of the notification appears as additional information. */ - source?: string; + readonly source?: string; /** * Actions to show as part of the notification. Primary actions show up as @@ -106,7 +106,7 @@ export interface INotification extends INotificationProperties { * The initial set of progress properties for the notification. To update progress * later on, access the `INotificationHandle.progress` property. */ - progress?: INotificationProgressProperties; + readonly progress?: INotificationProgressProperties; } export interface INotificationActions { @@ -115,14 +115,14 @@ export interface INotificationActions { * Primary actions show up as buttons as part of the message and will close * the notification once clicked. */ - primary?: ReadonlyArray; + readonly primary?: ReadonlyArray; /** * Secondary actions are meant to provide additional configuration or context * for the notification and will show up less prominent. A notification does not * close automatically when invoking a secondary action. */ - secondary?: ReadonlyArray; + readonly secondary?: ReadonlyArray; } export interface INotificationProgressProperties { @@ -130,17 +130,17 @@ export interface INotificationProgressProperties { /** * Causes the progress bar to spin infinitley. */ - infinite?: boolean; + readonly infinite?: boolean; /** * Indicate the total amount of work. */ - total?: number; + readonly total?: number; /** * Indicate that a specific chunk of work is done. */ - worked?: number; + readonly worked?: number; } export interface INotificationProgress { @@ -176,7 +176,7 @@ export interface INotificationHandle { /** * Will be fired whenever the visibility of the notification changes. * A notification can either be visible as toast or inside the notification - * center if it is visible. + * center if it is visible. */ readonly onDidChangeVisibility: Event; @@ -214,19 +214,19 @@ export interface IPromptChoice { /** * Label to show for the choice to the user. */ - label: string; + readonly label: string; /** * Primary choices show up as buttons in the notification below the message. * Secondary choices show up under the gear icon in the header of the notification. */ - isSecondary?: boolean; + readonly isSecondary?: boolean; /** * Whether to keep the notification open after the choice was selected * by the user. By default, will close the notification upon click. */ - keepOpen?: boolean; + readonly keepOpen?: boolean; /** * Triggered when the user selects the choice. @@ -249,13 +249,13 @@ export interface IStatusMessageOptions { * An optional timeout after which the status message should show. By default * the status message will show immediately. */ - showAfter?: number; + readonly showAfter?: number; /** * An optional timeout after which the status message is to be hidden. By default * the status message will not hide until another status message is displayed. */ - hideAfter?: number; + readonly hideAfter?: number; } export enum NotificationsFilter { diff --git a/src/vs/platform/opener/browser/link.ts b/src/vs/platform/opener/browser/link.ts index daf424fa848..7b6863cd048 100644 --- a/src/vs/platform/opener/browser/link.ts +++ b/src/vs/platform/opener/browser/link.ts @@ -5,7 +5,7 @@ import { Event } from 'vs/base/common/event'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { $ } from 'vs/base/browser/dom'; +import { $, EventHelper, EventLike } from 'vs/base/browser/dom'; import { domEvent } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -46,9 +46,12 @@ export class Link extends Disposable { .map(e => new StandardKeyboardEvent(e)) .filter(e => e.keyCode === KeyCode.Enter) .event; - const onOpen = Event.any(onClick, onEnterPress); + const onOpen = Event.any(onClick, onEnterPress); - this._register(onOpen(_ => openerService.open(link.href))); + this._register(onOpen(e => { + EventHelper.stop(e, true); + openerService.open(link.href); + })); this.applyStyles(); } diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index da88376513b..29927fec2a9 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -21,7 +21,7 @@ if (isWeb) { // Running out of sources if (Object.keys(product).length === 0) { assign(product, { - version: '1.43.0-dev', + version: '1.44.0-dev', nameLong: 'Visual Studio Code Web Dev', nameShort: 'VSCode Web Dev', urlProtocol: 'code-oss' diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts index 798f9f42518..7624fe5f8d2 100644 --- a/src/vs/platform/product/common/productService.ts +++ b/src/vs/platform/product/common/productService.ts @@ -89,7 +89,7 @@ export interface IProductConfiguration { readonly checksums?: { [path: string]: string; }; readonly checksumFailMoreInfoUrl?: string; - readonly hockeyApp?: { + readonly appCenter?: { readonly 'win32-ia32': string; readonly 'win32-x64': string; readonly 'linux-x64': string; diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index b6ff5e5057b..2a5cfda1b32 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -62,6 +62,7 @@ export interface IProgressNotificationOptions extends IProgressOptions { readonly primaryActions?: ReadonlyArray; readonly secondaryActions?: ReadonlyArray; readonly delay?: number; + readonly silent?: boolean; } export interface IProgressWindowOptions extends IProgressOptions { diff --git a/src/vs/platform/quickOpen/common/quickOpen.ts b/src/vs/platform/quickOpen/common/quickOpen.ts deleted file mode 100644 index 056f1b6e00d..00000000000 --- a/src/vs/platform/quickOpen/common/quickOpen.ts +++ /dev/null @@ -1,60 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Event } from 'vs/base/common/event'; -import { IQuickNavigateConfiguration, IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; - -export interface IShowOptions { - quickNavigateConfiguration?: IQuickNavigateConfiguration; - inputSelection?: { start: number; end: number; }; - autoFocus?: IAutoFocus; -} - -export const IQuickOpenService = createDecorator('quickOpenService'); - -export interface IQuickOpenService { - - _serviceBrand: undefined; - - /** - * Asks the container to show the quick open control with the optional prefix set. If the optional parameter - * is set for quick navigation mode, the quick open control will quickly navigate when the quick navigate - * key is pressed and will run the selection after the ctrl key is released. - * - * The returned promise completes when quick open is closing. - */ - show(prefix?: string, options?: IShowOptions): Promise; - - /** - * Allows to navigate from the outside in an opened picker. - */ - navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration): void; - - /** - * Accepts the selected value in quick open if visible. - */ - accept(): void; - - /** - * Focus into the quick open if visible. - */ - focus(): void; - - /** - * Closes any opened quick open. - */ - close(): void; - - /** - * Allows to register on the event that quick open is showing - */ - onShow: Event; - - /** - * Allows to register on the event that quick open is hiding - */ - onHide: Event; -} diff --git a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts new file mode 100644 index 00000000000..18b7dc26e6e --- /dev/null +++ b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts @@ -0,0 +1,305 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { PickerQuickAccessProvider, IPickerQuickAccessItem, IPickerQuickAccessProviderOptions } from 'vs/platform/quickinput/browser/pickerQuickAccess'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { or, matchesPrefix, matchesWords, matchesContiguousSubString } from 'vs/base/common/filters'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { LRUCache } from 'vs/base/common/map'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { isPromiseCanceledError } from 'vs/base/common/errors'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; + +export interface ICommandQuickPick extends IPickerQuickAccessItem { + commandId: string; + commandAlias?: string; +} + +export interface ICommandsQuickAccessOptions extends IPickerQuickAccessProviderOptions { + showAlias: boolean; +} + +export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAccessProvider implements IDisposable { + + static PREFIX = '>'; + + private static WORD_FILTER = or(matchesPrefix, matchesWords, matchesContiguousSubString); + + private readonly commandsHistory = this._register(this.instantiationService.createInstance(CommandsHistory)); + + constructor( + protected options: ICommandsQuickAccessOptions, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IKeybindingService private readonly keybindingService: IKeybindingService, + @ICommandService private readonly commandService: ICommandService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + @INotificationService private readonly notificationService: INotificationService + ) { + super(AbstractCommandsQuickAccessProvider.PREFIX, options); + } + + protected async getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Promise> { + + // Ask subclass for all command picks + const allCommandPicks = await this.getCommandPicks(disposables, token); + + if (token.isCancellationRequested) { + return []; + } + + // Filter + const filteredCommandPicks: ICommandQuickPick[] = []; + for (const commandPick of allCommandPicks) { + const labelHighlights = withNullAsUndefined(AbstractCommandsQuickAccessProvider.WORD_FILTER(filter, commandPick.label)); + const aliasHighlights = commandPick.commandAlias ? withNullAsUndefined(AbstractCommandsQuickAccessProvider.WORD_FILTER(filter, commandPick.commandAlias)) : undefined; + + // Add if matching in label or alias + if (labelHighlights || aliasHighlights) { + commandPick.highlights = { + label: labelHighlights, + detail: this.options.showAlias ? aliasHighlights : undefined + }; + + filteredCommandPicks.push(commandPick); + } + + // Also add if we have a 100% command ID match + else if (filter === commandPick.commandId) { + filteredCommandPicks.push(commandPick); + } + } + + // Add description to commands that have duplicate labels + const mapLabelToCommand = new Map(); + for (const commandPick of filteredCommandPicks) { + const existingCommandForLabel = mapLabelToCommand.get(commandPick.label); + if (existingCommandForLabel) { + commandPick.description = commandPick.commandId; + existingCommandForLabel.description = existingCommandForLabel.commandId; + } else { + mapLabelToCommand.set(commandPick.label, commandPick); + } + } + + // Sort by MRU order and fallback to name otherwise + filteredCommandPicks.sort((commandPickA, commandPickB) => { + const commandACounter = this.commandsHistory.peek(commandPickA.commandId); + const commandBCounter = this.commandsHistory.peek(commandPickB.commandId); + + if (commandACounter && commandBCounter) { + return commandACounter > commandBCounter ? -1 : 1; // use more recently used command before older + } + + if (commandACounter) { + return -1; // first command was used, so it wins over the non used one + } + + if (commandBCounter) { + return 1; // other command was used so it wins over the command + } + + // both commands were never used, so we sort by name + return commandPickA.label.localeCompare(commandPickB.label); + }); + + const commandPicks: Array = []; + + let addSeparator = false; + for (let i = 0; i < filteredCommandPicks.length; i++) { + const commandPick = filteredCommandPicks[i]; + const keybinding = this.keybindingService.lookupKeybinding(commandPick.commandId); + const ariaLabel = keybinding ? + localize('commandPickAriaLabelWithKeybinding', "{0}, {1}", commandPick.label, keybinding.getAriaLabel()) : + commandPick.label; + + // Separator: recently used + if (i === 0 && this.commandsHistory.peek(commandPick.commandId)) { + commandPicks.push({ type: 'separator', label: localize('recentlyUsed', "recently used") }); + addSeparator = true; + } + + // Separator: other commands + if (i !== 0 && addSeparator && !this.commandsHistory.peek(commandPick.commandId)) { + commandPicks.push({ type: 'separator', label: localize('morecCommands', "other commands") }); + addSeparator = false; // only once + } + + // Command + commandPicks.push({ + ...commandPick, + ariaLabel, + detail: this.options.showAlias && commandPick.commandAlias !== commandPick.label ? commandPick.commandAlias : undefined, + keybinding, + accept: async () => { + + // Add to history + this.commandsHistory.push(commandPick.commandId); + + // Telementry + this.telemetryService.publicLog2('workbenchActionExecuted', { + id: commandPick.commandId, + from: 'quick open' + }); + + // Run + try { + await this.commandService.executeCommand(commandPick.commandId); + } catch (error) { + if (!isPromiseCanceledError(error)) { + this.notificationService.error(localize('canNotRun', "Command '{0}' resulted in an error ({1})", commandPick.label, toErrorMessage(error))); + } + } + } + }); + } + + return commandPicks; + } + + /** + * Subclasses to provide the actual command entries. + */ + protected abstract getCommandPicks(disposables: DisposableStore, token: CancellationToken): Promise>; +} + +interface ISerializedCommandHistory { + usesLRU?: boolean; + entries: { key: string; value: number }[]; +} + +interface ICommandsQuickAccessConfiguration { + workbench: { + commandPalette: { + history: number; + preserveInput: boolean; + } + }; +} + +export class CommandsHistory extends Disposable { + + static readonly DEFAULT_COMMANDS_HISTORY_LENGTH = 50; + + private static readonly PREF_KEY_CACHE = 'commandPalette.mru.cache'; + private static readonly PREF_KEY_COUNTER = 'commandPalette.mru.counter'; + + private static cache: LRUCache | undefined; + private static counter = 1; + + private configuredCommandsHistoryLength = 0; + + constructor( + @IStorageService private readonly storageService: IStorageService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService + ) { + super(); + + // opt-in to syncing + storageKeysSyncRegistryService.registerStorageKey({ key: CommandsHistory.PREF_KEY_CACHE, version: 1 }); + storageKeysSyncRegistryService.registerStorageKey({ key: CommandsHistory.PREF_KEY_COUNTER, version: 1 }); + + this.updateConfiguration(); + this.load(); + + this.registerListeners(); + } + + private registerListeners(): void { + this._register(this.configurationService.onDidChangeConfiguration(() => this.updateConfiguration())); + } + + private updateConfiguration(): void { + this.configuredCommandsHistoryLength = CommandsHistory.getConfiguredCommandHistoryLength(this.configurationService); + + if (CommandsHistory.cache && CommandsHistory.cache.limit !== this.configuredCommandsHistoryLength) { + CommandsHistory.cache.limit = this.configuredCommandsHistoryLength; + + CommandsHistory.saveState(this.storageService); + } + } + + private load(): void { + const raw = this.storageService.get(CommandsHistory.PREF_KEY_CACHE, StorageScope.GLOBAL); + let serializedCache: ISerializedCommandHistory | undefined; + if (raw) { + try { + serializedCache = JSON.parse(raw); + } catch (error) { + // invalid data + } + } + + const cache = CommandsHistory.cache = new LRUCache(this.configuredCommandsHistoryLength, 1); + if (serializedCache) { + let entries: { key: string; value: number }[]; + if (serializedCache.usesLRU) { + entries = serializedCache.entries; + } else { + entries = serializedCache.entries.sort((a, b) => a.value - b.value); + } + entries.forEach(entry => cache.set(entry.key, entry.value)); + } + + CommandsHistory.counter = this.storageService.getNumber(CommandsHistory.PREF_KEY_COUNTER, StorageScope.GLOBAL, CommandsHistory.counter); + } + + push(commandId: string): void { + if (!CommandsHistory.cache) { + return; + } + + CommandsHistory.cache.set(commandId, CommandsHistory.counter++); // set counter to command + + CommandsHistory.saveState(this.storageService); + } + + peek(commandId: string): number | undefined { + return CommandsHistory.cache?.peek(commandId); + } + + static saveState(storageService: IStorageService): void { + if (!CommandsHistory.cache) { + return; + } + + const serializedCache: ISerializedCommandHistory = { usesLRU: true, entries: [] }; + CommandsHistory.cache.forEach((value, key) => serializedCache.entries.push({ key, value })); + + storageService.store(CommandsHistory.PREF_KEY_CACHE, JSON.stringify(serializedCache), StorageScope.GLOBAL); + storageService.store(CommandsHistory.PREF_KEY_COUNTER, CommandsHistory.counter, StorageScope.GLOBAL); + } + + static getConfiguredCommandHistoryLength(configurationService: IConfigurationService): number { + const config = configurationService.getValue(); + + const configuredCommandHistoryLength = config.workbench?.commandPalette?.history; + if (typeof configuredCommandHistoryLength === 'number') { + return configuredCommandHistoryLength; + } + + return CommandsHistory.DEFAULT_COMMANDS_HISTORY_LENGTH; + } + + static clearHistory(configurationService: IConfigurationService, storageService: IStorageService): void { + const commandHistoryLength = CommandsHistory.getConfiguredCommandHistoryLength(configurationService); + CommandsHistory.cache = new LRUCache(commandHistoryLength); + CommandsHistory.counter = 1; + + CommandsHistory.saveState(storageService); + } +} + diff --git a/src/vs/platform/quickinput/browser/helpQuickAccess.ts b/src/vs/platform/quickinput/browser/helpQuickAccess.ts new file mode 100644 index 00000000000..5bcd826daf0 --- /dev/null +++ b/src/vs/platform/quickinput/browser/helpQuickAccess.ts @@ -0,0 +1,89 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IQuickPick, IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickAccessProvider, IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { localize } from 'vs/nls'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; + +interface IHelpQuickAccessPickItem extends IQuickPickItem { + prefix: string; +} + +export class HelpQuickAccessProvider implements IQuickAccessProvider { + + static PREFIX = '?'; + + private readonly registry = Registry.as(Extensions.Quickaccess); + + constructor(@IQuickInputService private readonly quickInputService: IQuickInputService) { } + + provide(picker: IQuickPick): IDisposable { + const disposables = new DisposableStore(); + + // Open a picker with the selected value if picked + disposables.add(picker.onDidAccept(() => { + const [item] = picker.selectedItems; + if (item) { + this.quickInputService.quickAccess.show(item.prefix, { preserveValue: true }); + } + })); + + // Also open a picker when we detect the user typed the exact + // name of a provider (e.g. `?term` for terminals) + disposables.add(picker.onDidChangeValue(value => { + const providerDescriptor = this.registry.getQuickAccessProvider(value.substr(HelpQuickAccessProvider.PREFIX.length)); + if (providerDescriptor && providerDescriptor.prefix && providerDescriptor.prefix !== HelpQuickAccessProvider.PREFIX) { + this.quickInputService.quickAccess.show(providerDescriptor.prefix, { preserveValue: true }); + } + })); + + // Fill in all providers separated by editor/global scope + const { editorProviders, globalProviders } = this.getQuickAccessProviders(); + picker.items = editorProviders.length === 0 || globalProviders.length === 0 ? + + // Without groups + [ + ...(editorProviders.length === 0 ? globalProviders : editorProviders) + ] : + + // With groups + [ + { label: localize('globalCommands', "global commands"), type: 'separator' }, + ...globalProviders, + { label: localize('editorCommands', "editor commands"), type: 'separator' }, + ...editorProviders + ]; + + return disposables; + } + + private getQuickAccessProviders(): { editorProviders: IHelpQuickAccessPickItem[], globalProviders: IHelpQuickAccessPickItem[] } { + const globalProviders: IHelpQuickAccessPickItem[] = []; + const editorProviders: IHelpQuickAccessPickItem[] = []; + + for (const provider of this.registry.getQuickAccessProviders().sort((providerA, providerB) => providerA.prefix.localeCompare(providerB.prefix))) { + if (provider.prefix === HelpQuickAccessProvider.PREFIX) { + continue; // exclude help which is already active + } + + for (const helpEntry of provider.helpEntries) { + const prefix = helpEntry.prefix || provider.prefix; + const label = prefix || '\u2026' /* ... */; + + (helpEntry.needsEditor ? editorProviders : globalProviders).push({ + prefix, + label, + ariaLabel: localize('helpPickAriaLabel', "{0}, {1}", label, helpEntry.description), + description: helpEntry.description + }); + } + } + + return { editorProviders, globalProviders }; + } +} + diff --git a/src/vs/platform/quickinput/browser/pickerQuickAccess.ts b/src/vs/platform/quickinput/browser/pickerQuickAccess.ts new file mode 100644 index 00000000000..204ed7c4305 --- /dev/null +++ b/src/vs/platform/quickinput/browser/pickerQuickAccess.ts @@ -0,0 +1,321 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { IQuickPickSeparator, IKeyMods, IQuickPickAcceptEvent } from 'vs/base/parts/quickinput/common/quickInput'; +import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { IDisposable, DisposableStore, Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { timeout } from 'vs/base/common/async'; + +export enum TriggerAction { + + /** + * Do nothing after the button was clicked. + */ + NO_ACTION, + + /** + * Close the picker. + */ + CLOSE_PICKER, + + /** + * Update the results of the picker. + */ + REFRESH_PICKER, + + /** + * Remove the item from the picker. + */ + REMOVE_ITEM +} + +export interface IPickerQuickAccessItem extends IQuickPickItem { + + /** + * A method that will be executed when the pick item is accepted from + * the picker. The picker will close automatically before running this. + * + * @param keyMods the state of modifier keys when the item was accepted. + * @param event the underlying event that caused the accept to trigger. + */ + accept?(keyMods: IKeyMods, event: IQuickPickAcceptEvent): void; + + /** + * A method that will be executed when a button of the pick item was + * clicked on. + * + * @param buttonIndex index of the button of the item that + * was clicked. + * + * @param the state of modifier keys when the button was triggered. + * + * @returns a value that indicates what should happen after the trigger + * which can be a `Promise` for long running operations. + */ + trigger?(buttonIndex: number, keyMods: IKeyMods): TriggerAction | Promise; +} + +export interface IPickerQuickAccessProviderOptions { + + /** + * Enables support for opening picks in the background via gesture. + */ + canAcceptInBackground?: boolean; + + /** + * Enables to show a pick entry when no results are returned from a search. + */ + noResultsPick?: T; +} + +export type Pick = T | IQuickPickSeparator; +export type PicksWithActive = { items: ReadonlyArray>, active?: T }; +export type Picks = ReadonlyArray> | PicksWithActive; +export type FastAndSlowPicks = { picks: Picks, additionalPicks: Promise> }; +export type FastAndSlowPicksWithActive = { picks: PicksWithActive, additionalPicks: PicksWithActive> }; + +function isPicksWithActive(obj: unknown): obj is PicksWithActive { + const candidate = obj as PicksWithActive; + + return Array.isArray(candidate.items); +} + +function isFastAndSlowPicks(obj: unknown): obj is FastAndSlowPicks { + const candidate = obj as FastAndSlowPicks; + + return !!candidate.picks && candidate.additionalPicks instanceof Promise; +} + +export abstract class PickerQuickAccessProvider extends Disposable implements IQuickAccessProvider { + + private static FAST_PICKS_RACE_DELAY = 200; // timeout before we accept fast results before slow results are present + + constructor(private prefix: string, protected options?: IPickerQuickAccessProviderOptions) { + super(); + } + + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // Apply options if any + picker.canAcceptInBackground = !!this.options?.canAcceptInBackground; + + // Disable filtering & sorting, we control the results + picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false; + + // Set initial picks and update on type + let picksCts: CancellationTokenSource | undefined = undefined; + const picksDisposable = disposables.add(new MutableDisposable()); + const updatePickerItems = async () => { + const picksDisposables = picksDisposable.value = new DisposableStore(); + + // Cancel any previous ask for picks and busy + picksCts?.dispose(true); + picker.busy = false; + + // Create new cancellation source for this run + picksCts = new CancellationTokenSource(token); + + // Collect picks and support both long running and short or combined + const picksToken = picksCts.token; + const picksFilter = picker.value.substr(this.prefix.length).trim(); + const providedPicks = this.getPicks(picksFilter, picksDisposables, picksToken); + + const applyPicks = (picks: Picks, skipEmpty?: boolean): boolean => { + let items: ReadonlyArray>; + let activeItem: T | undefined = undefined; + + if (isPicksWithActive(picks)) { + items = picks.items; + activeItem = picks.active; + } else { + items = picks; + } + + if (items.length === 0) { + if (skipEmpty) { + return false; + } + + if (picksFilter.length > 0 && this.options?.noResultsPick) { + items = [this.options.noResultsPick]; + } + } + + picker.items = items; + if (activeItem) { + picker.activeItems = [activeItem]; + } + + return true; + }; + + // No Picks + if (providedPicks === null) { + // Ignore + } + + // Fast and Slow Picks + else if (isFastAndSlowPicks(providedPicks)) { + let fastPicksApplied = false; + let slowPicksApplied = false; + + await Promise.all([ + + // Fast Picks: to reduce amount of flicker, we race against + // the slow picks over 500ms and then set the fast picks. + // If the slow picks are faster, we reduce the flicker by + // only setting the items once. + (async () => { + await timeout(PickerQuickAccessProvider.FAST_PICKS_RACE_DELAY); + if (picksToken.isCancellationRequested) { + return; + } + + if (!slowPicksApplied) { + fastPicksApplied = applyPicks(providedPicks.picks, true /* skip over empty to reduce flicker */); + } + })(), + + // Slow Picks: we await the slow picks and then set them at + // once together with the fast picks, but only if we actually + // have additional results. + (async () => { + picker.busy = true; + try { + const awaitedAdditionalPicks = await providedPicks.additionalPicks; + if (picksToken.isCancellationRequested) { + return; + } + + let picks: ReadonlyArray>; + let activePick: Pick | undefined = undefined; + if (isPicksWithActive(providedPicks.picks)) { + picks = providedPicks.picks.items; + activePick = providedPicks.picks.active; + } else { + picks = providedPicks.picks; + } + + let additionalPicks: ReadonlyArray>; + let additionalActivePick: Pick | undefined = undefined; + if (isPicksWithActive(awaitedAdditionalPicks)) { + additionalPicks = awaitedAdditionalPicks.items; + additionalActivePick = awaitedAdditionalPicks.active; + } else { + additionalPicks = awaitedAdditionalPicks; + } + + if (additionalPicks.length > 0 || !fastPicksApplied) { + applyPicks({ + items: [...picks, ...additionalPicks], + active: activePick || additionalActivePick + }); + } + } finally { + if (!picksToken.isCancellationRequested) { + picker.busy = false; + } + + slowPicksApplied = true; + } + })() + ]); + } + + // Fast Picks + else if (!(providedPicks instanceof Promise)) { + applyPicks(providedPicks); + } + + // Slow Picks + else { + picker.busy = true; + try { + const awaitedPicks = await providedPicks; + if (picksToken.isCancellationRequested) { + return; + } + + applyPicks(awaitedPicks); + } finally { + if (!picksToken.isCancellationRequested) { + picker.busy = false; + } + } + } + }; + disposables.add(picker.onDidChangeValue(() => updatePickerItems())); + updatePickerItems(); + + // Accept the pick on accept and hide picker + disposables.add(picker.onDidAccept(event => { + const [item] = picker.selectedItems; + if (typeof item?.accept === 'function') { + if (!event.inBackground) { + picker.hide(); // hide picker unless we accept in background + } + + item.accept(picker.keyMods, event); + } + })); + + // Trigger the pick with button index if button triggered + disposables.add(picker.onDidTriggerItemButton(async ({ button, item }) => { + if (typeof item.trigger === 'function') { + const buttonIndex = item.buttons?.indexOf(button) ?? -1; + if (buttonIndex >= 0) { + const result = item.trigger(buttonIndex, picker.keyMods); + const action = (typeof result === 'number') ? result : await result; + + if (token.isCancellationRequested) { + return; + } + + switch (action) { + case TriggerAction.NO_ACTION: + break; + case TriggerAction.CLOSE_PICKER: + picker.hide(); + break; + case TriggerAction.REFRESH_PICKER: + updatePickerItems(); + break; + case TriggerAction.REMOVE_ITEM: + const index = picker.items.indexOf(item); + if (index !== -1) { + const items = picker.items.slice(); + items.splice(index, 1); + picker.items = items; + } + break; + } + } + } + })); + + return disposables; + } + + /** + * Returns an array of picks and separators as needed. If the picks are resolved + * long running, the provided cancellation token should be used to cancel the + * operation when the token signals this. + * + * The implementor is responsible for filtering and sorting the picks given the + * provided `filter`. + * + * @param filter a filter to apply to the picks. + * @param disposables can be used to register disposables that should be cleaned + * up when the picker closes. + * @param token for long running tasks, implementors need to check on cancellation + * through this token. + * @returns the picks either directly, as promise or combined fast and slow results. + * Pickers can return `null` to signal that no change in picks is needed. + */ + protected abstract getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Picks | Promise> | FastAndSlowPicks | null; +} diff --git a/src/vs/platform/quickinput/browser/quickAccess.ts b/src/vs/platform/quickinput/browser/quickAccess.ts new file mode 100644 index 00000000000..cc364f7ec9b --- /dev/null +++ b/src/vs/platform/quickinput/browser/quickAccess.ts @@ -0,0 +1,189 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IQuickInputService, IQuickPick, IQuickPickItem, ItemActivation } from 'vs/platform/quickinput/common/quickInput'; +import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { IQuickAccessController, IQuickAccessProvider, IQuickAccessRegistry, Extensions, IQuickAccessProviderDescriptor, IQuickAccessOptions, DefaultQuickAccessFilterValue } from 'vs/platform/quickinput/common/quickAccess'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { once } from 'vs/base/common/functional'; + +export class QuickAccessController extends Disposable implements IQuickAccessController { + + private readonly registry = Registry.as(Extensions.Quickaccess); + private readonly mapProviderToDescriptor = new Map(); + + private readonly lastAcceptedPickerValues = new Map(); + + private visibleQuickAccess: { + picker: IQuickPick, + descriptor: IQuickAccessProviderDescriptor | undefined, + value: string + } | undefined = undefined; + + constructor( + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { + super(); + } + + show(value = '', options?: IQuickAccessOptions): void { + + // Find provider for the value to show + const [provider, descriptor] = this.getOrInstantiateProvider(value); + + // Return early if quick access is already showing on that same prefix + const visibleQuickAccess = this.visibleQuickAccess; + const visibleDescriptor = visibleQuickAccess?.descriptor; + if (visibleQuickAccess && descriptor && visibleDescriptor === descriptor) { + + // Apply value only if it is more specific than the prefix + // from the provider and we are not instructed to preserve + if (value !== descriptor.prefix && !options?.preserveValue) { + visibleQuickAccess.picker.value = value; + } + + // Always adjust selection + this.adjustValueSelection(visibleQuickAccess.picker, descriptor, options); + + return; + } + + // Rewrite the filter value based on certain rules unless disabled + if (descriptor && !options?.preserveValue) { + let newValue: string | undefined = undefined; + + // If we have a visible provider with a value, take it's filter value but + // rewrite to new provider prefix in case they differ + if (visibleQuickAccess && visibleDescriptor && visibleDescriptor !== descriptor) { + const newValueCandidateWithoutPrefix = visibleQuickAccess.value.substr(visibleDescriptor.prefix.length); + if (newValueCandidateWithoutPrefix) { + newValue = `${descriptor.prefix}${newValueCandidateWithoutPrefix}`; + } + } + + // Otherwise, take a default value as instructed + if (!newValue) { + const defaultFilterValue = provider?.defaultFilterValue; + if (defaultFilterValue === DefaultQuickAccessFilterValue.LAST) { + newValue = this.lastAcceptedPickerValues.get(descriptor); + } else if (typeof defaultFilterValue === 'string') { + newValue = `${descriptor.prefix}${defaultFilterValue}`; + } + } + + if (typeof newValue === 'string') { + value = newValue; + } + } + + // Create a picker for the provider to use with the initial value + // and adjust the filtering to exclude the prefix from filtering + const disposables = new DisposableStore(); + const picker = disposables.add(this.quickInputService.createQuickPick()); + picker.value = value; + this.adjustValueSelection(picker, descriptor, options); + picker.placeholder = descriptor?.placeholder; + picker.quickNavigate = options?.quickNavigateConfiguration; + picker.hideInput = !!picker.quickNavigate && !visibleQuickAccess; // only hide input if there was no picker opened already + if (typeof options?.itemActivation === 'number' || options?.quickNavigateConfiguration) { + picker.itemActivation = options?.itemActivation ?? ItemActivation.SECOND /* quick nav is always second */; + } + picker.contextKey = descriptor?.contextKey; + picker.filterValue = (value: string) => value.substring(descriptor ? descriptor.prefix.length : 0); + if (descriptor?.placeholder) { + picker.ariaLabel = descriptor?.placeholder; + } + + // Register listeners + const cancellationToken = this.registerPickerListeners(picker, provider, descriptor, value, disposables); + + // Ask provider to fill the picker as needed if we have one + if (provider) { + disposables.add(provider.provide(picker, cancellationToken)); + } + + // Finally, show the picker. This is important because a provider + // may not call this and then our disposables would leak that rely + // on the onDidHide event. + picker.show(); + } + + private adjustValueSelection(picker: IQuickPick, descriptor?: IQuickAccessProviderDescriptor, options?: IQuickAccessOptions): void { + let valueSelection: [number, number]; + + // Preserve: just always put the cursor at the end + if (options?.preserveValue) { + valueSelection = [picker.value.length, picker.value.length]; + } + + // Otherwise: select the value up until the prefix + else { + valueSelection = [descriptor?.prefix.length ?? 0, picker.value.length]; + } + + picker.valueSelection = valueSelection; + } + + private registerPickerListeners(picker: IQuickPick, provider: IQuickAccessProvider | undefined, descriptor: IQuickAccessProviderDescriptor | undefined, value: string, disposables: DisposableStore): CancellationToken { + + // Remember as last visible picker and clean up once picker get's disposed + const visibleQuickAccess = this.visibleQuickAccess = { picker, descriptor, value }; + disposables.add(toDisposable(() => { + if (visibleQuickAccess === this.visibleQuickAccess) { + this.visibleQuickAccess = undefined; + } + })); + + // Whenever the value changes, check if the provider has + // changed and if so - re-create the picker from the beginning + disposables.add(picker.onDidChangeValue(value => { + const [providerForValue] = this.getOrInstantiateProvider(value); + if (providerForValue !== provider) { + this.show(value, { preserveValue: true } /* do not rewrite value from user typing! */); + } else { + visibleQuickAccess.value = value; // remember the value in our visible one + } + })); + + // Remember picker input for future use when accepting + if (descriptor) { + disposables.add(picker.onDidAccept(() => { + this.lastAcceptedPickerValues.set(descriptor, picker.value); + })); + } + + // Create a cancellation token source that is valid as long as the + // picker has not been closed without picking an item + const cts = disposables.add(new CancellationTokenSource()); + once(picker.onDidHide)(() => { + if (picker.selectedItems.length === 0) { + cts.cancel(); + } + + // Start to dispose once picker hides + disposables.dispose(); + }); + + return cts.token; + } + + private getOrInstantiateProvider(value: string): [IQuickAccessProvider | undefined, IQuickAccessProviderDescriptor | undefined] { + const providerDescriptor = this.registry.getQuickAccessProvider(value); + if (!providerDescriptor) { + return [undefined, undefined]; + } + + let provider = this.mapProviderToDescriptor.get(providerDescriptor); + if (!provider) { + provider = this.instantiationService.createInstance(providerDescriptor.ctor); + this.mapProviderToDescriptor.set(providerDescriptor, provider); + } + + return [provider, providerDescriptor]; + } +} diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts new file mode 100644 index 00000000000..19572226a62 --- /dev/null +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -0,0 +1,224 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IQuickInputService, IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInputButton, IInputBox, QuickPickInput, IKeyMods } from 'vs/platform/quickinput/common/quickInput'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; +import { inputBackground, inputForeground, inputBorder, inputValidationInfoBackground, inputValidationInfoForeground, inputValidationInfoBorder, inputValidationWarningBackground, inputValidationWarningForeground, inputValidationWarningBorder, inputValidationErrorBackground, inputValidationErrorForeground, inputValidationErrorBorder, badgeBackground, badgeForeground, contrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, progressBarBackground, widgetShadow, listFocusForeground, listFocusBackground, activeContrastBorder, pickerGroupBorder, pickerGroupForeground, quickInputForeground, quickInputBackground, quickInputTitleBackground } from 'vs/platform/theme/common/colorRegistry'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { computeStyles } from 'vs/platform/theme/common/styler'; +import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { QuickInputController, IQuickInputStyles, IQuickInputOptions } from 'vs/base/parts/quickinput/browser/quickInput'; +import { WorkbenchList } from 'vs/platform/list/browser/listService'; +import { List, IListOptions } from 'vs/base/browser/ui/list/listWidget'; +import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; +import { IQuickAccessController } from 'vs/platform/quickinput/common/quickAccess'; +import { QuickAccessController } from 'vs/platform/quickinput/browser/quickAccess'; + +export interface IQuickInputControllerHost extends ILayoutService { } + +export class QuickInputService extends Themable implements IQuickInputService { + + _serviceBrand: undefined; + + get backButton(): IQuickInputButton { return this.controller.backButton; } + + get onShow() { return this.controller.onShow; } + get onHide() { return this.controller.onHide; } + + private _controller: QuickInputController | undefined; + private get controller(): QuickInputController { + if (!this._controller) { + this._controller = this._register(this.createController()); + } + + return this._controller; + } + + private _quickAccess: IQuickAccessController | undefined; + get quickAccess(): IQuickAccessController { + if (!this._quickAccess) { + this._quickAccess = this._register(this.instantiationService.createInstance(QuickAccessController)); + } + + return this._quickAccess; + } + + private readonly contexts = new Map>(); + + constructor( + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IContextKeyService protected readonly contextKeyService: IContextKeyService, + @IThemeService themeService: IThemeService, + @IAccessibilityService private readonly accessibilityService: IAccessibilityService, + @ILayoutService protected readonly layoutService: ILayoutService + ) { + super(themeService); + } + + protected createController(host: IQuickInputControllerHost = this.layoutService, options?: Partial): QuickInputController { + const defaultOptions: IQuickInputOptions = { + idPrefix: 'quickInput_', // Constant since there is still only one. + container: host.container, + ignoreFocusOut: () => false, + isScreenReaderOptimized: () => this.accessibilityService.isScreenReaderOptimized(), + backKeybindingLabel: () => undefined, + setContextKey: (id?: string) => this.setContextKey(id), + returnFocus: () => host.focus(), + createList: ( + user: string, + container: HTMLElement, + delegate: IListVirtualDelegate, + renderers: IListRenderer[], + options: IListOptions, + ) => this.instantiationService.createInstance(WorkbenchList, user, container, delegate, renderers, options) as List, + styles: this.computeStyles() + }; + + const controller = this._register(new QuickInputController({ + ...defaultOptions, + ...options + })); + + controller.layout(host.dimension, host.offset?.top ?? 0); + + // Layout changes + this._register(host.onLayout(dimension => controller.layout(dimension, host.offset?.top ?? 0))); + + // Context keys + this._register(controller.onShow(() => this.resetContextKeys())); + this._register(controller.onHide(() => this.resetContextKeys())); + + return controller; + } + + private setContextKey(id?: string) { + let key: IContextKey | undefined; + if (id) { + key = this.contexts.get(id); + if (!key) { + key = new RawContextKey(id, false) + .bindTo(this.contextKeyService); + this.contexts.set(id, key); + } + } + + if (key && key.get()) { + return; // already active context + } + + this.resetContextKeys(); + + if (key) { + key.set(true); + } + } + + private resetContextKeys() { + this.contexts.forEach(context => { + if (context.get()) { + context.reset(); + } + }); + } + + pick>(picks: Promise[]> | QuickPickInput[], options: O = {}, token: CancellationToken = CancellationToken.None): Promise { + return this.controller.pick(picks, options, token); + } + + input(options: IInputOptions = {}, token: CancellationToken = CancellationToken.None): Promise { + return this.controller.input(options, token); + } + + createQuickPick(): IQuickPick { + return this.controller.createQuickPick(); + } + + createInputBox(): IInputBox { + return this.controller.createInputBox(); + } + + focus() { + this.controller.focus(); + } + + toggle() { + this.controller.toggle(); + } + + navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration) { + this.controller.navigate(next, quickNavigate); + } + + accept(keyMods?: IKeyMods) { + return this.controller.accept(keyMods); + } + + back() { + return this.controller.back(); + } + + cancel() { + return this.controller.cancel(); + } + + protected updateStyles() { + this.controller.applyStyles(this.computeStyles()); + } + + private computeStyles(): IQuickInputStyles { + return { + widget: { + ...computeStyles(this.theme, { + quickInputBackground, + quickInputForeground, + quickInputTitleBackground, + contrastBorder, + widgetShadow + }), + }, + inputBox: computeStyles(this.theme, { + inputForeground, + inputBackground, + inputBorder, + inputValidationInfoBackground, + inputValidationInfoForeground, + inputValidationInfoBorder, + inputValidationWarningBackground, + inputValidationWarningForeground, + inputValidationWarningBorder, + inputValidationErrorBackground, + inputValidationErrorForeground, + inputValidationErrorBorder + }), + countBadge: computeStyles(this.theme, { + badgeBackground, + badgeForeground, + badgeBorder: contrastBorder + }), + button: computeStyles(this.theme, { + buttonForeground, + buttonBackground, + buttonHoverBackground, + buttonBorder: contrastBorder + }), + progressBar: computeStyles(this.theme, { + progressBarBackground + }), + list: computeStyles(this.theme, { + listBackground: quickInputBackground, + // Look like focused when inactive. + listInactiveFocusForeground: listFocusForeground, + listInactiveFocusBackground: listFocusBackground, + listFocusOutline: activeContrastBorder, + listInactiveFocusOutline: activeContrastBorder, + pickerGroupBorder, + pickerGroupForeground + }) + }; + } +} diff --git a/src/vs/platform/quickinput/common/quickAccess.ts b/src/vs/platform/quickinput/common/quickAccess.ts new file mode 100644 index 00000000000..b817188c26c --- /dev/null +++ b/src/vs/platform/quickinput/common/quickAccess.ts @@ -0,0 +1,204 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IQuickPick, IQuickPickItem, IQuickNavigateConfiguration } from 'vs/platform/quickinput/common/quickInput'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { coalesce } from 'vs/base/common/arrays'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { ItemActivation } from 'vs/base/parts/quickinput/common/quickInput'; + +export interface IQuickAccessOptions { + + /** + * Allows to enable quick navigate support in quick input. + */ + quickNavigateConfiguration?: IQuickNavigateConfiguration; + + /** + * Allows to configure a different item activation strategy. + * By default the first item in the list will get activated. + */ + itemActivation?: ItemActivation; + + /** + * Wether to take the input value as is and not restore it + * from any existing value if quick access is visible. + */ + preserveValue?: boolean; +} + +export interface IQuickAccessController { + + /** + * Open the quick access picker with the optional value prefilled. + */ + show(value?: string, options?: IQuickAccessOptions): void; +} + +export enum DefaultQuickAccessFilterValue { + + /** + * Keep the value as it is given to quick access. + */ + PRESERVE = 0, + + /** + * Use the value that was used last time something was accepted from the picker. + */ + LAST = 1 +} + +export interface IQuickAccessProvider { + + /** + * Allows to set a default filter value when the provider opens. This can be: + * - `undefined` to not specify any default value + * - `DefaultFilterValues.PRESERVE` to use the value that was last typed + * - `string` for the actual value to use + * + * Note: the default filter will only be used if quick access was opened with + * the exact prefix of the provider. Otherwise the filter value is preserved. + */ + readonly defaultFilterValue?: string | DefaultQuickAccessFilterValue; + + /** + * Called whenever a prefix was typed into quick pick that matches the provider. + * + * @param picker the picker to use for showing provider results. The picker is + * automatically shown after the method returns, no need to call `show()`. + * @param token providers have to check the cancellation token everytime after + * a long running operation or from event handlers because it could be that the + * picker has been closed or changed meanwhile. The token can be used to find out + * that the picker was closed without picking an entry (e.g. was canceled by the user). + * @return a disposable that will automatically be disposed when the picker + * closes or is replaced by another picker. + */ + provide(picker: IQuickPick, token: CancellationToken): IDisposable; +} + +export interface IQuickAccessProviderHelp { + + /** + * The prefix to show for the help entry. If not provided, + * the prefix used for registration will be taken. + */ + prefix?: string; + + /** + * A description text to help understand the intent of the provider. + */ + description: string; + + /** + * Separation between provider for editors and global ones. + */ + needsEditor: boolean; +} + +export interface IQuickAccessProviderDescriptor { + + /** + * The actual provider that will be instantiated as needed. + */ + readonly ctor: { new(...services: any /* TS BrandedService but no clue how to type this properly */[]): IQuickAccessProvider }; + + /** + * The prefix for quick access picker to use the provider for. + */ + readonly prefix: string; + + /** + * A placeholder to use for the input field when the provider is active. + * This will also be read out by screen readers and thus helps for + * accessibility. + */ + readonly placeholder?: string; + + /** + * Documentation for the provider in the quick access help. + */ + readonly helpEntries: IQuickAccessProviderHelp[]; + + /** + * A context key that will be set automatically when the + * picker for the provider is showing. + */ + readonly contextKey?: string; +} + +export const Extensions = { + Quickaccess: 'workbench.contributions.quickaccess' +}; + +export interface IQuickAccessRegistry { + + /** + * Registers a quick access provider to the platform. + */ + registerQuickAccessProvider(provider: IQuickAccessProviderDescriptor): IDisposable; + + /** + * Get all registered quick access providers. + */ + getQuickAccessProviders(): IQuickAccessProviderDescriptor[]; + + /** + * Get a specific quick access provider for a given prefix. + */ + getQuickAccessProvider(prefix: string): IQuickAccessProviderDescriptor | undefined; +} + +export class QuickAccessRegistry implements IQuickAccessRegistry { + private providers: IQuickAccessProviderDescriptor[] = []; + private defaultProvider: IQuickAccessProviderDescriptor | undefined = undefined; + + registerQuickAccessProvider(provider: IQuickAccessProviderDescriptor): IDisposable { + + // Extract the default provider when no prefix is present + if (provider.prefix.length === 0) { + this.defaultProvider = provider; + } else { + this.providers.push(provider); + } + + // sort the providers by decreasing prefix length, such that longer + // prefixes take priority: 'ext' vs 'ext install' - the latter should win + this.providers.sort((providerA, providerB) => providerB.prefix.length - providerA.prefix.length); + + return toDisposable(() => { + this.providers.splice(this.providers.indexOf(provider), 1); + + if (this.defaultProvider === provider) { + this.defaultProvider = undefined; + } + }); + } + + getQuickAccessProviders(): IQuickAccessProviderDescriptor[] { + return coalesce([this.defaultProvider, ...this.providers]); + } + + getQuickAccessProvider(prefix: string): IQuickAccessProviderDescriptor | undefined { + const result = prefix ? (this.providers.find(provider => prefix.startsWith(provider.prefix)) || undefined) : undefined; + + return result || this.defaultProvider; + } + + clear(): Function { + const providers = [...this.providers]; + const defaultProvider = this.defaultProvider; + + this.providers = []; + this.defaultProvider = undefined; + + return () => { + this.providers = providers; + this.defaultProvider = defaultProvider; + }; + } +} + +Registry.add(Extensions.Quickaccess, new QuickAccessRegistry()); diff --git a/src/vs/platform/quickinput/common/quickInput.ts b/src/vs/platform/quickinput/common/quickInput.ts index 5827452f6e4..ec1ef7e96a9 100644 --- a/src/vs/platform/quickinput/common/quickInput.ts +++ b/src/vs/platform/quickinput/common/quickInput.ts @@ -3,11 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInputButton, IInputBox, QuickPickInput } from 'vs/base/parts/quickinput/common/quickInput'; +import { IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInputButton, IInputBox, QuickPickInput, IKeyMods } from 'vs/base/parts/quickinput/common/quickInput'; +import { IQuickAccessController } from 'vs/platform/quickinput/common/quickAccess'; -export { IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInput, IQuickInputButton, IInputBox, IQuickPickItemButtonEvent, QuickPickInput, IQuickPickSeparator, IKeyMods } from 'vs/base/parts/quickinput/common/quickInput'; +export * from 'vs/base/parts/quickinput/common/quickInput'; export const IQuickInputService = createDecorator('quickInputService'); @@ -18,7 +20,28 @@ export interface IQuickInputService { _serviceBrand: undefined; /** - * Opens the quick input box for selecting items and returns a promise with the user selected item(s) if any. + * Provides access to the back button in quick input. + */ + readonly backButton: IQuickInputButton; + + /** + * Provides access to the quick access providers. + */ + readonly quickAccess: IQuickAccessController; + + /** + * Allows to register on the event that quick input is showing. + */ + readonly onShow: Event; + + /** + * Allows to register on the event that quick input is hiding. + */ + readonly onHide: Event; + + /** + * Opens the quick input box for selecting items and returns a promise + * with the user selected item(s) if any. */ pick(picks: Promise[]> | QuickPickInput[], options?: IPickOptions & { canPickMany: true }, token?: CancellationToken): Promise; pick(picks: Promise[]> | QuickPickInput[], options?: IPickOptions & { canPickMany: false }, token?: CancellationToken): Promise; @@ -29,20 +52,46 @@ export interface IQuickInputService { */ input(options?: IInputOptions, token?: CancellationToken): Promise; - backButton: IQuickInputButton; - + /** + * Provides raw access to the quick pick controller. + */ createQuickPick(): IQuickPick; + + /** + * Provides raw access to the quick input controller. + */ createInputBox(): IInputBox; + /** + * Moves focus into quick input. + */ focus(): void; + /** + * Toggle the checked state of the selected item. + */ toggle(): void; + /** + * Navigate inside the opened quick input list. + */ navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration): void; - accept(): Promise; - + /** + * Navigate back in a multi-step quick input. + */ back(): Promise; + /** + * Accept the selected item. + * + * @param keyMods allows to override the state of key + * modifiers that should be present when invoking. + */ + accept(keyMods?: IKeyMods): Promise; + + /** + * Cancels quick input and closes it. + */ cancel(): Promise; } diff --git a/src/vs/platform/remote/common/remoteAuthorityResolver.ts b/src/vs/platform/remote/common/remoteAuthorityResolver.ts index 0f05864a61d..10d818bd949 100644 --- a/src/vs/platform/remote/common/remoteAuthorityResolver.ts +++ b/src/vs/platform/remote/common/remoteAuthorityResolver.ts @@ -19,7 +19,7 @@ export interface ResolvedOptions { export interface TunnelDescription { remoteAddress: { port: number, host: string }; - localAddress: string; + localAddress: { port: number, host: string } | string; } export interface TunnelInformation { environmentTunnels?: TunnelDescription[]; @@ -40,28 +40,24 @@ export enum RemoteAuthorityResolverErrorCode { export class RemoteAuthorityResolverError extends Error { - public static isHandledNotAvailable(err: any): boolean { - if (err instanceof RemoteAuthorityResolverError) { - if (err._code === RemoteAuthorityResolverErrorCode.NotAvailable && err._detail === true) { - return true; - } - } - - return this.isTemporarilyNotAvailable(err); - } - public static isTemporarilyNotAvailable(err: any): boolean { return (err instanceof RemoteAuthorityResolverError) && err._code === RemoteAuthorityResolverErrorCode.TemporarilyNotAvailable; } - public static isNoResolverFound(err: any): boolean { + public static isNoResolverFound(err: any): err is RemoteAuthorityResolverError { return (err instanceof RemoteAuthorityResolverError) && err._code === RemoteAuthorityResolverErrorCode.NoResolverFound; } + public static isHandled(err: any): boolean { + return (err instanceof RemoteAuthorityResolverError) && err.isHandled; + } + public readonly _message: string | undefined; public readonly _code: RemoteAuthorityResolverErrorCode; public readonly _detail: any; + public isHandled: boolean; + constructor(message?: string, code: RemoteAuthorityResolverErrorCode = RemoteAuthorityResolverErrorCode.Unknown, detail?: any) { super(message); @@ -69,6 +65,8 @@ export class RemoteAuthorityResolverError extends Error { this._code = code; this._detail = detail; + this.isHandled = (code === RemoteAuthorityResolverErrorCode.NotAvailable) && detail === true; + // workaround when extending builtin objects and when compiling to ES5, see: // https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work if (typeof (Object).setPrototypeOf === 'function') { diff --git a/src/vs/platform/remote/common/tunnel.ts b/src/vs/platform/remote/common/tunnel.ts index 4b0fdee24c1..0cdceb96358 100644 --- a/src/vs/platform/remote/common/tunnel.ts +++ b/src/vs/platform/remote/common/tunnel.ts @@ -15,7 +15,7 @@ export interface RemoteTunnel { readonly tunnelRemoteHost: string; readonly tunnelLocalPort?: number; readonly localAddress: string; - dispose(): void; + dispose(silent?: boolean): void; } export interface TunnelOptions { diff --git a/src/vs/platform/resource/common/resourceIdentityService.ts b/src/vs/platform/resource/common/resourceIdentityService.ts new file mode 100644 index 00000000000..d81e3dc8e2f --- /dev/null +++ b/src/vs/platform/resource/common/resourceIdentityService.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 { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { URI } from 'vs/base/common/uri'; +import { hash } from 'vs/base/common/hash'; +import { Disposable } from 'vs/base/common/lifecycle'; + +export const IResourceIdentityService = createDecorator('IResourceIdentityService'); +export interface IResourceIdentityService { + _serviceBrand: undefined; + resolveResourceIdentity(resource: URI): Promise; +} + +export class WebResourceIdentityService extends Disposable implements IResourceIdentityService { + _serviceBrand: undefined; + async resolveResourceIdentity(resource: URI): Promise { + return hash(resource.toString()).toString(16); + } +} diff --git a/src/vs/platform/resource/node/resourceIdentityServiceImpl.ts b/src/vs/platform/resource/node/resourceIdentityServiceImpl.ts new file mode 100644 index 00000000000..71cb292b6ec --- /dev/null +++ b/src/vs/platform/resource/node/resourceIdentityServiceImpl.ts @@ -0,0 +1,54 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createHash } from 'crypto'; +import { stat } from 'vs/base/node/pfs'; +import { Schemas } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; +import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; +import { IResourceIdentityService } from 'vs/platform/resource/common/resourceIdentityService'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ResourceMap } from 'vs/base/common/map'; + +export class NativeResourceIdentityService extends Disposable implements IResourceIdentityService { + + _serviceBrand: undefined; + + private readonly cache: ResourceMap> = new ResourceMap>(); + + resolveResourceIdentity(resource: URI): Promise { + let promise = this.cache.get(resource); + if (!promise) { + promise = this.createIdentity(resource); + this.cache.set(resource, promise); + } + return promise; + } + + private async createIdentity(resource: URI): Promise { + // Return early the folder is not local + if (resource.scheme !== Schemas.file) { + return createHash('md5').update(resource.toString()).digest('hex'); + } + + const fileStat = await stat(resource.fsPath); + let ctime: number | undefined; + if (isLinux) { + ctime = fileStat.ino; // Linux: birthtime is ctime, so we cannot use it! We use the ino instead! + } else if (isMacintosh) { + ctime = fileStat.birthtime.getTime(); // macOS: birthtime is fine to use as is + } else if (isWindows) { + if (typeof fileStat.birthtimeMs === 'number') { + ctime = Math.floor(fileStat.birthtimeMs); // Windows: fix precision issue in node.js 8.x to get 7.x results (see https://github.com/nodejs/node/issues/19897) + } else { + ctime = fileStat.birthtime.getTime(); + } + } + + // we use the ctime as extra salt to the ID so that we catch the case of a folder getting + // deleted and recreated. in that case we do not want to carry over previous state + return createHash('md5').update(resource.fsPath).update(ctime ? String(ctime) : '').digest('hex'); + } +} diff --git a/src/vs/platform/serviceMachineId/common/serviceMachineId.ts b/src/vs/platform/serviceMachineId/common/serviceMachineId.ts new file mode 100644 index 00000000000..16178a9da1f --- /dev/null +++ b/src/vs/platform/serviceMachineId/common/serviceMachineId.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 { IFileService } from 'vs/platform/files/common/files'; +import { StorageScope } from 'vs/platform/storage/common/storage'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { isUUID, generateUuid } from 'vs/base/common/uuid'; +import { VSBuffer } from 'vs/base/common/buffer'; + +export async function getServiceMachineId(environmentService: IEnvironmentService, fileService: IFileService, storageService: { + get: (key: string, scope: StorageScope, fallbackValue?: string | undefined) => string | undefined, + store: (key: string, value: string, scope: StorageScope) => void +} | undefined): Promise { + let uuid: string | null = storageService ? storageService.get('storage.serviceMachineId', StorageScope.GLOBAL) || null : null; + if (uuid) { + return uuid; + } + if (environmentService.serviceMachineIdResource) { + try { + const contents = await fileService.readFile(environmentService.serviceMachineIdResource); + const value = contents.value.toString(); + uuid = isUUID(value) ? value : null; + } catch (e) { + uuid = null; + } + + if (!uuid) { + uuid = generateUuid(); + try { + await fileService.writeFile(environmentService.serviceMachineIdResource, VSBuffer.fromString(uuid)); + } catch (error) { + //noop + } + } + } + if (uuid && storageService) { + storageService.store('storage.serviceMachineId', uuid, StorageScope.GLOBAL); + } + return uuid; +} diff --git a/src/vs/platform/storage/browser/storageService.ts b/src/vs/platform/storage/browser/storageService.ts index 1c8174147b0..cbf002a0605 100644 --- a/src/vs/platform/storage/browser/storageService.ts +++ b/src/vs/platform/storage/browser/storageService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { IWorkspaceStorageChangeEvent, IStorageService, StorageScope, IWillSaveStateEvent, WillSaveStateReason, logStorage } from 'vs/platform/storage/common/storage'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; @@ -13,7 +13,6 @@ import { IStorage, Storage, IStorageDatabase, IStorageItemsChangeEvent, IUpdateR import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; import { runWhenIdle, RunOnceScheduler } from 'vs/base/common/async'; -import { serializableToMap, mapToSerializable } from 'vs/base/common/map'; import { VSBuffer } from 'vs/base/common/buffer'; import { assertIsDefined, assertAllDefined } from 'vs/base/common/types'; @@ -189,8 +188,8 @@ export class BrowserStorageService extends Disposable implements IStorageService export class FileStorageDatabase extends Disposable implements IStorageDatabase { - private readonly _onDidChangeItemsExternal: Emitter = this._register(new Emitter()); - readonly onDidChangeItemsExternal: Event = this._onDidChangeItemsExternal.event; + private readonly _onDidChangeItemsExternal = this._register(new Emitter()); + readonly onDidChangeItemsExternal = this._onDidChangeItemsExternal.event; private cache: Map | undefined; @@ -291,7 +290,7 @@ export class FileStorageDatabase extends Disposable implements IStorageDatabase this.ensureWatching(); // now that the file must exist, ensure we watch it for changes - return serializableToMap(JSON.parse(itemsRaw.value.toString())); + return new Map(JSON.parse(itemsRaw.value.toString())); } async updateItems(request: IUpdateRequest): Promise { @@ -311,7 +310,7 @@ export class FileStorageDatabase extends Disposable implements IStorageDatabase try { this._hasPendingUpdate = true; - await this.fileService.writeFile(this.file, VSBuffer.fromString(JSON.stringify(mapToSerializable(items)))); + await this.fileService.writeFile(this.file, VSBuffer.fromString(JSON.stringify(Array.from(items.entries())))); this.ensureWatching(); // now that the file must exist, ensure we watch it for changes } finally { diff --git a/src/vs/platform/storage/common/storage.ts b/src/vs/platform/storage/common/storage.ts index 8a97b4be30c..a2ee988de63 100644 --- a/src/vs/platform/storage/common/storage.ts +++ b/src/vs/platform/storage/common/storage.ts @@ -125,8 +125,8 @@ export const enum StorageScope { } export interface IWorkspaceStorageChangeEvent { - key: string; - scope: StorageScope; + readonly key: string; + readonly scope: StorageScope; } export class InMemoryStorageService extends Disposable implements IStorageService { @@ -139,8 +139,8 @@ export class InMemoryStorageService extends Disposable implements IStorageServic protected readonly _onWillSaveState = this._register(new Emitter()); readonly onWillSaveState = this._onWillSaveState.event; - private globalCache: Map = new Map(); - private workspaceCache: Map = new Map(); + private readonly globalCache = new Map(); + private readonly workspaceCache = new Map(); private getCache(scope: StorageScope): Map { return scope === StorageScope.GLOBAL ? this.globalCache : this.workspaceCache; diff --git a/src/vs/platform/storage/node/storageIpc.ts b/src/vs/platform/storage/node/storageIpc.ts index a6f2367f355..5b1d824e58b 100644 --- a/src/vs/platform/storage/node/storageIpc.ts +++ b/src/vs/platform/storage/node/storageIpc.ts @@ -7,12 +7,10 @@ import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event, Emitter } from 'vs/base/common/event'; import { IStorageChangeEvent, IStorageMainService } from 'vs/platform/storage/node/storageMainService'; import { IUpdateRequest, IStorageDatabase, IStorageItemsChangeEvent } from 'vs/base/parts/storage/common/storage'; -import { mapToSerializable, serializableToMap, values } from 'vs/base/common/map'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { onUnexpectedError } from 'vs/base/common/errors'; import { ILogService } from 'vs/platform/log/common/log'; import { generateUuid } from 'vs/base/common/uuid'; -import { instanceStorageKey, firstSessionDateStorageKey, lastSessionDateStorageKey, currentSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry'; +import { instanceStorageKey, firstSessionDateStorageKey, lastSessionDateStorageKey, currentSessionDateStorageKey, crashReporterIdStorageKey } from 'vs/platform/telemetry/common/telemetry'; type Key = string; type Value = string; @@ -24,8 +22,8 @@ interface ISerializableUpdateRequest { } interface ISerializableItemsChangeEvent { - changed?: Item[]; - deleted?: Key[]; + readonly changed?: Item[]; + readonly deleted?: Key[]; } export class GlobalStorageDatabaseChannel extends Disposable implements IServerChannel { @@ -35,23 +33,30 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC private readonly _onDidChangeItems = this._register(new Emitter()); readonly onDidChangeItems = this._onDidChangeItems.event; - private whenReady: Promise; + private readonly whenReady = this.init(); constructor( private logService: ILogService, private storageMainService: IStorageMainService ) { super(); - - this.whenReady = this.init(); } private async init(): Promise { try { await this.storageMainService.initialize(); } catch (error) { - onUnexpectedError(error); - this.logService.error(error); + this.logService.error(`[storage] init(): Unable to init global storage due to ${error}`); + } + + // This is unique to the application instance and thereby + // should be written from the main process once. + // + // THIS SHOULD NEVER BE SENT TO TELEMETRY. + // + const crashReporterId = this.storageMainService.get(crashReporterIdStorageKey, undefined); + if (crashReporterId === undefined) { + this.storageMainService.store(crashReporterIdStorageKey, generateUuid()); } // Apply global telemetry values as part of the initialization @@ -111,7 +116,10 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC } }); - return { changed: mapToSerializable(changed), deleted: values(deleted) }; + return { + changed: Array.from(changed.entries()), + deleted: Array.from(deleted.values()) + }; } listen(_: unknown, event: string): Event { @@ -130,7 +138,7 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC // handle call switch (command) { case 'getItems': { - return mapToSerializable(this.storageMainService.items); + return Array.from(this.storageMainService.items.entries()); } case 'updateItems': { @@ -158,8 +166,8 @@ export class GlobalStorageDatabaseChannelClient extends Disposable implements IS _serviceBrand: undefined; - private readonly _onDidChangeItemsExternal: Emitter = this._register(new Emitter()); - readonly onDidChangeItemsExternal: Event = this._onDidChangeItemsExternal.event; + private readonly _onDidChangeItemsExternal = this._register(new Emitter()); + readonly onDidChangeItemsExternal = this._onDidChangeItemsExternal.event; private onDidChangeItemsOnMainListener: IDisposable | undefined; @@ -176,7 +184,7 @@ export class GlobalStorageDatabaseChannelClient extends Disposable implements IS private onDidChangeItemsOnMain(e: ISerializableItemsChangeEvent): void { if (Array.isArray(e.changed) || Array.isArray(e.deleted)) { this._onDidChangeItemsExternal.fire({ - changed: e.changed ? serializableToMap(e.changed) : undefined, + changed: e.changed ? new Map(e.changed) : undefined, deleted: e.deleted ? new Set(e.deleted) : undefined }); } @@ -185,18 +193,18 @@ export class GlobalStorageDatabaseChannelClient extends Disposable implements IS async getItems(): Promise> { const items: Item[] = await this.channel.call('getItems'); - return serializableToMap(items); + return new Map(items); } updateItems(request: IUpdateRequest): Promise { const serializableRequest: ISerializableUpdateRequest = Object.create(null); if (request.insert) { - serializableRequest.insert = mapToSerializable(request.insert); + serializableRequest.insert = Array.from(request.insert.entries()); } if (request.delete) { - serializableRequest.delete = values(request.delete); + serializableRequest.delete = Array.from(request.delete.values()); } return this.channel.call('updateItems', serializableRequest); diff --git a/src/vs/platform/storage/node/storageService.ts b/src/vs/platform/storage/node/storageService.ts index cf68e7b5aac..64ab2fa493f 100644 --- a/src/vs/platform/storage/node/storageService.ts +++ b/src/vs/platform/storage/node/storageService.ts @@ -14,7 +14,6 @@ import { join } from 'vs/base/common/path'; import { copy, exists, mkdirp, writeFile } from 'vs/base/node/pfs'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceInitializationPayload, isWorkspaceIdentifier, isSingleFolderWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; -import { onUnexpectedError } from 'vs/base/common/errors'; import { assertIsDefined } from 'vs/base/common/types'; import { RunOnceScheduler, runWhenIdle } from 'vs/base/common/async'; @@ -31,7 +30,7 @@ export class NativeStorageService extends Disposable implements IStorageService private readonly _onWillSaveState = this._register(new Emitter()); readonly onWillSaveState = this._onWillSaveState.event; - private globalStorage: IStorage; + private readonly globalStorage = new Storage(this.globalStorageDatabase); private workspaceStoragePath: string | undefined; private workspaceStorage: IStorage | undefined; @@ -43,14 +42,18 @@ export class NativeStorageService extends Disposable implements IStorageService private runWhenIdleDisposable: IDisposable | undefined = undefined; constructor( - globalStorageDatabase: IStorageDatabase, + private globalStorageDatabase: IStorageDatabase, @ILogService private readonly logService: ILogService, @IEnvironmentService private readonly environmentService: IEnvironmentService ) { super(); - // Global Storage - this.globalStorage = new Storage(globalStorageDatabase); + this.registerListeners(); + } + + private registerListeners(): void { + + // Global Storage change events this._register(this.globalStorage.onDidChangeStorage(key => this.handleDidChangeStorage(key, StorageScope.GLOBAL))); } @@ -95,15 +98,16 @@ export class NativeStorageService extends Disposable implements IStorageService // Create workspace storage and initialize mark('willInitWorkspaceStorage'); try { - await this.createWorkspaceStorage(useInMemoryStorage ? SQLiteStorageDatabase.IN_MEMORY_PATH : join(result.path, NativeStorageService.WORKSPACE_STORAGE_NAME), result.wasCreated ? StorageHint.STORAGE_DOES_NOT_EXIST : undefined).init(); + const workspaceStorage = this.createWorkspaceStorage( + useInMemoryStorage ? SQLiteStorageDatabase.IN_MEMORY_PATH : join(result.path, NativeStorageService.WORKSPACE_STORAGE_NAME), + result.wasCreated ? StorageHint.STORAGE_DOES_NOT_EXIST : undefined + ); + await workspaceStorage.init(); } finally { mark('didInitWorkspaceStorage'); } } catch (error) { - onUnexpectedError(error); - - // Upon error, fallback to in-memory storage - return this.createWorkspaceStorage(SQLiteStorageDatabase.IN_MEMORY_PATH).init(); + this.logService.error(`[storage] initializeWorkspaceStorage(): Unable to init workspace storage due to ${error}`); } } @@ -156,6 +160,7 @@ export class NativeStorageService extends Disposable implements IStorageService } if (meta) { + const logService = this.logService; const workspaceStorageMetaPath = join(this.getWorkspaceStorageFolderPath(payload), NativeStorageService.WORKSPACE_META_NAME); (async function () { try { @@ -164,7 +169,7 @@ export class NativeStorageService extends Disposable implements IStorageService await writeFile(workspaceStorageMetaPath, JSON.stringify(meta, undefined, 2)); } } catch (error) { - onUnexpectedError(error); + logService.error(error); } })(); } diff --git a/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts b/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts index b798518500b..ff1f99715e0 100644 --- a/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts +++ b/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts @@ -4,17 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; - -export const instanceStorageKey = 'telemetry.instanceId'; -export const currentSessionDateStorageKey = 'telemetry.currentSessionDate'; -export const firstSessionDateStorageKey = 'telemetry.firstSessionDate'; -export const lastSessionDateStorageKey = 'telemetry.lastSessionDate'; -export const machineIdKey = 'telemetry.machineId'; - import * as Platform from 'vs/base/common/platform'; import * as uuid from 'vs/base/common/uuid'; import { cleanRemoteAuthority } from 'vs/platform/telemetry/common/telemetryUtils'; import { mixin } from 'vs/base/common/objects'; +import { firstSessionDateStorageKey, lastSessionDateStorageKey, machineIdKey } from 'vs/platform/telemetry/common/telemetry'; export async function resolveWorkbenchCommonProperties( storageService: IStorageService, diff --git a/src/vs/platform/telemetry/common/telemetry.ts b/src/vs/platform/telemetry/common/telemetry.ts index d1125e0ffb3..07ad8719c7e 100644 --- a/src/vs/platform/telemetry/common/telemetry.ts +++ b/src/vs/platform/telemetry/common/telemetry.ts @@ -45,3 +45,6 @@ export const instanceStorageKey = 'telemetry.instanceId'; export const currentSessionDateStorageKey = 'telemetry.currentSessionDate'; export const firstSessionDateStorageKey = 'telemetry.firstSessionDate'; export const lastSessionDateStorageKey = 'telemetry.lastSessionDate'; +export const machineIdKey = 'telemetry.machineId'; +export const trueMachineIdKey = 'telemetry.trueMachineId'; +export const crashReporterIdStorageKey = 'crashReporter.guid'; diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 3fde36a7b6f..54575132334 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -6,9 +6,8 @@ import * as platform from 'vs/platform/registry/common/platform'; import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import { Color, RGBA } from 'vs/base/common/color'; -import { ITheme } from 'vs/platform/theme/common/themeService'; +import { IColorTheme } from 'vs/platform/theme/common/themeService'; import { Event, Emitter } from 'vs/base/common/event'; - import * as nls from 'vs/nls'; import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -27,7 +26,7 @@ export interface ColorContribution { export interface ColorFunction { - (theme: ITheme): Color | undefined; + (theme: IColorTheme): Color | undefined; } export interface ColorDefaults { @@ -71,7 +70,7 @@ export interface IColorRegistry { /** * Gets the default color of the given id */ - resolveDefaultColor(id: ColorIdentifier, theme: ITheme): Color | undefined; + resolveDefaultColor(id: ColorIdentifier, theme: IColorTheme): Color | undefined; /** * JSON schema for an object to assign color values to one of the color contributions. @@ -85,8 +84,6 @@ export interface IColorRegistry { } - - class ColorRegistry implements IColorRegistry { private readonly _onDidChangeSchema = new Emitter(); @@ -131,7 +128,7 @@ class ColorRegistry implements IColorRegistry { return Object.keys(this.colorsById).map(id => this.colorsById[id]); } - public resolveDefaultColor(id: ColorIdentifier, theme: ITheme): Color | undefined { + public resolveDefaultColor(id: ColorIdentifier, theme: IColorTheme): Color | undefined { const colorDesc = this.colorsById[id]; if (colorDesc && colorDesc.defaults) { const colorValue = colorDesc.defaults[theme.type]; @@ -227,10 +224,6 @@ export const simpleCheckboxBackground = registerColor('checkbox.background', { d export const simpleCheckboxForeground = registerColor('checkbox.foreground', { dark: selectForeground, light: selectForeground, hc: selectForeground }, nls.localize('checkbox.foreground', "Foreground color of checkbox widget.")); export const simpleCheckboxBorder = registerColor('checkbox.border', { dark: selectBorder, light: selectBorder, hc: selectBorder }, nls.localize('checkbox.border', "Border color of checkbox widget.")); - -export const pickerGroupForeground = registerColor('pickerGroup.foreground', { dark: '#3794FF', light: '#0066BF', hc: Color.white }, nls.localize('pickerGroupForeground', "Quick picker color for grouping labels.")); -export const pickerGroupBorder = registerColor('pickerGroup.border', { dark: '#3F3F46', light: '#CCCEDB', hc: Color.white }, nls.localize('pickerGroupBorder', "Quick picker color for grouping borders.")); - export const buttonForeground = registerColor('button.foreground', { dark: Color.white, light: Color.white, hc: Color.white }, nls.localize('buttonForeground', "Button foreground color.")); export const buttonBackground = registerColor('button.background', { dark: '#0E639C', light: '#007ACC', hc: null }, nls.localize('buttonBackground', "Button background color.")); export const buttonHoverBackground = registerColor('button.hoverBackground', { dark: lighten(buttonBackground, 0.2), light: darken(buttonBackground, 0.2), hc: null }, nls.localize('buttonHoverBackground', "Button background color when hovering.")); @@ -279,6 +272,15 @@ export const editorWidgetBorder = registerColor('editorWidget.border', { dark: ' export const editorWidgetResizeBorder = registerColor('editorWidget.resizeBorder', { light: null, dark: null, hc: null }, nls.localize('editorWidgetResizeBorder', "Border color of the resize bar of editor widgets. The color is only used if the widget chooses to have a resize border and if the color is not overridden by a widget.")); +/** + * Quick pick widget + */ +export const quickInputBackground = registerColor('quickInput.background', { dark: editorWidgetBackground, light: editorWidgetBackground, hc: editorWidgetBackground }, nls.localize('pickerBackground', "Quick picker background color. The quick picker widget is the container for pickers like the command palette.")); +export const quickInputForeground = registerColor('quickInput.foreground', { dark: editorWidgetForeground, light: editorWidgetForeground, hc: editorWidgetForeground }, nls.localize('pickerForeground', "Quick picker foreground color. The quick picker widget is the container for pickers like the command palette.")); +export const quickInputTitleBackground = registerColor('quickInputTitle.background', { dark: new Color(new RGBA(255, 255, 255, 0.105)), light: new Color(new RGBA(0, 0, 0, 0.06)), hc: '#000000' }, nls.localize('pickerTitleBackground', "Quick picker title background color. The quick picker widget is the container for pickers like the command palette.")); +export const pickerGroupForeground = registerColor('pickerGroup.foreground', { dark: '#3794FF', light: '#0066BF', hc: Color.white }, nls.localize('pickerGroupForeground', "Quick picker color for grouping labels.")); +export const pickerGroupBorder = registerColor('pickerGroup.border', { dark: '#3F3F46', light: '#CCCEDB', hc: Color.white }, nls.localize('pickerGroupBorder', "Quick picker color for grouping borders.")); + /** * Editor selection colors. */ @@ -363,6 +365,7 @@ export const listFilterWidgetNoMatchesOutline = registerColor('listFilterWidget. export const listFilterMatchHighlight = registerColor('list.filterMatchBackground', { dark: editorFindMatchHighlight, light: editorFindMatchHighlight, hc: null }, nls.localize('listFilterMatchHighlight', 'Background color of the filtered match.')); export const listFilterMatchHighlightBorder = registerColor('list.filterMatchBorder', { dark: editorFindMatchHighlightBorder, light: editorFindMatchHighlightBorder, hc: contrastBorder }, nls.localize('listFilterMatchHighlightBorder', 'Border color of the filtered match.')); export const treeIndentGuidesStroke = registerColor('tree.indentGuidesStroke', { dark: '#585858', light: '#a9a9a9', hc: '#a9a9a9' }, nls.localize('treeIndentGuidesStroke', "Tree stroke color for the indentation guides.")); +export const listDeemphasizedForeground = registerColor('list.deemphasizedForeground', { dark: '#8C8C8C', light: '#8E8E90', hc: '#A7A8A9' }, nls.localize('listDeemphasizedForeground', "List/Tree foreground color for items that are deemphasized. ")); /** * Menu colors @@ -500,7 +503,7 @@ function lessProminent(colorValue: ColorValue, backgroundColorValue: ColorValue, /** * @param colorValue Resolve a color value in the context of a theme */ -export function resolveColorValue(colorValue: ColorValue | null, theme: ITheme): Color | undefined { +export function resolveColorValue(colorValue: ColorValue | null, theme: IColorTheme): Color | undefined { if (colorValue === null) { return undefined; } else if (typeof colorValue === 'string') { diff --git a/src/vs/platform/theme/common/iconRegistry.ts b/src/vs/platform/theme/common/iconRegistry.ts new file mode 100644 index 00000000000..42b1d589352 --- /dev/null +++ b/src/vs/platform/theme/common/iconRegistry.ts @@ -0,0 +1,543 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as platform from 'vs/platform/registry/common/platform'; +import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { Event, Emitter } from 'vs/base/common/event'; +import { localize } from 'vs/nls'; + +// ------ API types + + +// color registry +export const Extensions = { + IconContribution: 'base.contributions.icons' +}; + +export interface IconDefaults { + font?: string; + character: string; +} + +export interface IconContribution { + id: string; + description: string; + deprecationMessage?: string; + defaults: IconDefaults; +} + +export interface IIconRegistry { + + readonly onDidChangeSchema: Event; + + /** + * Register a icon to the registry. + * @param id The icon id + * @param defaults The default values + * @description the description + */ + registerIcon(id: string, defaults: IconDefaults, description: string): ThemeIcon; + + /** + * Register a icon to the registry. + */ + deregisterIcon(id: string): void; + + /** + * Get all icon contributions + */ + getIcons(): IconContribution[]; + + /** + * JSON schema for an object to assign icon values to one of the color contributions. + */ + getIconSchema(): IJSONSchema; + + /** + * JSON schema to for a reference to a icon contribution. + */ + getIconReferenceSchema(): IJSONSchema; + +} + +class IconRegistry implements IIconRegistry { + + private readonly _onDidChangeSchema = new Emitter(); + readonly onDidChangeSchema: Event = this._onDidChangeSchema.event; + + private iconsById: { [key: string]: IconContribution }; + private iconSchema: IJSONSchema & { properties: IJSONSchemaMap } = { type: 'object', properties: {} }; + private iconReferenceSchema: IJSONSchema & { enum: string[], enumDescriptions: string[] } = { type: 'string', enum: [], enumDescriptions: [] }; + + constructor() { + this.iconsById = {}; + } + + public registerIcon(id: string, defaults: IconDefaults, description: string, deprecationMessage?: string): ThemeIcon { + let iconContribution: IconContribution = { id, description, defaults, deprecationMessage }; + this.iconsById[id] = iconContribution; + let propertySchema: IJSONSchema = { type: 'object', description, properties: { font: { type: 'string' }, fontCharacter: { type: 'string' } }, defaultSnippets: [{ body: { fontCharacter: '\\\\e030' } }] }; + if (deprecationMessage) { + propertySchema.deprecationMessage = deprecationMessage; + } + this.iconSchema.properties[id] = propertySchema; + this.iconReferenceSchema.enum.push(id); + this.iconReferenceSchema.enumDescriptions.push(description); + + this._onDidChangeSchema.fire(); + return { id }; + } + + + public deregisterIcon(id: string): void { + delete this.iconsById[id]; + delete this.iconSchema.properties[id]; + const index = this.iconReferenceSchema.enum.indexOf(id); + if (index !== -1) { + this.iconReferenceSchema.enum.splice(index, 1); + this.iconReferenceSchema.enumDescriptions.splice(index, 1); + } + this._onDidChangeSchema.fire(); + } + + public getIcons(): IconContribution[] { + return Object.keys(this.iconsById).map(id => this.iconsById[id]); + } + + public getIconSchema(): IJSONSchema { + return this.iconSchema; + } + + public getIconReferenceSchema(): IJSONSchema { + return this.iconReferenceSchema; + } + + public toString() { + let sorter = (a: string, b: string) => { + let cat1 = a.indexOf('.') === -1 ? 0 : 1; + let cat2 = b.indexOf('.') === -1 ? 0 : 1; + if (cat1 !== cat2) { + return cat1 - cat2; + } + return a.localeCompare(b); + }; + + return Object.keys(this.iconsById).sort(sorter).map(k => `- \`${k}\`: ${this.iconsById[k].description}`).join('\n'); + } + +} + +const iconRegistry = new IconRegistry(); +platform.Registry.add(Extensions.IconContribution, iconRegistry); + +export function registerIcon(id: string, defaults: IconDefaults, description: string, deprecationMessage?: string): ThemeIcon { + return iconRegistry.registerIcon(id, defaults, description, deprecationMessage); +} + +export function getIconRegistry(): IIconRegistry { + return iconRegistry; +} + +registerIcon('add', { character: '\ea60' }, localize('add', '')); +registerIcon('plus', { character: '\ea60' }, localize('plus', '')); +registerIcon('gist-new', { character: '\ea60' }, localize('gist-new', '')); +registerIcon('repo-create', { character: '\ea60' }, localize('repo-create', '')); +registerIcon('lightbulb', { character: '\ea61' }, localize('lightbulb', '')); +registerIcon('light-bulb', { character: '\ea61' }, localize('light-bulb', '')); +registerIcon('repo', { character: '\ea62' }, localize('repo', '')); +registerIcon('repo-delete', { character: '\ea62' }, localize('repo-delete', '')); +registerIcon('gist-fork', { character: '\ea63' }, localize('gist-fork', '')); +registerIcon('repo-forked', { character: '\ea63' }, localize('repo-forked', '')); +registerIcon('git-pull-request', { character: '\ea64' }, localize('git-pull-request', '')); +registerIcon('git-pull-request-abandoned', { character: '\ea64' }, localize('git-pull-request-abandoned', '')); +registerIcon('record-keys', { character: '\ea65' }, localize('record-keys', '')); +registerIcon('keyboard', { character: '\ea65' }, localize('keyboard', '')); +registerIcon('tag', { character: '\ea66' }, localize('tag', '')); +registerIcon('tag-add', { character: '\ea66' }, localize('tag-add', '')); +registerIcon('tag-remove', { character: '\ea66' }, localize('tag-remove', '')); +registerIcon('person', { character: '\ea67' }, localize('person', '')); +registerIcon('person-add', { character: '\ea67' }, localize('person-add', '')); +registerIcon('person-follow', { character: '\ea67' }, localize('person-follow', '')); +registerIcon('person-outline', { character: '\ea67' }, localize('person-outline', '')); +registerIcon('person-filled', { character: '\ea67' }, localize('person-filled', '')); +registerIcon('git-branch', { character: '\ea68' }, localize('git-branch', '')); +registerIcon('git-branch-create', { character: '\ea68' }, localize('git-branch-create', '')); +registerIcon('git-branch-delete', { character: '\ea68' }, localize('git-branch-delete', '')); +registerIcon('source-control', { character: '\ea68' }, localize('source-control', '')); +registerIcon('mirror', { character: '\ea69' }, localize('mirror', '')); +registerIcon('mirror-public', { character: '\ea69' }, localize('mirror-public', '')); +registerIcon('star', { character: '\ea6a' }, localize('star', '')); +registerIcon('star-add', { character: '\ea6a' }, localize('star-add', '')); +registerIcon('star-delete', { character: '\ea6a' }, localize('star-delete', '')); +registerIcon('star-empty', { character: '\ea6a' }, localize('star-empty', '')); +registerIcon('comment', { character: '\ea6b' }, localize('comment', '')); +registerIcon('comment-add', { character: '\ea6b' }, localize('comment-add', '')); +registerIcon('alert', { character: '\ea6c' }, localize('alert', '')); +registerIcon('warning', { character: '\ea6c' }, localize('warning', '')); +registerIcon('search', { character: '\ea6d' }, localize('search', '')); +registerIcon('search-save', { character: '\ea6d' }, localize('search-save', '')); +registerIcon('log-out', { character: '\ea6e' }, localize('log-out', '')); +registerIcon('sign-out', { character: '\ea6e' }, localize('sign-out', '')); +registerIcon('log-in', { character: '\ea6f' }, localize('log-in', '')); +registerIcon('sign-in', { character: '\ea6f' }, localize('sign-in', '')); +registerIcon('eye', { character: '\ea70' }, localize('eye', '')); +registerIcon('eye-unwatch', { character: '\ea70' }, localize('eye-unwatch', '')); +registerIcon('eye-watch', { character: '\ea70' }, localize('eye-watch', '')); +registerIcon('circle-filled', { character: '\ea71' }, localize('circle-filled', '')); +registerIcon('primitive-dot', { character: '\ea71' }, localize('primitive-dot', '')); +registerIcon('close-dirty', { character: '\ea71' }, localize('close-dirty', '')); +registerIcon('debug-breakpoint', { character: '\ea71' }, localize('debug-breakpoint', '')); +registerIcon('debug-breakpoint-disabled', { character: '\ea71' }, localize('debug-breakpoint-disabled', '')); +registerIcon('debug-hint', { character: '\ea71' }, localize('debug-hint', '')); +registerIcon('primitive-square', { character: '\ea72' }, localize('primitive-square', '')); +registerIcon('edit', { character: '\ea73' }, localize('edit', '')); +registerIcon('pencil', { character: '\ea73' }, localize('pencil', '')); +registerIcon('info', { character: '\ea74' }, localize('info', '')); +registerIcon('issue-opened', { character: '\ea74' }, localize('issue-opened', '')); +registerIcon('gist-private', { character: '\ea75' }, localize('gist-private', '')); +registerIcon('git-fork-private', { character: '\ea75' }, localize('git-fork-private', '')); +registerIcon('lock', { character: '\ea75' }, localize('lock', '')); +registerIcon('mirror-private', { character: '\ea75' }, localize('mirror-private', '')); +registerIcon('close', { character: '\ea76' }, localize('close', '')); +registerIcon('remove-close', { character: '\ea76' }, localize('remove-close', '')); +registerIcon('x', { character: '\ea76' }, localize('x', '')); +registerIcon('repo-sync', { character: '\ea77' }, localize('repo-sync', '')); +registerIcon('sync', { character: '\ea77' }, localize('sync', '')); +registerIcon('clone', { character: '\ea78' }, localize('clone', '')); +registerIcon('desktop-download', { character: '\ea78' }, localize('desktop-download', '')); +registerIcon('beaker', { character: '\ea79' }, localize('beaker', '')); +registerIcon('microscope', { character: '\ea79' }, localize('microscope', '')); +registerIcon('vm', { character: '\ea7a' }, localize('vm', '')); +registerIcon('device-desktop', { character: '\ea7a' }, localize('device-desktop', '')); +registerIcon('file', { character: '\ea7b' }, localize('file', '')); +registerIcon('file-text', { character: '\ea7b' }, localize('file-text', '')); +registerIcon('more', { character: '\ea7c' }, localize('more', '')); +registerIcon('ellipsis', { character: '\ea7c' }, localize('ellipsis', '')); +registerIcon('kebab-horizontal', { character: '\ea7c' }, localize('kebab-horizontal', '')); +registerIcon('mail-reply', { character: '\ea7d' }, localize('mail-reply', '')); +registerIcon('reply', { character: '\ea7d' }, localize('reply', '')); +registerIcon('organization', { character: '\ea7e' }, localize('organization', '')); +registerIcon('organization-filled', { character: '\ea7e' }, localize('organization-filled', '')); +registerIcon('organization-outline', { character: '\ea7e' }, localize('organization-outline', '')); +registerIcon('new-file', { character: '\ea7f' }, localize('new-file', '')); +registerIcon('file-add', { character: '\ea7f' }, localize('file-add', '')); +registerIcon('new-folder', { character: '\ea80' }, localize('new-folder', '')); +registerIcon('file-directory-create', { character: '\ea80' }, localize('file-directory-create', '')); +registerIcon('trash', { character: '\ea81' }, localize('trash', '')); +registerIcon('trashcan', { character: '\ea81' }, localize('trashcan', '')); +registerIcon('history', { character: '\ea82' }, localize('history', '')); +registerIcon('clock', { character: '\ea82' }, localize('clock', '')); +registerIcon('folder', { character: '\ea83' }, localize('folder', '')); +registerIcon('file-directory', { character: '\ea83' }, localize('file-directory', '')); +registerIcon('symbol-folder', { character: '\ea83' }, localize('symbol-folder', '')); +registerIcon('logo-github', { character: '\ea84' }, localize('logo-github', '')); +registerIcon('mark-github', { character: '\ea84' }, localize('mark-github', '')); +registerIcon('github', { character: '\ea84' }, localize('github', '')); +registerIcon('terminal', { character: '\ea85' }, localize('terminal', '')); +registerIcon('console', { character: '\ea85' }, localize('console', '')); +registerIcon('repl', { character: '\ea85' }, localize('repl', '')); +registerIcon('zap', { character: '\ea86' }, localize('zap', '')); +registerIcon('symbol-event', { character: '\ea86' }, localize('symbol-event', '')); +registerIcon('error', { character: '\ea87' }, localize('error', '')); +registerIcon('stop', { character: '\ea87' }, localize('stop', '')); +registerIcon('variable', { character: '\ea88' }, localize('variable', '')); +registerIcon('symbol-variable', { character: '\ea88' }, localize('symbol-variable', '')); +registerIcon('array', { character: '\ea8a' }, localize('array', '')); +registerIcon('symbol-array', { character: '\ea8a' }, localize('symbol-array', '')); +registerIcon('symbol-module', { character: '\ea8b' }, localize('symbol-module', '')); +registerIcon('symbol-package', { character: '\ea8b' }, localize('symbol-package', '')); +registerIcon('symbol-namespace', { character: '\ea8b' }, localize('symbol-namespace', '')); +registerIcon('symbol-object', { character: '\ea8b' }, localize('symbol-object', '')); +registerIcon('symbol-method', { character: '\ea8c' }, localize('symbol-method', '')); +registerIcon('symbol-function', { character: '\ea8c' }, localize('symbol-function', '')); +registerIcon('symbol-constructor', { character: '\ea8c' }, localize('symbol-constructor', '')); +registerIcon('symbol-boolean', { character: '\ea8f' }, localize('symbol-boolean', '')); +registerIcon('symbol-null', { character: '\ea8f' }, localize('symbol-null', '')); +registerIcon('symbol-numeric', { character: '\ea90' }, localize('symbol-numeric', '')); +registerIcon('symbol-number', { character: '\ea90' }, localize('symbol-number', '')); +registerIcon('symbol-structure', { character: '\ea91' }, localize('symbol-structure', '')); +registerIcon('symbol-struct', { character: '\ea91' }, localize('symbol-struct', '')); +registerIcon('symbol-parameter', { character: '\ea92' }, localize('symbol-parameter', '')); +registerIcon('symbol-type-parameter', { character: '\ea92' }, localize('symbol-type-parameter', '')); +registerIcon('symbol-key', { character: '\ea93' }, localize('symbol-key', '')); +registerIcon('symbol-text', { character: '\ea93' }, localize('symbol-text', '')); +registerIcon('symbol-reference', { character: '\ea94' }, localize('symbol-reference', '')); +registerIcon('go-to-file', { character: '\ea94' }, localize('go-to-file', '')); +registerIcon('symbol-enum', { character: '\ea95' }, localize('symbol-enum', '')); +registerIcon('symbol-value', { character: '\ea95' }, localize('symbol-value', '')); +registerIcon('symbol-ruler', { character: '\ea96' }, localize('symbol-ruler', '')); +registerIcon('symbol-unit', { character: '\ea96' }, localize('symbol-unit', '')); +registerIcon('activate-breakpoints', { character: '\ea97' }, localize('activate-breakpoints', '')); +registerIcon('archive', { character: '\ea98' }, localize('archive', '')); +registerIcon('arrow-both', { character: '\ea99' }, localize('arrow-both', '')); +registerIcon('arrow-down', { character: '\ea9a' }, localize('arrow-down', '')); +registerIcon('arrow-left', { character: '\ea9b' }, localize('arrow-left', '')); +registerIcon('arrow-right', { character: '\ea9c' }, localize('arrow-right', '')); +registerIcon('arrow-small-down', { character: '\ea9d' }, localize('arrow-small-down', '')); +registerIcon('arrow-small-left', { character: '\ea9e' }, localize('arrow-small-left', '')); +registerIcon('arrow-small-right', { character: '\ea9f' }, localize('arrow-small-right', '')); +registerIcon('arrow-small-up', { character: '\eaa0' }, localize('arrow-small-up', '')); +registerIcon('arrow-up', { character: '\eaa1' }, localize('arrow-up', '')); +registerIcon('bell', { character: '\eaa2' }, localize('bell', '')); +registerIcon('bold', { character: '\eaa3' }, localize('bold', '')); +registerIcon('book', { character: '\eaa4' }, localize('book', '')); +registerIcon('bookmark', { character: '\eaa5' }, localize('bookmark', '')); +registerIcon('debug-breakpoint-conditional-unverified', { character: '\eaa6' }, localize('debug-breakpoint-conditional-unverified', '')); +registerIcon('debug-breakpoint-conditional', { character: '\eaa7' }, localize('debug-breakpoint-conditional', '')); +registerIcon('debug-breakpoint-conditional-disabled', { character: '\eaa7' }, localize('debug-breakpoint-conditional-disabled', '')); +registerIcon('debug-breakpoint-data-unverified', { character: '\eaa8' }, localize('debug-breakpoint-data-unverified', '')); +registerIcon('debug-breakpoint-data', { character: '\eaa9' }, localize('debug-breakpoint-data', '')); +registerIcon('debug-breakpoint-data-disabled', { character: '\eaa9' }, localize('debug-breakpoint-data-disabled', '')); +registerIcon('debug-breakpoint-log-unverified', { character: '\eaaa' }, localize('debug-breakpoint-log-unverified', '')); +registerIcon('debug-breakpoint-log', { character: '\eaab' }, localize('debug-breakpoint-log', '')); +registerIcon('debug-breakpoint-log-disabled', { character: '\eaab' }, localize('debug-breakpoint-log-disabled', '')); +registerIcon('briefcase', { character: '\eaac' }, localize('briefcase', '')); +registerIcon('broadcast', { character: '\eaad' }, localize('broadcast', '')); +registerIcon('browser', { character: '\eaae' }, localize('browser', '')); +registerIcon('bug', { character: '\eaaf' }, localize('bug', '')); +registerIcon('calendar', { character: '\eab0' }, localize('calendar', '')); +registerIcon('case-sensitive', { character: '\eab1' }, localize('case-sensitive', '')); +registerIcon('check', { character: '\eab2' }, localize('check', '')); +registerIcon('checklist', { character: '\eab3' }, localize('checklist', '')); +registerIcon('chevron-down', { character: '\eab4' }, localize('chevron-down', '')); +registerIcon('chevron-left', { character: '\eab5' }, localize('chevron-left', '')); +registerIcon('chevron-right', { character: '\eab6' }, localize('chevron-right', '')); +registerIcon('chevron-up', { character: '\eab7' }, localize('chevron-up', '')); +registerIcon('chrome-close', { character: '\eab8' }, localize('chrome-close', '')); +registerIcon('chrome-maximize', { character: '\eab9' }, localize('chrome-maximize', '')); +registerIcon('chrome-minimize', { character: '\eaba' }, localize('chrome-minimize', '')); +registerIcon('chrome-restore', { character: '\eabb' }, localize('chrome-restore', '')); +registerIcon('circle-outline', { character: '\eabc' }, localize('circle-outline', '')); +registerIcon('debug-breakpoint-unverified', { character: '\eabc' }, localize('debug-breakpoint-unverified', '')); +registerIcon('circle-slash', { character: '\eabd' }, localize('circle-slash', '')); +registerIcon('circuit-board', { character: '\eabe' }, localize('circuit-board', '')); +registerIcon('clear-all', { character: '\eabf' }, localize('clear-all', '')); +registerIcon('clippy', { character: '\eac0' }, localize('clippy', '')); +registerIcon('close-all', { character: '\eac1' }, localize('close-all', '')); +registerIcon('cloud-download', { character: '\eac2' }, localize('cloud-download', '')); +registerIcon('cloud-upload', { character: '\eac3' }, localize('cloud-upload', '')); +registerIcon('code', { character: '\eac4' }, localize('code', '')); +registerIcon('collapse-all', { character: '\eac5' }, localize('collapse-all', '')); +registerIcon('color-mode', { character: '\eac6' }, localize('color-mode', '')); +registerIcon('comment-discussion', { character: '\eac7' }, localize('comment-discussion', '')); +registerIcon('compare-changes', { character: '\eac8' }, localize('compare-changes', '')); +registerIcon('credit-card', { character: '\eac9' }, localize('credit-card', '')); +registerIcon('dash', { character: '\eacc' }, localize('dash', '')); +registerIcon('dashboard', { character: '\eacd' }, localize('dashboard', '')); +registerIcon('database', { character: '\eace' }, localize('database', '')); +registerIcon('debug-continue', { character: '\eacf' }, localize('debug-continue', '')); +registerIcon('debug-disconnect', { character: '\ead0' }, localize('debug-disconnect', '')); +registerIcon('debug-pause', { character: '\ead1' }, localize('debug-pause', '')); +registerIcon('debug-restart', { character: '\ead2' }, localize('debug-restart', '')); +registerIcon('debug-start', { character: '\ead3' }, localize('debug-start', '')); +registerIcon('debug-step-into', { character: '\ead4' }, localize('debug-step-into', '')); +registerIcon('debug-step-out', { character: '\ead5' }, localize('debug-step-out', '')); +registerIcon('debug-step-over', { character: '\ead6' }, localize('debug-step-over', '')); +registerIcon('debug-stop', { character: '\ead7' }, localize('debug-stop', '')); +registerIcon('debug', { character: '\ead8' }, localize('debug', '')); +registerIcon('device-camera-video', { character: '\ead9' }, localize('device-camera-video', '')); +registerIcon('device-camera', { character: '\eada' }, localize('device-camera', '')); +registerIcon('device-mobile', { character: '\eadb' }, localize('device-mobile', '')); +registerIcon('diff-added', { character: '\eadc' }, localize('diff-added', '')); +registerIcon('diff-ignored', { character: '\eadd' }, localize('diff-ignored', '')); +registerIcon('diff-modified', { character: '\eade' }, localize('diff-modified', '')); +registerIcon('diff-removed', { character: '\eadf' }, localize('diff-removed', '')); +registerIcon('diff-renamed', { character: '\eae0' }, localize('diff-renamed', '')); +registerIcon('diff', { character: '\eae1' }, localize('diff', '')); +registerIcon('discard', { character: '\eae2' }, localize('discard', '')); +registerIcon('editor-layout', { character: '\eae3' }, localize('editor-layout', '')); +registerIcon('empty-window', { character: '\eae4' }, localize('empty-window', '')); +registerIcon('exclude', { character: '\eae5' }, localize('exclude', '')); +registerIcon('extensions', { character: '\eae6' }, localize('extensions', '')); +registerIcon('eye-closed', { character: '\eae7' }, localize('eye-closed', '')); +registerIcon('file-binary', { character: '\eae8' }, localize('file-binary', '')); +registerIcon('file-code', { character: '\eae9' }, localize('file-code', '')); +registerIcon('file-media', { character: '\eaea' }, localize('file-media', '')); +registerIcon('file-pdf', { character: '\eaeb' }, localize('file-pdf', '')); +registerIcon('file-submodule', { character: '\eaec' }, localize('file-submodule', '')); +registerIcon('file-symlink-directory', { character: '\eaed' }, localize('file-symlink-directory', '')); +registerIcon('file-symlink-file', { character: '\eaee' }, localize('file-symlink-file', '')); +registerIcon('file-zip', { character: '\eaef' }, localize('file-zip', '')); +registerIcon('files', { character: '\eaf0' }, localize('files', '')); +registerIcon('filter', { character: '\eaf1' }, localize('filter', '')); +registerIcon('flame', { character: '\eaf2' }, localize('flame', '')); +registerIcon('fold-down', { character: '\eaf3' }, localize('fold-down', '')); +registerIcon('fold-up', { character: '\eaf4' }, localize('fold-up', '')); +registerIcon('fold', { character: '\eaf5' }, localize('fold', '')); +registerIcon('folder-active', { character: '\eaf6' }, localize('folder-active', '')); +registerIcon('folder-opened', { character: '\eaf7' }, localize('folder-opened', '')); +registerIcon('gear', { character: '\eaf8' }, localize('gear', '')); +registerIcon('gift', { character: '\eaf9' }, localize('gift', '')); +registerIcon('gist-secret', { character: '\eafa' }, localize('gist-secret', '')); +registerIcon('gist', { character: '\eafb' }, localize('gist', '')); +registerIcon('git-commit', { character: '\eafc' }, localize('git-commit', '')); +registerIcon('git-compare', { character: '\eafd' }, localize('git-compare', '')); +registerIcon('git-merge', { character: '\eafe' }, localize('git-merge', '')); +registerIcon('github-action', { character: '\eaff' }, localize('github-action', '')); +registerIcon('github-alt', { character: '\eb00' }, localize('github-alt', '')); +registerIcon('globe', { character: '\eb01' }, localize('globe', '')); +registerIcon('grabber', { character: '\eb02' }, localize('grabber', '')); +registerIcon('graph', { character: '\eb03' }, localize('graph', '')); +registerIcon('gripper', { character: '\eb04' }, localize('gripper', '')); +registerIcon('heart', { character: '\eb05' }, localize('heart', '')); +registerIcon('home', { character: '\eb06' }, localize('home', '')); +registerIcon('horizontal-rule', { character: '\eb07' }, localize('horizontal-rule', '')); +registerIcon('hubot', { character: '\eb08' }, localize('hubot', '')); +registerIcon('inbox', { character: '\eb09' }, localize('inbox', '')); +registerIcon('issue-closed', { character: '\eb0a' }, localize('issue-closed', '')); +registerIcon('issue-reopened', { character: '\eb0b' }, localize('issue-reopened', '')); +registerIcon('issues', { character: '\eb0c' }, localize('issues', '')); +registerIcon('italic', { character: '\eb0d' }, localize('italic', '')); +registerIcon('jersey', { character: '\eb0e' }, localize('jersey', '')); +registerIcon('json', { character: '\eb0f' }, localize('json', '')); +registerIcon('kebab-vertical', { character: '\eb10' }, localize('kebab-vertical', '')); +registerIcon('key', { character: '\eb11' }, localize('key', '')); +registerIcon('law', { character: '\eb12' }, localize('law', '')); +registerIcon('lightbulb-autofix', { character: '\eb13' }, localize('lightbulb-autofix', '')); +registerIcon('link-external', { character: '\eb14' }, localize('link-external', '')); +registerIcon('link', { character: '\eb15' }, localize('link', '')); +registerIcon('list-ordered', { character: '\eb16' }, localize('list-ordered', '')); +registerIcon('list-unordered', { character: '\eb17' }, localize('list-unordered', '')); +registerIcon('live-share', { character: '\eb18' }, localize('live-share', '')); +registerIcon('loading', { character: '\eb19' }, localize('loading', '')); +registerIcon('location', { character: '\eb1a' }, localize('location', '')); +registerIcon('mail-read', { character: '\eb1b' }, localize('mail-read', '')); +registerIcon('mail', { character: '\eb1c' }, localize('mail', '')); +registerIcon('markdown', { character: '\eb1d' }, localize('markdown', '')); +registerIcon('megaphone', { character: '\eb1e' }, localize('megaphone', '')); +registerIcon('mention', { character: '\eb1f' }, localize('mention', '')); +registerIcon('milestone', { character: '\eb20' }, localize('milestone', '')); +registerIcon('mortar-board', { character: '\eb21' }, localize('mortar-board', '')); +registerIcon('move', { character: '\eb22' }, localize('move', '')); +registerIcon('multiple-windows', { character: '\eb23' }, localize('multiple-windows', '')); +registerIcon('mute', { character: '\eb24' }, localize('mute', '')); +registerIcon('no-newline', { character: '\eb25' }, localize('no-newline', '')); +registerIcon('note', { character: '\eb26' }, localize('note', '')); +registerIcon('octoface', { character: '\eb27' }, localize('octoface', '')); +registerIcon('open-preview', { character: '\eb28' }, localize('open-preview', '')); +registerIcon('package', { character: '\eb29' }, localize('package', '')); +registerIcon('paintcan', { character: '\eb2a' }, localize('paintcan', '')); +registerIcon('pin', { character: '\eb2b' }, localize('pin', '')); +registerIcon('play', { character: '\eb2c' }, localize('play', '')); +registerIcon('run', { character: '\eb2c' }, localize('run', '')); +registerIcon('plug', { character: '\eb2d' }, localize('plug', '')); +registerIcon('preserve-case', { character: '\eb2e' }, localize('preserve-case', '')); +registerIcon('preview', { character: '\eb2f' }, localize('preview', '')); +registerIcon('project', { character: '\eb30' }, localize('project', '')); +registerIcon('pulse', { character: '\eb31' }, localize('pulse', '')); +registerIcon('question', { character: '\eb32' }, localize('question', '')); +registerIcon('quote', { character: '\eb33' }, localize('quote', '')); +registerIcon('radio-tower', { character: '\eb34' }, localize('radio-tower', '')); +registerIcon('reactions', { character: '\eb35' }, localize('reactions', '')); +registerIcon('references', { character: '\eb36' }, localize('references', '')); +registerIcon('refresh', { character: '\eb37' }, localize('refresh', '')); +registerIcon('regex', { character: '\eb38' }, localize('regex', '')); +registerIcon('remote-explorer', { character: '\eb39' }, localize('remote-explorer', '')); +registerIcon('remote', { character: '\eb3a' }, localize('remote', '')); +registerIcon('remove', { character: '\eb3b' }, localize('remove', '')); +registerIcon('replace-all', { character: '\eb3c' }, localize('replace-all', '')); +registerIcon('replace', { character: '\eb3d' }, localize('replace', '')); +registerIcon('repo-clone', { character: '\eb3e' }, localize('repo-clone', '')); +registerIcon('repo-force-push', { character: '\eb3f' }, localize('repo-force-push', '')); +registerIcon('repo-pull', { character: '\eb40' }, localize('repo-pull', '')); +registerIcon('repo-push', { character: '\eb41' }, localize('repo-push', '')); +registerIcon('report', { character: '\eb42' }, localize('report', '')); +registerIcon('request-changes', { character: '\eb43' }, localize('request-changes', '')); +registerIcon('rocket', { character: '\eb44' }, localize('rocket', '')); +registerIcon('root-folder-opened', { character: '\eb45' }, localize('root-folder-opened', '')); +registerIcon('root-folder', { character: '\eb46' }, localize('root-folder', '')); +registerIcon('rss', { character: '\eb47' }, localize('rss', '')); +registerIcon('ruby', { character: '\eb48' }, localize('ruby', '')); +registerIcon('save-all', { character: '\eb49' }, localize('save-all', '')); +registerIcon('save-as', { character: '\eb4a' }, localize('save-as', '')); +registerIcon('save', { character: '\eb4b' }, localize('save', '')); +registerIcon('screen-full', { character: '\eb4c' }, localize('screen-full', '')); +registerIcon('screen-normal', { character: '\eb4d' }, localize('screen-normal', '')); +registerIcon('search-stop', { character: '\eb4e' }, localize('search-stop', '')); +registerIcon('server', { character: '\eb50' }, localize('server', '')); +registerIcon('settings-gear', { character: '\eb51' }, localize('settings-gear', '')); +registerIcon('settings', { character: '\eb52' }, localize('settings', '')); +registerIcon('shield', { character: '\eb53' }, localize('shield', '')); +registerIcon('smiley', { character: '\eb54' }, localize('smiley', '')); +registerIcon('sort-precedence', { character: '\eb55' }, localize('sort-precedence', '')); +registerIcon('split-horizontal', { character: '\eb56' }, localize('split-horizontal', '')); +registerIcon('split-vertical', { character: '\eb57' }, localize('split-vertical', '')); +registerIcon('squirrel', { character: '\eb58' }, localize('squirrel', '')); +registerIcon('star-full', { character: '\eb59' }, localize('star-full', '')); +registerIcon('star-half', { character: '\eb5a' }, localize('star-half', '')); +registerIcon('symbol-class', { character: '\eb5b' }, localize('symbol-class', '')); +registerIcon('symbol-color', { character: '\eb5c' }, localize('symbol-color', '')); +registerIcon('symbol-constant', { character: '\eb5d' }, localize('symbol-constant', '')); +registerIcon('symbol-enum-member', { character: '\eb5e' }, localize('symbol-enum-member', '')); +registerIcon('symbol-field', { character: '\eb5f' }, localize('symbol-field', '')); +registerIcon('symbol-file', { character: '\eb60' }, localize('symbol-file', '')); +registerIcon('symbol-interface', { character: '\eb61' }, localize('symbol-interface', '')); +registerIcon('symbol-keyword', { character: '\eb62' }, localize('symbol-keyword', '')); +registerIcon('symbol-misc', { character: '\eb63' }, localize('symbol-misc', '')); +registerIcon('symbol-operator', { character: '\eb64' }, localize('symbol-operator', '')); +registerIcon('symbol-property', { character: '\eb65' }, localize('symbol-property', '')); +registerIcon('wrench', { character: '\eb65' }, localize('wrench', '')); +registerIcon('wrench-subaction', { character: '\eb65' }, localize('wrench-subaction', '')); +registerIcon('symbol-snippet', { character: '\eb66' }, localize('symbol-snippet', '')); +registerIcon('tasklist', { character: '\eb67' }, localize('tasklist', '')); +registerIcon('telescope', { character: '\eb68' }, localize('telescope', '')); +registerIcon('text-size', { character: '\eb69' }, localize('text-size', '')); +registerIcon('three-bars', { character: '\eb6a' }, localize('three-bars', '')); +registerIcon('thumbsdown', { character: '\eb6b' }, localize('thumbsdown', '')); +registerIcon('thumbsup', { character: '\eb6c' }, localize('thumbsup', '')); +registerIcon('tools', { character: '\eb6d' }, localize('tools', '')); +registerIcon('triangle-down', { character: '\eb6e' }, localize('triangle-down', '')); +registerIcon('triangle-left', { character: '\eb6f' }, localize('triangle-left', '')); +registerIcon('triangle-right', { character: '\eb70' }, localize('triangle-right', '')); +registerIcon('triangle-up', { character: '\eb71' }, localize('triangle-up', '')); +registerIcon('twitter', { character: '\eb72' }, localize('twitter', '')); +registerIcon('unfold', { character: '\eb73' }, localize('unfold', '')); +registerIcon('unlock', { character: '\eb74' }, localize('unlock', '')); +registerIcon('unmute', { character: '\eb75' }, localize('unmute', '')); +registerIcon('unverified', { character: '\eb76' }, localize('unverified', '')); +registerIcon('verified', { character: '\eb77' }, localize('verified', '')); +registerIcon('versions', { character: '\eb78' }, localize('versions', '')); +registerIcon('vm-active', { character: '\eb79' }, localize('vm-active', '')); +registerIcon('vm-outline', { character: '\eb7a' }, localize('vm-outline', '')); +registerIcon('vm-running', { character: '\eb7b' }, localize('vm-running', '')); +registerIcon('watch', { character: '\eb7c' }, localize('watch', '')); +registerIcon('whitespace', { character: '\eb7d' }, localize('whitespace', '')); +registerIcon('whole-word', { character: '\eb7e' }, localize('whole-word', '')); +registerIcon('window', { character: '\eb7f' }, localize('window', '')); +registerIcon('word-wrap', { character: '\eb80' }, localize('word-wrap', '')); +registerIcon('zoom-in', { character: '\eb81' }, localize('zoom-in', '')); +registerIcon('zoom-out', { character: '\eb82' }, localize('zoom-out', '')); +registerIcon('list-filter', { character: '\eb83' }, localize('list-filter', '')); +registerIcon('list-flat', { character: '\eb84' }, localize('list-flat', '')); +registerIcon('list-selection', { character: '\eb85' }, localize('list-selection', '')); +registerIcon('selection', { character: '\eb85' }, localize('selection', '')); +registerIcon('list-tree', { character: '\eb86' }, localize('list-tree', '')); +registerIcon('debug-breakpoint-function-unverified', { character: '\eb87' }, localize('debug-breakpoint-function-unverified', '')); +registerIcon('debug-breakpoint-function', { character: '\eb88' }, localize('debug-breakpoint-function', '')); +registerIcon('debug-breakpoint-function-disabled', { character: '\eb88' }, localize('debug-breakpoint-function-disabled', '')); +registerIcon('debug-stackframe-active', { character: '\eb89' }, localize('debug-stackframe-active', '')); +registerIcon('debug-stackframe-dot', { character: '\eb8a' }, localize('debug-stackframe-dot', '')); +registerIcon('debug-stackframe', { character: '\eb8b' }, localize('debug-stackframe', '')); +registerIcon('debug-stackframe-focused', { character: '\eb8b' }, localize('debug-stackframe-focused', '')); +registerIcon('debug-breakpoint-unsupported', { character: '\eb8c' }, localize('debug-breakpoint-unsupported', '')); +registerIcon('symbol-string', { character: '\eb8d' }, localize('symbol-string', '')); +registerIcon('debug-reverse-continue', { character: '\eb8e' }, localize('debug-reverse-continue', '')); +registerIcon('debug-step-back', { character: '\eb8f' }, localize('debug-step-back', '')); +registerIcon('debug-restart-frame', { character: '\eb90' }, localize('debug-restart-frame', '')); +registerIcon('debug-alternate', { character: '\eb91' }, localize('debug-alternate', '')); +registerIcon('call-incoming', { character: '\eb92' }, localize('call-incoming', '')); +registerIcon('call-outgoing', { character: '\eb93' }, localize('call-outgoing', '')); +registerIcon('menu', { character: '\eb94' }, localize('menu', '')); +registerIcon('expand-all', { character: '\eb95' }, localize('expand-all', '')); +registerIcon('feedback', { character: '\eb96' }, localize('feedback', '')); +registerIcon('group-by-ref-type', { character: '\eb97' }, localize('group-by-ref-type', '')); +registerIcon('ungroup-by-ref-type', { character: '\eb98' }, localize('ungroup-by-ref-type', '')); +registerIcon('bell-dot', { character: '\f101' }, localize('bell-dot', '')); +registerIcon('debug-alt-2', { character: '\f102' }, localize('debug-alt-2', '')); +registerIcon('debug-alt', { character: '\f103' }, localize('debug-alt', '')); + + +// setTimeout(_ => console.log(colorRegistry.toString()), 5000); diff --git a/src/vs/platform/theme/common/styler.ts b/src/vs/platform/theme/common/styler.ts index 5ffda77ea7d..97e7a7ac64a 100644 --- a/src/vs/platform/theme/common/styler.ts +++ b/src/vs/platform/theme/common/styler.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { focusBorder, inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectListBackground, selectBorder, inputBorder, foreground, editorBackground, contrastBorder, inputActiveOptionBorder, inputActiveOptionBackground, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listInactiveFocusBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupBorder, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, badgeBackground, badgeForeground, progressBarBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, editorWidgetBorder, inputValidationInfoForeground, inputValidationWarningForeground, inputValidationErrorForeground, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuBorder, menuSeparatorBackground, darken, listFilterWidgetOutline, listFilterWidgetNoMatchesOutline, listFilterWidgetBackground, editorWidgetBackground, treeIndentGuidesStroke, editorWidgetForeground, simpleCheckboxBackground, simpleCheckboxBorder, simpleCheckboxForeground, ColorValue, resolveColorValue, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Color } from 'vs/base/common/color'; @@ -21,7 +21,7 @@ export interface IComputedStyles { [color: string]: Color | undefined; } -export function computeStyles(theme: ITheme, styleMap: IColorMapping): IComputedStyles { +export function computeStyles(theme: IColorTheme, styleMap: IColorMapping): IComputedStyles { const styles = Object.create(null) as IComputedStyles; for (let key in styleMap) { const value = styleMap[key]; @@ -34,8 +34,8 @@ export function computeStyles(theme: ITheme, styleMap: IColorMapping): IComputed } export function attachStyler(themeService: IThemeService, styleMap: T, widgetOrCallback: IThemable | styleFn): IDisposable { - function applyStyles(theme: ITheme): void { - const styles = computeStyles(themeService.getTheme(), styleMap); + function applyStyles(theme: IColorTheme): void { + const styles = computeStyles(themeService.getColorTheme(), styleMap); if (typeof widgetOrCallback === 'function') { widgetOrCallback(styles); @@ -44,9 +44,9 @@ export function attachStyler(themeService: IThemeServic } } - applyStyles(themeService.getTheme()); + applyStyles(themeService.getColorTheme()); - return themeService.onThemeChange(applyStyles); + return themeService.onDidColorThemeChange(applyStyles); } export interface ICheckboxStyleOverrides extends IStyleOverrides { @@ -154,7 +154,7 @@ export function attachFindReplaceInputBoxStyler(widget: IThemable, themeService: } as IInputBoxStyleOverrides, widget); } -export interface IQuickOpenStyleOverrides extends IListStyleOverrides, IInputBoxStyleOverrides, IProgressBarStyleOverrides { +export interface IQuickInputStyleOverrides extends IListStyleOverrides, IInputBoxStyleOverrides, IProgressBarStyleOverrides { foreground?: ColorIdentifier; background?: ColorIdentifier; borderColor?: ColorIdentifier; @@ -163,7 +163,7 @@ export interface IQuickOpenStyleOverrides extends IListStyleOverrides, IInputBox pickerGroupBorder?: ColorIdentifier; } -export function attachQuickOpenStyler(widget: IThemable, themeService: IThemeService, style?: IQuickOpenStyleOverrides): IDisposable { +export function attachQuickInputStyler(widget: IThemable, themeService: IThemeService, style?: IQuickInputStyleOverrides): IDisposable { return attachStyler(themeService, { foreground: (style && style.foreground) || foreground, background: (style && style.background) || editorBackground, @@ -199,7 +199,7 @@ export function attachQuickOpenStyler(widget: IThemable, themeService: IThemeSer listFocusOutline: (style && style.listFocusOutline) || activeContrastBorder, listSelectionOutline: (style && style.listSelectionOutline) || activeContrastBorder, listHoverOutline: (style && style.listHoverOutline) || activeContrastBorder - } as IQuickOpenStyleOverrides, widget); + } as IQuickInputStyleOverrides, widget); } export interface IListStyleOverrides extends IStyleOverrides { diff --git a/src/vs/platform/theme/common/themeService.ts b/src/vs/platform/theme/common/themeService.ts index bc4bb79c9d6..77f4795576f 100644 --- a/src/vs/platform/theme/common/themeService.ts +++ b/src/vs/platform/theme/common/themeService.ts @@ -5,7 +5,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Color } from 'vs/base/common/color'; -import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/platform/registry/common/platform'; import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; import { Event, Emitter } from 'vs/base/common/event'; @@ -86,7 +86,7 @@ export interface ITokenStyle { readonly italic?: boolean; } -export interface ITheme { +export interface IColorTheme { readonly type: ThemeType; /** @@ -106,15 +106,20 @@ export interface ITheme { /** * Returns the token style for a given classification. The result uses the MetadataConsts format */ - getTokenStyleMetadata(type: string, modifiers: string[]): ITokenStyle | undefined; + getTokenStyleMetadata(type: string, modifiers: string[], modelLanguage: string): ITokenStyle | undefined; /** * List of all colors used with tokens. getTokenStyleMetadata references the colors by index into this list. */ readonly tokenColorMap: string[]; + + /** + * Defines whether semantic highlighting should be enabled for the theme. + */ + readonly semanticHighlighting: boolean; } -export interface IIconTheme { +export interface IFileIconTheme { readonly hasFileIcons: boolean; readonly hasFolderIcons: boolean; readonly hidesExplorerArrows: boolean; @@ -125,19 +130,19 @@ export interface ICssStyleCollector { } export interface IThemingParticipant { - (theme: ITheme, collector: ICssStyleCollector, environment: IEnvironmentService): void; + (theme: IColorTheme, collector: ICssStyleCollector, environment: IEnvironmentService): void; } export interface IThemeService { _serviceBrand: undefined; - getTheme(): ITheme; + getColorTheme(): IColorTheme; - readonly onThemeChange: Event; + readonly onDidColorThemeChange: Event; - getIconTheme(): IIconTheme; + getFileIconTheme(): IFileIconTheme; - readonly onIconThemeChange: Event; + readonly onDidFileIconThemeChange: Event; } @@ -151,7 +156,7 @@ export interface IThemingRegistry { /** * Register a theming participant that is invoked on every theme change. */ - onThemeChange(participant: IThemingParticipant): IDisposable; + onColorThemeChange(participant: IThemingParticipant): IDisposable; getThemingParticipants(): IThemingParticipant[]; @@ -167,7 +172,7 @@ class ThemingRegistry implements IThemingRegistry { this.onThemingParticipantAddedEmitter = new Emitter(); } - public onThemeChange(participant: IThemingParticipant): IDisposable { + public onColorThemeChange(participant: IThemingParticipant): IDisposable { this.themingParticipants.push(participant); this.onThemingParticipantAddedEmitter.fire(participant); return toDisposable(() => { @@ -189,5 +194,43 @@ let themingRegistry = new ThemingRegistry(); platform.Registry.add(Extensions.ThemingContribution, themingRegistry); export function registerThemingParticipant(participant: IThemingParticipant): IDisposable { - return themingRegistry.onThemeChange(participant); + return themingRegistry.onColorThemeChange(participant); +} + +/** + * Utility base class for all themable components. + */ +export class Themable extends Disposable { + protected theme: IColorTheme; + + constructor( + protected themeService: IThemeService + ) { + super(); + + this.theme = themeService.getColorTheme(); + + // Hook up to theme changes + this._register(this.themeService.onDidColorThemeChange(theme => this.onThemeChange(theme))); + } + + protected onThemeChange(theme: IColorTheme): void { + this.theme = theme; + + this.updateStyles(); + } + + protected updateStyles(): void { + // Subclasses to override + } + + protected getColor(id: string, modify?: (color: Color, theme: IColorTheme) => Color): string | null { + let color = this.theme.getColor(id); + + if (color && modify) { + color = modify(color, this.theme); + } + + return color ? color.toString() : null; + } } diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts index 9faf353353a..433f57a6582 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -5,7 +5,7 @@ import * as platform from 'vs/platform/registry/common/platform'; import { Color } from 'vs/base/common/color'; -import { ITheme } from 'vs/platform/theme/common/themeService'; +import { IColorTheme } from 'vs/platform/theme/common/themeService'; import * as nls from 'vs/nls'; import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -13,16 +13,22 @@ import { Event, Emitter } from 'vs/base/common/event'; import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; export const TOKEN_TYPE_WILDCARD = '*'; +export const TOKEN_CLASSIFIER_LANGUAGE_SEPARATOR = ':'; +export const CLASSIFIER_MODIFIER_SEPARATOR = '.'; -// qualified string [type|*](.modifier)* +// qualified string [type|*](.modifier)*(/language)! export type TokenClassificationString = string; -export const typeAndModifierIdPattern = '^\\w+[-_\\w+]*$'; +export const idPattern = '\\w+[-_\\w+]*'; +export const typeAndModifierIdPattern = `^${idPattern}$`; + +export const selectorPattern = `^(${idPattern}|\\*)(\\${CLASSIFIER_MODIFIER_SEPARATOR}${idPattern})*(\\${TOKEN_CLASSIFIER_LANGUAGE_SEPARATOR}${idPattern})?$`; + export const fontStylePattern = '^(\\s*(-?italic|-?bold|-?underline))*\\s*$'; export interface TokenSelector { - match(type: string, modifiers: string[]): number; - readonly selectorString: string; + match(type: string, modifiers: string[], language: string): number; + readonly id: string; } export interface TokenTypeOrModifierContribution { @@ -52,7 +58,36 @@ export class TokenStyle implements Readonly { } export namespace TokenStyle { - export function fromData(data: { foreground?: Color, bold?: boolean, underline?: boolean, italic?: boolean }) { + export function toJSONObject(style: TokenStyle): any { + return { + _foreground: style.foreground === undefined ? null : Color.Format.CSS.formatHexA(style.foreground, true), + _bold: style.bold === undefined ? null : style.bold, + _underline: style.underline === undefined ? null : style.underline, + _italic: style.italic === undefined ? null : style.italic, + }; + } + export function fromJSONObject(obj: any): TokenStyle | undefined { + if (obj) { + const boolOrUndef = (b: any) => (typeof b === 'boolean') ? b : undefined; + const colorOrUndef = (s: any) => (typeof s === 'string') ? Color.fromHex(s) : undefined; + return new TokenStyle(colorOrUndef(obj._foreground), boolOrUndef(obj._bold), boolOrUndef(obj._underline), boolOrUndef(obj._italic)); + } + return undefined; + } + export function equals(s1: any, s2: any): boolean { + if (s1 === s2) { + return true; + } + return s1 !== undefined && s2 !== undefined + && (s1.foreground instanceof Color ? s1.foreground.equals(s2.foreground) : s2.foreground === undefined) + && s1.bold === s2.bold + && s1.underline === s2.underline + && s1.italic === s2.italic; + } + export function is(s: any): s is TokenStyle { + return s instanceof TokenStyle; + } + export function fromData(data: { foreground?: Color, bold?: boolean, underline?: boolean, italic?: boolean }): TokenStyle { return new TokenStyle(data.foreground, data.bold, data.underline, data.italic); } export function fromSettings(foreground: string | undefined, fontStyle: string | undefined): TokenStyle { @@ -85,7 +120,7 @@ export namespace TokenStyle { export type ProbeScope = string[]; export interface TokenStyleFunction { - (theme: ITheme): TokenStyle | undefined; + (theme: IColorTheme): TokenStyle | undefined; } export interface TokenStyleDefaults { @@ -105,6 +140,38 @@ export interface TokenStylingRule { selector: TokenSelector; } +export namespace TokenStylingRule { + export function fromJSONObject(registry: ITokenClassificationRegistry, o: any): TokenStylingRule | undefined { + if (o && typeof o._selector === 'string' && o._style) { + const style = TokenStyle.fromJSONObject(o._style); + if (style) { + try { + return { selector: registry.parseTokenSelector(o._selector), style }; + } catch (_ignore) { + } + } + } + return undefined; + } + export function toJSONObject(rule: TokenStylingRule): any { + return { + _selector: rule.selector.id, + _style: TokenStyle.toJSONObject(rule.style) + }; + } + export function equals(r1: TokenStylingRule | undefined, r2: TokenStylingRule | undefined) { + if (r1 === r2) { + return true; + } + return r1 !== undefined && r2 !== undefined + && r1.selector && r2.selector && r1.selector.id === r2.selector.id + && TokenStyle.equals(r1.style, r2.style); + } + export function is(r: any): r is TokenStylingRule { + return r && r.selector && typeof r.selector.selectorString === 'string' && TokenStyle.is(r.style); + } +} + /** * A TokenStyle Value is either a token style literal, or a TokenClassificationString */ @@ -136,10 +203,11 @@ export interface ITokenClassificationRegistry { /** * Parses a token selector from a selector string. * @param selectorString selector string in the form (*|type)(.modifier)* + * @param language language to which the selector applies or undefined if the selector is for all languafe * @returns the parsesd selector * @throws an error if the string is not a valid selector */ - parseTokenSelector(selectorString: string): TokenSelector; + parseTokenSelector(selectorString: string, language?: string): TokenSelector; /** * Register a TokenStyle default to the registry. @@ -268,36 +336,42 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { this.tokenStylingSchema.properties[`*.${id}`] = getStylingSchemeEntry(description, deprecationMessage); } - public parseTokenSelector(selectorString: string): TokenSelector { - const [selectorType, ...selectorModifiers] = selectorString.split('.'); + public parseTokenSelector(selectorString: string, language?: string): TokenSelector { + const selector = parseClassifierString(selectorString, language); - if (!selectorType) { + if (!selector.type) { return { match: () => -1, - selectorString + id: '$invalid' }; } return { - match: (type: string, modifiers: string[]) => { + match: (type: string, modifiers: string[], language: string) => { let score = 0; - if (selectorType !== TOKEN_TYPE_WILDCARD) { + if (selector.language !== undefined) { + if (selector.language !== language) { + return -1; + } + score += 10; + } + if (selector.type !== TOKEN_TYPE_WILDCARD) { const hierarchy = this.getTypeHierarchy(type); - const level = hierarchy.indexOf(selectorType); + const level = hierarchy.indexOf(selector.type); if (level === -1) { return -1; } - score = 100 - level; + score += (100 - level); } // all selector modifiers must be present - for (const selectorModifier of selectorModifiers) { + for (const selectorModifier of selector.modifiers) { if (modifiers.indexOf(selectorModifier) === -1) { return -1; } } - return score + selectorModifiers.length * 100; + return score + selector.modifiers.length * 100; }, - selectorString + id: `${[selector.type, ...selector.modifiers.sort()].join('.')}${selector.language !== undefined ? ':' + selector.language : ''}` }; } @@ -306,8 +380,8 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { } public deregisterTokenStyleDefault(selector: TokenSelector): void { - const selectorString = selector.selectorString; - this.tokenStylingDefaultRules = this.tokenStylingDefaultRules.filter(r => r.selector.selectorString !== selectorString); + const selectorString = selector.id; + this.tokenStylingDefaultRules = this.tokenStylingDefaultRules.filter(r => r.selector.id !== selectorString); } public deregisterTokenType(id: string): void { @@ -366,15 +440,43 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { } +const CHAR_LANGUAGE = TOKEN_CLASSIFIER_LANGUAGE_SEPARATOR.charCodeAt(0); +const CHAR_MODIFIER = CLASSIFIER_MODIFIER_SEPARATOR.charCodeAt(0); -const tokenClassificationRegistry = new TokenClassificationRegistry(); +export function parseClassifierString(s: string, defaultLanguage: string): { type: string, modifiers: string[], language: string; }; +export function parseClassifierString(s: string, defaultLanguage?: string): { type: string, modifiers: string[], language: string | undefined; }; +export function parseClassifierString(s: string, defaultLanguage: string | undefined): { type: string, modifiers: string[], language: string | undefined; } { + let k = s.length; + let language: string | undefined = defaultLanguage; + const modifiers = []; + + for (let i = k - 1; i >= 0; i--) { + const ch = s.charCodeAt(i); + if (ch === CHAR_LANGUAGE || ch === CHAR_MODIFIER) { + const segment = s.substring(i + 1, k); + k = i; + if (ch === CHAR_LANGUAGE) { + language = segment; + } else { + modifiers.push(segment); + } + } + } + const type = s.substring(0, k); + return { type, modifiers, language }; +} + + +let tokenClassificationRegistry = createDefaultTokenClassificationRegistry(); platform.Registry.add(Extensions.TokenClassificationContribution, tokenClassificationRegistry); -registerDefaultClassifications(); -function registerDefaultClassifications(): void { +function createDefaultTokenClassificationRegistry(): TokenClassificationRegistry { + + const registry = new TokenClassificationRegistry(); + function registerTokenType(id: string, description: string, scopesToProbe: ProbeScope[] = [], superType?: string, deprecationMessage?: string): string { - tokenClassificationRegistry.registerTokenType(id, description, superType, deprecationMessage); + registry.registerTokenType(id, description, superType, deprecationMessage); if (scopesToProbe) { registerTokenStyleDefault(id, scopesToProbe); } @@ -383,8 +485,8 @@ function registerDefaultClassifications(): void { function registerTokenStyleDefault(selectorString: string, scopesToProbe: ProbeScope[]) { try { - const selector = tokenClassificationRegistry.parseTokenSelector(selectorString); - tokenClassificationRegistry.registerTokenStyleDefault(selector, { scopesToProbe }); + const selector = registry.parseTokenSelector(selectorString); + registry.registerTokenStyleDefault(selector, { scopesToProbe }); } catch (e) { console.log(e); } @@ -422,17 +524,28 @@ function registerDefaultClassifications(): void { // default token modifiers - tokenClassificationRegistry.registerTokenModifier('declaration', nls.localize('declaration', "Style for all symbol declarations."), undefined); - tokenClassificationRegistry.registerTokenModifier('documentation', nls.localize('documentation', "Style to use for references in documentation."), undefined); - tokenClassificationRegistry.registerTokenModifier('static', nls.localize('static', "Style to use for symbols that are static."), undefined); - tokenClassificationRegistry.registerTokenModifier('abstract', nls.localize('abstract', "Style to use for symbols that are abstract."), undefined); - tokenClassificationRegistry.registerTokenModifier('deprecated', nls.localize('deprecated', "Style to use for symbols that are deprecated."), undefined); - tokenClassificationRegistry.registerTokenModifier('modification', nls.localize('modification', "Style to use for write accesses."), undefined); - tokenClassificationRegistry.registerTokenModifier('async', nls.localize('async', "Style to use for symbols that are async."), undefined); - tokenClassificationRegistry.registerTokenModifier('readonly', nls.localize('readonly', "Style to use for symbols that are readonly."), undefined); + registry.registerTokenModifier('declaration', nls.localize('declaration', "Style for all symbol declarations."), undefined); + registry.registerTokenModifier('documentation', nls.localize('documentation', "Style to use for references in documentation."), undefined); + registry.registerTokenModifier('static', nls.localize('static', "Style to use for symbols that are static."), undefined); + registry.registerTokenModifier('abstract', nls.localize('abstract', "Style to use for symbols that are abstract."), undefined); + registry.registerTokenModifier('deprecated', nls.localize('deprecated', "Style to use for symbols that are deprecated."), undefined); + registry.registerTokenModifier('modification', nls.localize('modification', "Style to use for write accesses."), undefined); + registry.registerTokenModifier('async', nls.localize('async', "Style to use for symbols that are async."), undefined); + registry.registerTokenModifier('readonly', nls.localize('readonly', "Style to use for symbols that are readonly."), undefined); registerTokenStyleDefault('variable.readonly', [['variable.other.constant']]); + registerTokenStyleDefault('property.readonly', [['variable.other.constant.property']]); + registerTokenStyleDefault('type.defaultLibrary', [['support.type']]); + registerTokenStyleDefault('class.defaultLibrary', [['support.class']]); + registerTokenStyleDefault('interface.defaultLibrary', [['support.class']]); + registerTokenStyleDefault('variable.defaultLibrary', [['support.variable'], ['support.other.variable']]); + registerTokenStyleDefault('variable.defaultLibrary.readonly', [['support.constant']]); + registerTokenStyleDefault('property.defaultLibrary', [['support.variable.property']]); + registerTokenStyleDefault('property.defaultLibrary.readonly', [['support.constant.property']]); + registerTokenStyleDefault('function.defaultLibrary', [['support.function']]); + registerTokenStyleDefault('member.defaultLibrary', [['support.function']]); + return registry; } export function getTokenClassificationRegistry(): ITokenClassificationRegistry { diff --git a/src/vs/platform/theme/test/common/testThemeService.ts b/src/vs/platform/theme/test/common/testThemeService.ts index cb907d30757..82dbe518768 100644 --- a/src/vs/platform/theme/test/common/testThemeService.ts +++ b/src/vs/platform/theme/test/common/testThemeService.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; -import { IThemeService, ITheme, DARK, IIconTheme, ITokenStyle } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme, DARK, IFileIconTheme, ITokenStyle } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; -export class TestTheme implements ITheme { +export class TestColorTheme implements IColorTheme { constructor(private colors: { [id: string]: string; } = {}, public type = DARK) { } @@ -24,16 +24,18 @@ export class TestTheme implements ITheme { throw new Error('Method not implemented.'); } - getTokenStyleMetadata(type: string, modifiers: string[]): ITokenStyle | undefined { + getTokenStyleMetadata(type: string, modifiers: string[], modelLanguage: string): ITokenStyle | undefined { return undefined; } + readonly semanticHighlighting = false; + get tokenColorMap(): string[] { return []; } } -export class TestIconTheme implements IIconTheme { +export class TestFileIconTheme implements IFileIconTheme { hasFileIcons = false; hasFolderIcons = false; hidesExplorerArrows = false; @@ -42,38 +44,38 @@ export class TestIconTheme implements IIconTheme { export class TestThemeService implements IThemeService { _serviceBrand: undefined; - _theme: ITheme; - _iconTheme: IIconTheme; - _onThemeChange = new Emitter(); - _onIconThemeChange = new Emitter(); + _colorTheme: IColorTheme; + _fileIconTheme: IFileIconTheme; + _onThemeChange = new Emitter(); + _onFileIconThemeChange = new Emitter(); - constructor(theme = new TestTheme(), iconTheme = new TestIconTheme()) { - this._theme = theme; - this._iconTheme = iconTheme; + constructor(theme = new TestColorTheme(), iconTheme = new TestFileIconTheme()) { + this._colorTheme = theme; + this._fileIconTheme = iconTheme; } - getTheme(): ITheme { - return this._theme; + getColorTheme(): IColorTheme { + return this._colorTheme; } - setTheme(theme: ITheme) { - this._theme = theme; + setTheme(theme: IColorTheme) { + this._colorTheme = theme; this.fireThemeChange(); } fireThemeChange() { - this._onThemeChange.fire(this._theme); + this._onThemeChange.fire(this._colorTheme); } - public get onThemeChange(): Event { + public get onDidColorThemeChange(): Event { return this._onThemeChange.event; } - getIconTheme(): IIconTheme { - return this._iconTheme; + getFileIconTheme(): IFileIconTheme { + return this._fileIconTheme; } - public get onIconThemeChange(): Event { - return this._onIconThemeChange.event; + public get onDidFileIconThemeChange(): Event { + return this._onFileIconThemeChange.event; } } diff --git a/src/vs/platform/undoRedo/common/undoRedo.ts b/src/vs/platform/undoRedo/common/undoRedo.ts index b8fef5dd2f6..935e3ffcb22 100644 --- a/src/vs/platform/undoRedo/common/undoRedo.ts +++ b/src/vs/platform/undoRedo/common/undoRedo.ts @@ -30,6 +30,13 @@ export interface IWorkspaceUndoRedoElement { split(): IResourceUndoRedoElement[]; } +export type IUndoRedoElement = IResourceUndoRedoElement | IWorkspaceUndoRedoElement; + +export interface IPastFutureElements { + past: IUndoRedoElement[]; + future: IUndoRedoElement[]; +} + export interface IUndoRedoService { _serviceBrand: undefined; @@ -37,12 +44,18 @@ export interface IUndoRedoService { * Add a new element to the `undo` stack. * This will destroy the `redo` stack. */ - pushElement(element: IResourceUndoRedoElement | IWorkspaceUndoRedoElement): void; + pushElement(element: IUndoRedoElement): void; /** * Get the last pushed element. If the last pushed element has been undone, returns null. */ - getLastElement(resource: URI): IResourceUndoRedoElement | IWorkspaceUndoRedoElement | null; + getLastElement(resource: URI): IUndoRedoElement | null; + + getElements(resource: URI): IPastFutureElements; + + hasElements(resource: URI): boolean; + + setElementsIsValid(resource: URI, isValid: boolean): void; /** * Remove elements that target `resource`. diff --git a/src/vs/platform/undoRedo/common/undoRedoService.ts b/src/vs/platform/undoRedo/common/undoRedoService.ts index 8568767aa3d..9858c9eb6af 100644 --- a/src/vs/platform/undoRedo/common/undoRedoService.ts +++ b/src/vs/platform/undoRedo/common/undoRedoService.ts @@ -4,9 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { IUndoRedoService, IResourceUndoRedoElement, IWorkspaceUndoRedoElement, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo'; +import { IUndoRedoService, IResourceUndoRedoElement, IWorkspaceUndoRedoElement, UndoRedoElementType, IUndoRedoElement, IPastFutureElements } from 'vs/platform/undoRedo/common/undoRedo'; import { URI } from 'vs/base/common/uri'; -import { getComparisonKey as uriGetComparisonKey } from 'vs/base/common/resources'; import { onUnexpectedError } from 'vs/base/common/errors'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; @@ -14,6 +13,10 @@ import Severity from 'vs/base/common/severity'; import { Schemas } from 'vs/base/common/network'; import { INotificationService } from 'vs/platform/notification/common/notification'; +function uriGetComparisonKey(resource: URI): string { + return resource.toString(); +} + class ResourceStackElement { public readonly type = UndoRedoElementType.Resource; public readonly actual: IResourceUndoRedoElement; @@ -23,6 +26,7 @@ class ResourceStackElement { public readonly strResource: string; public readonly resources: URI[]; public readonly strResources: string[]; + public isValid: boolean; constructor(actual: IResourceUndoRedoElement) { this.actual = actual; @@ -31,6 +35,11 @@ class ResourceStackElement { this.strResource = uriGetComparisonKey(this.resource); this.resources = [this.resource]; this.strResources = [this.strResource]; + this.isValid = true; + } + + public setValid(isValid: boolean): void { + this.isValid = isValid; } } @@ -39,22 +48,57 @@ const enum RemovedResourceReason { NoParallelUniverses = 1 } +class ResourceReasonPair { + constructor( + public readonly resource: URI, + public readonly reason: RemovedResourceReason + ) { } +} + class RemovedResources { - public readonly set: Set = new Set(); - public readonly reason: [URI[], URI[]] = [[], []]; + private readonly elements = new Map(); + + private _getPath(resource: URI): string { + return resource.scheme === Schemas.file ? resource.fsPath : resource.path; + } public createMessage(): string { - let messages: string[] = []; - if (this.reason[RemovedResourceReason.ExternalRemoval].length > 0) { - const paths = this.reason[RemovedResourceReason.ExternalRemoval].map(uri => uri.scheme === Schemas.file ? uri.fsPath : uri.path); - messages.push(nls.localize('externalRemoval', "The following files have been closed: {0}.", paths.join(', '))); + const externalRemoval: string[] = []; + const noParallelUniverses: string[] = []; + for (const [, element] of this.elements) { + const dest = ( + element.reason === RemovedResourceReason.ExternalRemoval + ? externalRemoval + : noParallelUniverses + ); + dest.push(this._getPath(element.resource)); } - if (this.reason[RemovedResourceReason.NoParallelUniverses].length > 0) { - const paths = this.reason[RemovedResourceReason.NoParallelUniverses].map(uri => uri.scheme === Schemas.file ? uri.fsPath : uri.path); - messages.push(nls.localize('noParallelUniverses', "The following files have been modified in an incompatible way: {0}.", paths.join(', '))); + + let messages: string[] = []; + if (externalRemoval.length > 0) { + messages.push(nls.localize('externalRemoval', "The following files have been closed: {0}.", externalRemoval.join(', '))); + } + if (noParallelUniverses.length > 0) { + messages.push(nls.localize('noParallelUniverses', "The following files have been modified in an incompatible way: {0}.", noParallelUniverses.join(', '))); } return messages.join('\n'); } + + public get size(): number { + return this.elements.size; + } + + public has(strResource: string): boolean { + return this.elements.has(strResource); + } + + public set(strResource: string, value: ResourceReasonPair): void { + this.elements.set(strResource, value); + } + + public delete(strResource: string): boolean { + return this.elements.delete(strResource); + } } class WorkspaceStackElement { @@ -65,6 +109,7 @@ class WorkspaceStackElement { public readonly resources: URI[]; public readonly strResources: string[]; public removedResources: RemovedResources | null; + public invalidatedResources: RemovedResources | null; constructor(actual: IWorkspaceUndoRedoElement) { this.actual = actual; @@ -72,18 +117,37 @@ class WorkspaceStackElement { this.resources = actual.resources.slice(0); this.strResources = this.resources.map(resource => uriGetComparisonKey(resource)); this.removedResources = null; + this.invalidatedResources = null; } public removeResource(resource: URI, strResource: string, reason: RemovedResourceReason): void { if (!this.removedResources) { this.removedResources = new RemovedResources(); } - if (!this.removedResources.set.has(strResource)) { - this.removedResources.set.add(strResource); - this.removedResources.reason[reason].push(resource); + if (!this.removedResources.has(strResource)) { + this.removedResources.set(strResource, new ResourceReasonPair(resource, reason)); + } + } + + public setValid(resource: URI, strResource: string, isValid: boolean): void { + if (isValid) { + if (this.invalidatedResources) { + this.invalidatedResources.delete(strResource); + if (this.invalidatedResources.size === 0) { + this.invalidatedResources = null; + } + } + } else { + if (!this.invalidatedResources) { + this.invalidatedResources = new RemovedResources(); + } + if (!this.invalidatedResources.has(strResource)) { + this.invalidatedResources.set(strResource, new ResourceReasonPair(resource, RemovedResourceReason.ExternalRemoval)); + } } } } + type StackElement = ResourceStackElement | WorkspaceStackElement; class ResourceEditStack { @@ -110,7 +174,7 @@ export class UndoRedoService implements IUndoRedoService { this._editStacks = new Map(); } - public pushElement(_element: IResourceUndoRedoElement | IWorkspaceUndoRedoElement): void { + public pushElement(_element: IUndoRedoElement): void { const element: StackElement = (_element.type === UndoRedoElementType.Resource ? new ResourceStackElement(_element) : new WorkspaceStackElement(_element)); for (let i = 0, len = element.resources.length; i < len; i++) { const resource = element.resources[i]; @@ -131,11 +195,18 @@ export class UndoRedoService implements IUndoRedoService { } } editStack.future = []; + if (editStack.past.length > 0) { + const lastElement = editStack.past[editStack.past.length - 1]; + if (lastElement.type === UndoRedoElementType.Resource && !lastElement.isValid) { + // clear undo stack + editStack.past = []; + } + } editStack.past.push(element); } } - public getLastElement(resource: URI): IResourceUndoRedoElement | IWorkspaceUndoRedoElement | null { + public getLastElement(resource: URI): IUndoRedoElement | null { const strResource = uriGetComparisonKey(resource); if (this._editStacks.has(strResource)) { const editStack = this._editStacks.get(strResource)!; @@ -150,7 +221,7 @@ export class UndoRedoService implements IUndoRedoService { return null; } - private _splitPastWorkspaceElement(toRemove: WorkspaceStackElement, ignoreResources: Set | null): void { + private _splitPastWorkspaceElement(toRemove: WorkspaceStackElement, ignoreResources: RemovedResources | null): void { const individualArr = toRemove.actual.split(); const individualMap = new Map(); for (const _element of individualArr) { @@ -178,7 +249,7 @@ export class UndoRedoService implements IUndoRedoService { } } - private _splitFutureWorkspaceElement(toRemove: WorkspaceStackElement, ignoreResources: Set | null): void { + private _splitFutureWorkspaceElement(toRemove: WorkspaceStackElement, ignoreResources: RemovedResources | null): void { const individualArr = toRemove.actual.split(); const individualMap = new Map(); for (const _element of individualArr) { @@ -224,6 +295,56 @@ export class UndoRedoService implements IUndoRedoService { } } + public setElementsIsValid(resource: URI, isValid: boolean): void { + const strResource = uriGetComparisonKey(resource); + if (this._editStacks.has(strResource)) { + const editStack = this._editStacks.get(strResource)!; + for (const element of editStack.past) { + if (element.type === UndoRedoElementType.Workspace) { + element.setValid(resource, strResource, isValid); + } else { + element.setValid(isValid); + } + } + for (const element of editStack.future) { + if (element.type === UndoRedoElementType.Workspace) { + element.setValid(resource, strResource, isValid); + } else { + element.setValid(isValid); + } + } + } + } + + // resource + + public hasElements(resource: URI): boolean { + const strResource = uriGetComparisonKey(resource); + if (this._editStacks.has(strResource)) { + const editStack = this._editStacks.get(strResource)!; + return (editStack.past.length > 0 || editStack.future.length > 0); + } + return false; + } + + public getElements(resource: URI): IPastFutureElements { + const past: IUndoRedoElement[] = []; + const future: IUndoRedoElement[] = []; + + const strResource = uriGetComparisonKey(resource); + if (this._editStacks.has(strResource)) { + const editStack = this._editStacks.get(strResource)!; + for (const element of editStack.past) { + past.push(element.actual); + } + for (const element of editStack.future) { + future.push(element.actual); + } + } + + return { past, future }; + } + public canUndo(resource: URI): boolean { const strResource = uriGetComparisonKey(resource); if (this._editStacks.has(strResource)) { @@ -257,11 +378,17 @@ export class UndoRedoService implements IUndoRedoService { private _workspaceUndo(resource: URI, element: WorkspaceStackElement): Promise | void { if (element.removedResources) { - this._splitPastWorkspaceElement(element, element.removedResources.set); + this._splitPastWorkspaceElement(element, element.removedResources); const message = nls.localize('cannotWorkspaceUndo', "Could not undo '{0}' across all files. {1}", element.label, element.removedResources.createMessage()); this._notificationService.info(message); return this.undo(resource); } + if (element.invalidatedResources) { + this._splitPastWorkspaceElement(element, element.invalidatedResources); + const message = nls.localize('cannotWorkspaceUndo', "Could not undo '{0}' across all files. {1}", element.label, element.invalidatedResources.createMessage()); + this._notificationService.info(message); + return this.undo(resource); + } // this must be the last past element in all the impacted resources! let affectedEditStacks: ResourceEditStack[] = []; @@ -288,8 +415,8 @@ export class UndoRedoService implements IUndoRedoService { Severity.Info, nls.localize('confirmWorkspace', "Would you like to undo '{0}' across all files?", element.label), [ - nls.localize('ok', "Undo In {0} Files", affectedEditStacks.length), - nls.localize('nok', "Undo This File"), + nls.localize('ok', "Undo in {0} Files", affectedEditStacks.length), + nls.localize('nok', "Undo this File"), nls.localize('cancel', "Cancel"), ], { @@ -313,6 +440,12 @@ export class UndoRedoService implements IUndoRedoService { } private _resourceUndo(editStack: ResourceEditStack, element: ResourceStackElement): Promise | void { + if (!element.isValid) { + // invalid element => immediately flush edit stack! + editStack.past = []; + editStack.future = []; + return; + } editStack.past.pop(); editStack.future.push(element); return this._safeInvoke(element, () => element.actual.undo()); @@ -348,11 +481,17 @@ export class UndoRedoService implements IUndoRedoService { private _workspaceRedo(resource: URI, element: WorkspaceStackElement): Promise | void { if (element.removedResources) { - this._splitFutureWorkspaceElement(element, element.removedResources.set); + this._splitFutureWorkspaceElement(element, element.removedResources); const message = nls.localize('cannotWorkspaceRedo', "Could not redo '{0}' across all files. {1}", element.label, element.removedResources.createMessage()); this._notificationService.info(message); return this.redo(resource); } + if (element.invalidatedResources) { + this._splitFutureWorkspaceElement(element, element.invalidatedResources); + const message = nls.localize('cannotWorkspaceRedo', "Could not redo '{0}' across all files. {1}", element.label, element.invalidatedResources.createMessage()); + this._notificationService.info(message); + return this.redo(resource); + } // this must be the last future element in all the impacted resources! let affectedEditStacks: ResourceEditStack[] = []; @@ -383,6 +522,12 @@ export class UndoRedoService implements IUndoRedoService { } private _resourceRedo(editStack: ResourceEditStack, element: ResourceStackElement): Promise | void { + if (!element.isValid) { + // invalid element => immediately flush edit stack! + editStack.past = []; + editStack.future = []; + return; + } editStack.future.pop(); editStack.past.push(element); return this._safeInvoke(element, () => element.actual.redo()); diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index 58ea47ff8bc..fd38c3bac80 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -4,14 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; -import { IFileService, IFileContent, FileChangesEvent, FileSystemProviderError, FileSystemProviderErrorCode, FileOperationResult, FileOperationError } from 'vs/platform/files/common/files'; +import { IFileService, IFileContent, FileChangesEvent, FileOperationResult, FileOperationError } from 'vs/platform/files/common/files'; import { VSBuffer } from 'vs/base/common/buffer'; import { URI } from 'vs/base/common/uri'; -import { SyncSource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncResource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, Conflict, ISyncResourceHandle, USER_DATA_SYNC_SCHEME, ISyncPreviewResult } from 'vs/platform/userDataSync/common/userDataSync'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { joinPath, dirname } from 'vs/base/common/resources'; -import { toLocalISOString } from 'vs/base/common/date'; -import { ThrottledDelayer, CancelablePromise } from 'vs/base/common/async'; +import { joinPath, dirname, isEqual, basename } from 'vs/base/common/resources'; +import { CancelablePromise } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ParseError, parse } from 'vs/base/common/json'; @@ -19,6 +18,9 @@ import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { IStringDictionary } from 'vs/base/common/collections'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { isString } from 'vs/base/common/types'; +import { uppercaseFirstLetter } from 'vs/base/common/strings'; +import { equals } from 'vs/base/common/arrays'; type SyncSourceClassification = { source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; @@ -44,33 +46,38 @@ function isSyncData(thing: any): thing is ISyncData { export abstract class AbstractSynchroniser extends Disposable { protected readonly syncFolder: URI; - private cleanUpDelayer: ThrottledDelayer; private _status: SyncStatus = SyncStatus.Idle; get status(): SyncStatus { return this._status; } private _onDidChangStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangStatus.event; + private _conflicts: Conflict[] = []; + get conflicts(): Conflict[] { return this._conflicts; } + private _onDidChangeConflicts: Emitter = this._register(new Emitter()); + readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; + protected readonly _onDidChangeLocal: Emitter = this._register(new Emitter()); readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; protected readonly lastSyncResource: URI; + protected readonly syncResourceLogLabel: string; constructor( - readonly source: SyncSource, + readonly resource: SyncResource, @IFileService protected readonly fileService: IFileService, @IEnvironmentService environmentService: IEnvironmentService, @IUserDataSyncStoreService protected readonly userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncBackupStoreService protected readonly userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, @IUserDataSyncEnablementService protected readonly userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IUserDataSyncLogService protected readonly logService: IUserDataSyncLogService, @IConfigurationService protected readonly configurationService: IConfigurationService, ) { super(); - this.syncFolder = joinPath(environmentService.userDataSyncHome, source); - this.lastSyncResource = joinPath(this.syncFolder, `lastSync${source}.json`); - this.cleanUpDelayer = new ThrottledDelayer(50); - this.cleanUpBackup(); + this.syncResourceLogLabel = uppercaseFirstLetter(this.resource); + this.syncFolder = joinPath(environmentService.userDataSyncHome, resource); + this.lastSyncResource = joinPath(this.syncFolder, `lastSync${this.resource}.json`); } protected setStatus(status: SyncStatus): void { @@ -80,52 +87,91 @@ export abstract class AbstractSynchroniser extends Disposable { this._onDidChangStatus.fire(status); if (status === SyncStatus.HasConflicts) { // Log to telemetry when there is a sync conflict - this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/conflictsDetected', { source: this.source }); + this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/conflictsDetected', { source: this.resource }); } if (oldStatus === SyncStatus.HasConflicts && status === SyncStatus.Idle) { // Log to telemetry when conflicts are resolved - this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/conflictsResolved', { source: this.source }); + this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/conflictsResolved', { source: this.resource }); + } + if (this.status !== SyncStatus.HasConflicts) { + this.setConflicts([]); } } } - protected get enabled(): boolean { return this.userDataSyncEnablementService.isResourceEnabled(this.resourceKey); } + protected setConflicts(conflicts: Conflict[]) { + if (!equals(this._conflicts, conflicts, (a, b) => isEqual(a.local, b.local) && isEqual(a.remote, b.remote))) { + this._conflicts = conflicts; + this._onDidChangeConflicts.fire(this._conflicts); + } + } - async sync(ref?: string, donotUseLastSyncUserData?: boolean): Promise { - if (!this.enabled) { - this.logService.info(`${this.source}: Skipped synchronizing ${this.source.toLowerCase()} as it is disabled.`); + protected isEnabled(): boolean { return this.userDataSyncEnablementService.isResourceEnabled(this.resource); } + + async sync(ref?: string): Promise { + if (!this.isEnabled()) { + if (this.status !== SyncStatus.Idle) { + await this.stop(); + } + this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing ${this.resource.toLowerCase()} as it is disabled.`); return; } if (this.status === SyncStatus.HasConflicts) { - this.logService.info(`${this.source}: Skipped synchronizing ${this.source.toLowerCase()} as there are conflicts.`); + this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing ${this.resource.toLowerCase()} as there are conflicts.`); return; } if (this.status === SyncStatus.Syncing) { - this.logService.info(`${this.source}: Skipped synchronizing ${this.source.toLowerCase()} as it is running already.`); + this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing ${this.resource.toLowerCase()} as it is running already.`); return; } - this.logService.trace(`${this.source}: Started synchronizing ${this.source.toLowerCase()}...`); + this.logService.trace(`${this.syncResourceLogLabel}: Started synchronizing ${this.resource.toLowerCase()}...`); this.setStatus(SyncStatus.Syncing); - const lastSyncUserData = donotUseLastSyncUserData ? null : await this.getLastSyncUserData(); + const lastSyncUserData = await this.getLastSyncUserData(); const remoteUserData = ref && lastSyncUserData && lastSyncUserData.ref === ref ? lastSyncUserData : await this.getRemoteUserData(lastSyncUserData); - if (remoteUserData.syncData && remoteUserData.syncData.version > this.version) { - // current version is not compatible with cloud version - this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/incompatible', { source: this.source }); - throw new UserDataSyncError(localize('incompatible', "Cannot sync {0} as its version {1} is not compatible with cloud {2}", this.source, this.version, remoteUserData.syncData.version), UserDataSyncErrorCode.Incompatible, this.source); + let status: SyncStatus = SyncStatus.Idle; + try { + status = await this.doSync(remoteUserData, lastSyncUserData); + if (status === SyncStatus.HasConflicts) { + this.logService.info(`${this.syncResourceLogLabel}: Detected conflicts while synchronizing ${this.resource.toLowerCase()}.`); + } else if (status === SyncStatus.Idle) { + this.logService.trace(`${this.syncResourceLogLabel}: Finished synchronizing ${this.resource.toLowerCase()}.`); + } + } finally { + this.setStatus(status); + } + } + + async getSyncPreview(): Promise { + if (!this.isEnabled()) { + return { hasLocalChanged: false, hasRemoteChanged: false }; } + const lastSyncUserData = await this.getLastSyncUserData(); + const remoteUserData = await this.getRemoteUserData(lastSyncUserData); + return this.generatePreview(remoteUserData, lastSyncUserData); + } + + protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { + if (remoteUserData.syncData && remoteUserData.syncData.version > this.version) { + // current version is not compatible with cloud version + this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/incompatible', { source: this.resource }); + throw new UserDataSyncError(localize('incompatible', "Cannot sync {0} as its version {1} is not compatible with cloud {2}", this.resource, this.version, remoteUserData.syncData.version), UserDataSyncErrorCode.Incompatible, this.resource); + } try { - await this.doSync(remoteUserData, lastSyncUserData); + const status = await this.performSync(remoteUserData, lastSyncUserData); + return status; } catch (e) { if (e instanceof UserDataSyncError) { switch (e.code) { case UserDataSyncErrorCode.RemotePreconditionFailed: // Rejected as there is a new remote version. Syncing again, - this.logService.info(`${this.source}: Failed to synchronize as there is a new remote version available. Synchronizing again...`); - return this.sync(undefined, true); + this.logService.info(`${this.syncResourceLogLabel}: Failed to synchronize as there is a new remote version available. Synchronizing again...`); + // Avoid cache and get latest remote user data - https://github.com/microsoft/vscode/issues/90624 + remoteUserData = await this.getRemoteUserData(null); + return this.doSync(remoteUserData, lastSyncUserData); } } throw e; @@ -137,10 +183,34 @@ export abstract class AbstractSynchroniser extends Disposable { return !!lastSyncData; } - async getRemoteContent(): Promise { - const lastSyncData = await this.getLastSyncUserData(); - const { syncData } = await this.getRemoteUserData(lastSyncData); - return syncData ? syncData.content : null; + async getRemoteSyncResourceHandles(): Promise { + const handles = await this.userDataSyncStoreService.getAllRefs(this.resource); + return handles.map(({ created, ref }) => ({ created, uri: this.toRemoteBackupResource(ref) })); + } + + async getLocalSyncResourceHandles(): Promise { + const handles = await this.userDataSyncBackupStoreService.getAllRefs(this.resource); + return handles.map(({ created, ref }) => ({ created, uri: this.toLocalBackupResource(ref) })); + } + + private toRemoteBackupResource(ref: string): URI { + return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote-backup', path: `/${this.resource}/${ref}` }); + } + + private toLocalBackupResource(ref: string): URI { + return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local-backup', path: `/${this.resource}/${ref}` }); + } + + async resolveContent(uri: URI): Promise { + const ref = basename(uri); + if (isEqual(uri, this.toRemoteBackupResource(ref))) { + const { content } = await this.getUserData(ref); + return content; + } + if (isEqual(uri, this.toLocalBackupResource(ref))) { + return this.userDataSyncBackupStoreService.resolveContent(this.resource, ref); + } + return null; } async resetLocal(): Promise { @@ -176,87 +246,63 @@ export abstract class AbstractSynchroniser extends Disposable { } protected async getRemoteUserData(lastSyncData: IRemoteUserData | null): Promise { - const lastSyncUserData: IUserData | null = lastSyncData ? { ref: lastSyncData.ref, content: lastSyncData.syncData ? JSON.stringify(lastSyncData.syncData) : null } : null; - const { ref, content } = await this.userDataSyncStoreService.read(this.resourceKey, lastSyncUserData, this.source); + const { ref, content } = await this.getUserData(lastSyncData); let syncData: ISyncData | null = null; if (content !== null) { - try { - syncData = JSON.parse(content); - - // Migration from old content to sync data - if (!isSyncData(syncData)) { - syncData = { version: this.version, content }; - } - - } catch (e) { - this.logService.error(e); - } + syncData = this.parseSyncData(content); } return { ref, syncData }; } + protected parseSyncData(content: string): ISyncData | null { + let syncData: ISyncData | null = null; + try { + syncData = JSON.parse(content); + + // Migration from old content to sync data + if (!isSyncData(syncData)) { + syncData = { version: this.version, content }; + } + + } catch (e) { + this.logService.error(e); + } + return syncData; + } + + private async getUserData(refOrLastSyncData: string | IRemoteUserData | null): Promise { + if (isString(refOrLastSyncData)) { + const content = await this.userDataSyncStoreService.resolveContent(this.resource, refOrLastSyncData); + return { ref: refOrLastSyncData, content }; + } else { + const lastSyncUserData: IUserData | null = refOrLastSyncData ? { ref: refOrLastSyncData.ref, content: refOrLastSyncData.syncData ? JSON.stringify(refOrLastSyncData.syncData) : null } : null; + return this.userDataSyncStoreService.read(this.resource, lastSyncUserData); + } + } + protected async updateRemoteUserData(content: string, ref: string | null): Promise { const syncData: ISyncData = { version: this.version, content }; - ref = await this.userDataSyncStoreService.write(this.resourceKey, JSON.stringify(syncData), ref, this.source); + ref = await this.userDataSyncStoreService.write(this.resource, JSON.stringify(syncData), ref); return { ref, syncData }; } - protected async backupLocal(content: VSBuffer): Promise { - const resource = joinPath(this.syncFolder, `${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}.json`); - try { - await this.fileService.writeFile(resource, content); - } catch (e) { - this.logService.error(e); - } - this.cleanUpDelayer.trigger(() => this.cleanUpBackup()); + protected async backupLocal(content: string): Promise { + const syncData: ISyncData = { version: this.version, content }; + return this.userDataSyncBackupStoreService.backup(this.resource, JSON.stringify(syncData)); } - private async cleanUpBackup(): Promise { - try { - if (!(await this.fileService.exists(this.syncFolder))) { - return; - } - const stat = await this.fileService.resolve(this.syncFolder); - if (stat.children) { - const all = stat.children.filter(stat => stat.isFile && /^\d{8}T\d{6}(\.json)?$/.test(stat.name)).sort(); - const backUpMaxAge = 1000 * 60 * 60 * 24 * (this.configurationService.getValue('sync.localBackupDuration') || 30 /* Default 30 days */); - let toDelete = all.filter(stat => { - const ctime = stat.ctime || new Date( - parseInt(stat.name.substring(0, 4)), - parseInt(stat.name.substring(4, 6)) - 1, - parseInt(stat.name.substring(6, 8)), - parseInt(stat.name.substring(9, 11)), - parseInt(stat.name.substring(11, 13)), - parseInt(stat.name.substring(13, 15)) - ).getTime(); - return Date.now() - ctime > backUpMaxAge; - }); - const remaining = all.length - toDelete.length; - if (remaining < 10) { - toDelete = toDelete.slice(10 - remaining); - } - await Promise.all(toDelete.map(stat => { - this.logService.info('Deleting from backup', stat.resource.path); - this.fileService.del(stat.resource); - })); - } - } catch (e) { - this.logService.error(e); - } - } + abstract stop(): Promise; - abstract readonly resourceKey: ResourceKey; protected abstract readonly version: number; - protected abstract doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise; + protected abstract performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise; + protected abstract generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise; } -export interface IFileSyncPreviewResult { +export interface IFileSyncPreviewResult extends ISyncPreviewResult { readonly fileContent: IFileContent | null; readonly remoteUserData: IRemoteUserData; readonly lastSyncUserData: IRemoteUserData | null; readonly content: string | null; - readonly hasLocalChanged: boolean; - readonly hasRemoteChanged: boolean; readonly hasConflicts: boolean; } @@ -266,37 +312,43 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { constructor( protected readonly file: URI, - source: SyncSource, + resource: SyncResource, @IFileService fileService: IFileService, @IEnvironmentService environmentService: IEnvironmentService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IConfigurationService configurationService: IConfigurationService, ) { - super(source, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); + super(resource, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); this._register(this.fileService.watch(dirname(file))); this._register(this.fileService.onDidFilesChange(e => this.onFileChanges(e))); } async stop(): Promise { this.cancel(); - this.logService.trace(`${this.source}: Stopped synchronizing ${this.source.toLowerCase()}.`); + this.logService.info(`${this.syncResourceLogLabel}: Stopped synchronizing ${this.resource.toLowerCase()}.`); try { - await this.fileService.del(this.conflictsPreviewResource); + await this.fileService.del(this.localPreviewResource); } catch (e) { /* ignore */ } this.setStatus(SyncStatus.Idle); } - async getRemoteContent(preview?: boolean): Promise { - if (preview) { + protected async getConflictContent(conflictResource: URI): Promise { + if (isEqual(this.remotePreviewResource, conflictResource) || isEqual(this.localPreviewResource, conflictResource)) { if (this.syncPreviewResultPromise) { const result = await this.syncPreviewResultPromise; - return result.remoteUserData && result.remoteUserData.syncData ? result.remoteUserData.syncData.content : null; + if (isEqual(this.remotePreviewResource, conflictResource)) { + return result.remoteUserData && result.remoteUserData.syncData ? result.remoteUserData.syncData.content : null; + } + if (isEqual(this.localPreviewResource, conflictResource)) { + return result.fileContent ? result.fileContent.value.toString() : null; + } } } - return super.getRemoteContent(); + return null; } protected async getLocalFileContent(): Promise { @@ -311,14 +363,13 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { try { if (oldContent) { // file exists already - await this.backupLocal(oldContent.value); await this.fileService.writeFile(this.file, VSBuffer.fromString(newContent), oldContent); } else { // file does not exist await this.fileService.createFile(this.file, VSBuffer.fromString(newContent), { overwrite: false }); } } catch (e) { - if ((e instanceof FileSystemProviderError && e.code === FileSystemProviderErrorCode.FileExists) || + if ((e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_NOT_FOUND) || (e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE)) { throw new UserDataSyncError(e.message, UserDataSyncErrorCode.LocalPreconditionFailed); } else { @@ -332,7 +383,7 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { return; } - if (!this.enabled) { + if (!this.isEnabled()) { return; } @@ -340,7 +391,7 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { if (this.status === SyncStatus.HasConflicts) { this.syncPreviewResultPromise?.then(result => { this.cancel(); - this.doSync(result.remoteUserData, result.lastSyncUserData); + this.doSync(result.remoteUserData, result.lastSyncUserData).then(status => this.setStatus(status)); }); } @@ -358,24 +409,26 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { } } - protected abstract readonly conflictsPreviewResource: URI; + protected abstract readonly localPreviewResource: URI; + protected abstract readonly remotePreviewResource: URI; } export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroniser { constructor( file: URI, - source: SyncSource, + resource: SyncResource, @IFileService fileService: IFileService, @IEnvironmentService environmentService: IEnvironmentService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IUserDataSyncUtilService protected readonly userDataSyncUtilService: IUserDataSyncUtilService, @IConfigurationService configurationService: IConfigurationService, ) { - super(file, source, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); + super(file, resource, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); } protected hasErrors(content: string): boolean { diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index ccdf3c48517..33901ed48ce 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, ISyncResourceHandle, ISyncPreviewResult } from 'vs/platform/userDataSync/common/userDataSync'; import { Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -11,14 +11,16 @@ import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/comm import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IFileService } from 'vs/platform/files/common/files'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { localize } from 'vs/nls'; import { merge } from 'vs/platform/userDataSync/common/extensionsMerge'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { AbstractSynchroniser, IRemoteUserData, ISyncData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { VSBuffer } from 'vs/base/common/buffer'; +import { URI } from 'vs/base/common/uri'; +import { joinPath, dirname, basename } from 'vs/base/common/resources'; +import { format } from 'vs/base/common/jsonFormatter'; +import { applyEdits } from 'vs/base/common/jsonEdit'; -interface ISyncPreviewResult { +interface IExtensionsSyncPreviewResult extends ISyncPreviewResult { readonly localExtensions: ISyncExtension[]; readonly remoteUserData: IRemoteUserData; readonly lastSyncUserData: ILastSyncUserData | null; @@ -35,13 +37,14 @@ interface ILastSyncUserData extends IRemoteUserData { export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { - readonly resourceKey: ResourceKey = 'extensions'; protected readonly version: number = 2; + protected isEnabled(): boolean { return super.isEnabled() && this.extensionGalleryService.isEnabled(); } constructor( @IEnvironmentService environmentService: IEnvironmentService, @IFileService fileService: IFileService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @@ -50,7 +53,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, ) { - super(SyncSource.Extensions, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); + super(SyncResource.Extensions, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); this._register( Event.debounce( Event.any( @@ -61,15 +64,15 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } async pull(): Promise { - if (!this.enabled) { - this.logService.info('Extensions: Skipped pulling extensions as it is disabled.'); + if (!this.isEnabled()) { + this.logService.info(`${this.syncResourceLogLabel}: Skipped pulling extensions as it is disabled.`); return; } this.stop(); try { - this.logService.info('Extensions: Started pulling extensions...'); + this.logService.info(`${this.syncResourceLogLabel}: Started pulling extensions...`); this.setStatus(SyncStatus.Syncing); const lastSyncUserData = await this.getLastSyncUserData(); @@ -79,57 +82,80 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const localExtensions = await this.getLocalExtensions(); const remoteExtensions = this.parseExtensions(remoteUserData.syncData); const { added, updated, remote, removed } = merge(localExtensions, remoteExtensions, localExtensions, [], this.getIgnoredExtensions()); - await this.apply({ added, removed, updated, remote, remoteUserData, localExtensions, skippedExtensions: [], lastSyncUserData }); + await this.apply({ + added, removed, updated, remote, remoteUserData, localExtensions, skippedExtensions: [], lastSyncUserData, + hasLocalChanged: added.length > 0 || removed.length > 0 || updated.length > 0, + hasRemoteChanged: remote !== null + }); } // No remote exists to pull else { - this.logService.info('Extensions: Remote extensions does not exist.'); + this.logService.info(`${this.syncResourceLogLabel}: Remote extensions does not exist.`); } - this.logService.info('Extensions: Finished pulling extensions.'); + this.logService.info(`${this.syncResourceLogLabel}: Finished pulling extensions.`); } finally { this.setStatus(SyncStatus.Idle); } } async push(): Promise { - if (!this.enabled) { - this.logService.info('Extensions: Skipped pushing extensions as it is disabled.'); + if (!this.isEnabled()) { + this.logService.info(`${this.syncResourceLogLabel}: Skipped pushing extensions as it is disabled.`); return; } this.stop(); try { - this.logService.info('Extensions: Started pushing extensions...'); + this.logService.info(`${this.syncResourceLogLabel}: Started pushing extensions...`); this.setStatus(SyncStatus.Syncing); const localExtensions = await this.getLocalExtensions(); const { added, removed, updated, remote } = merge(localExtensions, null, null, [], this.getIgnoredExtensions()); const lastSyncUserData = await this.getLastSyncUserData(); const remoteUserData = await this.getRemoteUserData(lastSyncUserData); - await this.apply({ added, removed, updated, remote, remoteUserData, localExtensions, skippedExtensions: [], lastSyncUserData }, true); + await this.apply({ + added, removed, updated, remote, remoteUserData, localExtensions, skippedExtensions: [], lastSyncUserData, + hasLocalChanged: added.length > 0 || removed.length > 0 || updated.length > 0, + hasRemoteChanged: remote !== null + }, true); - this.logService.info('Extensions: Finished pushing extensions.'); + this.logService.info(`${this.syncResourceLogLabel}: Finished pushing extensions.`); } finally { this.setStatus(SyncStatus.Idle); } } - async sync(ref?: string): Promise { - if (!this.extensionGalleryService.isEnabled()) { - this.logService.info('Extensions: Skipping synchronizing extensions as gallery is disabled.'); - return; - } - return super.sync(ref); - } - async stop(): Promise { } - accept(content: string): Promise { - throw new Error('Extensions: Conflicts should not occur'); + async getAssociatedResources({ uri }: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]> { + return [{ resource: joinPath(uri, 'extensions.json') }]; + } + + async resolveContent(uri: URI): Promise { + let content = await super.resolveContent(uri); + if (content) { + return content; + } + content = await super.resolveContent(dirname(uri)); + if (content) { + const syncData = this.parseSyncData(content); + if (syncData) { + switch (basename(uri)) { + case 'extensions.json': + const edits = format(syncData.content, undefined, {}); + return applyEdits(syncData.content, edits); + } + } + } + return null; + } + + async acceptConflict(conflict: URI, content: string): Promise { + throw new Error(`${this.syncResourceLogLabel}: Conflicts should not occur`); } async hasLocalData(): Promise { @@ -144,24 +170,13 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse return false; } - async getRemoteContent(): Promise { - return null; + protected async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise { + const previewResult = await this.generatePreview(remoteUserData, lastSyncUserData); + await this.apply(previewResult); + return SyncStatus.Idle; } - protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise { - try { - const previewResult = await this.getPreview(remoteUserData, lastSyncUserData); - await this.apply(previewResult); - } catch (e) { - this.setStatus(SyncStatus.Idle); - throw e; - } - - this.logService.trace('Extensions: Finished synchronizing extensions.'); - this.setStatus(SyncStatus.Idle); - } - - private async getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise { + protected async generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise { const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? this.parseExtensions(remoteUserData.syncData) : null; const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? this.parseExtensions(lastSyncUserData.syncData!) : null; const skippedExtensions: ISyncExtension[] = lastSyncUserData ? lastSyncUserData.skippedExtensions || [] : []; @@ -169,48 +184,57 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const localExtensions = await this.getLocalExtensions(); if (remoteExtensions) { - this.logService.trace('Extensions: Merging remote extensions with local extensions...'); + this.logService.trace(`${this.syncResourceLogLabel}: Merging remote extensions with local extensions...`); } else { - this.logService.trace('Extensions: Remote extensions does not exist. Synchronizing extensions for the first time.'); + this.logService.trace(`${this.syncResourceLogLabel}: Remote extensions does not exist. Synchronizing extensions for the first time.`); } const { added, removed, updated, remote } = merge(localExtensions, remoteExtensions, lastSyncExtensions, skippedExtensions, this.getIgnoredExtensions()); - return { added, removed, updated, remote, skippedExtensions, remoteUserData, localExtensions, lastSyncUserData }; + return { + added, + removed, + updated, + remote, + skippedExtensions, + remoteUserData, + localExtensions, + lastSyncUserData, + hasLocalChanged: added.length > 0 || removed.length > 0 || updated.length > 0, + hasRemoteChanged: remote !== null + }; } private getIgnoredExtensions() { return this.configurationService.getValue('sync.ignoredExtensions') || []; } - private async apply({ added, removed, updated, remote, remoteUserData, skippedExtensions, lastSyncUserData, localExtensions }: ISyncPreviewResult, forcePush?: boolean): Promise { + private async apply({ added, removed, updated, remote, remoteUserData, skippedExtensions, lastSyncUserData, localExtensions, hasLocalChanged, hasRemoteChanged }: IExtensionsSyncPreviewResult, forcePush?: boolean): Promise { - const hasChanges = added.length || removed.length || updated.length || remote; - - if (!hasChanges) { - this.logService.info('Extensions: No changes found during synchronizing extensions.'); + if (!hasLocalChanged && !hasRemoteChanged) { + this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing extensions.`); } - if (added.length || removed.length || updated.length) { + if (hasLocalChanged) { // back up all disabled or market place extensions const backUpExtensions = localExtensions.filter(e => e.disabled || !!e.identifier.uuid); - await this.backupLocal(VSBuffer.fromString(JSON.stringify(backUpExtensions, null, '\t'))); + await this.backupLocal(JSON.stringify(backUpExtensions)); skippedExtensions = await this.updateLocalExtensions(added, removed, updated, skippedExtensions); } if (remote) { // update remote - this.logService.trace('Extensions: Updating remote extensions...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating remote extensions...`); const content = JSON.stringify(remote); remoteUserData = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref); - this.logService.info('Extensions: Updated remote extensions'); + this.logService.info(`${this.syncResourceLogLabel}: Updated remote extensions`); } if (lastSyncUserData?.ref !== remoteUserData.ref) { // update last sync - this.logService.trace('Extensions: Updating last synchronized extensions...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized extensions...`); await this.updateLastSyncUserData(remoteUserData, { skippedExtensions }); - this.logService.info('Extensions: Updated last synchronized extensions'); + this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized extensions`); } } @@ -222,9 +246,9 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User); const extensionsToRemove = installedExtensions.filter(({ identifier }) => removed.some(r => areSameExtensions(identifier, r))); await Promise.all(extensionsToRemove.map(async extensionToRemove => { - this.logService.trace('Extensions: Uninstalling local extension...', extensionToRemove.identifier.id); + this.logService.trace(`${this.syncResourceLogLabel}: Uninstalling local extension...`, extensionToRemove.identifier.id); await this.extensionManagementService.uninstall(extensionToRemove); - this.logService.info('Extensions: Uninstalled local extension.', extensionToRemove.identifier.id); + this.logService.info(`${this.syncResourceLogLabel}: Uninstalled local extension.`, extensionToRemove.identifier.id); removeFromSkipped.push(extensionToRemove.identifier); })); } @@ -237,13 +261,13 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse // Builtin Extension: Sync only enablement state if (installedExtension && installedExtension.type === ExtensionType.System) { if (e.disabled) { - this.logService.trace('Extensions: Disabling extension...', e.identifier.id); + this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...`, e.identifier.id); await this.extensionEnablementService.disableExtension(e.identifier); - this.logService.info('Extensions: Disabled extension', e.identifier.id); + this.logService.info(`${this.syncResourceLogLabel}: Disabled extension`, e.identifier.id); } else { - this.logService.trace('Extensions: Enabling extension...', e.identifier.id); + this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...`, e.identifier.id); await this.extensionEnablementService.enableExtension(e.identifier); - this.logService.info('Extensions: Enabled extension', e.identifier.id); + this.logService.info(`${this.syncResourceLogLabel}: Enabled extension`, e.identifier.id); } removeFromSkipped.push(e.identifier); return; @@ -253,25 +277,25 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse if (extension) { try { if (e.disabled) { - this.logService.trace('Extensions: Disabling extension...', e.identifier.id, extension.version); + this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...`, e.identifier.id, extension.version); await this.extensionEnablementService.disableExtension(extension.identifier); - this.logService.info('Extensions: Disabled extension', e.identifier.id, extension.version); + this.logService.info(`${this.syncResourceLogLabel}: Disabled extension`, e.identifier.id, extension.version); } else { - this.logService.trace('Extensions: Enabling extension...', e.identifier.id, extension.version); + this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...`, e.identifier.id, extension.version); await this.extensionEnablementService.enableExtension(extension.identifier); - this.logService.info('Extensions: Enabled extension', e.identifier.id, extension.version); + this.logService.info(`${this.syncResourceLogLabel}: Enabled extension`, e.identifier.id, extension.version); } // Install only if the extension does not exist if (!installedExtension || installedExtension.manifest.version !== extension.version) { - this.logService.trace('Extensions: Installing extension...', e.identifier.id, extension.version); + this.logService.trace(`${this.syncResourceLogLabel}: Installing extension...`, e.identifier.id, extension.version); await this.extensionManagementService.installFromGallery(extension); - this.logService.info('Extensions: Installed extension.', e.identifier.id, extension.version); + this.logService.info(`${this.syncResourceLogLabel}: Installed extension.`, e.identifier.id, extension.version); removeFromSkipped.push(extension.identifier); } } catch (error) { addToSkipped.push(e); this.logService.error(error); - this.logService.info(localize('skip extension', "Skipped synchronizing extension {0}", extension.displayName || extension.identifier.id)); + this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing extension`, extension.displayName || extension.identifier.id); } } else { addToSkipped.push(e); diff --git a/src/vs/platform/userDataSync/common/globalStateMerge.ts b/src/vs/platform/userDataSync/common/globalStateMerge.ts index f4520a76a38..4d4755fb245 100644 --- a/src/vs/platform/userDataSync/common/globalStateMerge.ts +++ b/src/vs/platform/userDataSync/common/globalStateMerge.ts @@ -4,63 +4,143 @@ *--------------------------------------------------------------------------------------------*/ import * as objects from 'vs/base/common/objects'; -import { IGlobalState } from 'vs/platform/userDataSync/common/userDataSync'; +import { IStorageValue } from 'vs/platform/userDataSync/common/userDataSync'; import { IStringDictionary } from 'vs/base/common/collections'; import { values } from 'vs/base/common/map'; +import { IStorageKey } from 'vs/platform/userDataSync/common/storageKeys'; +import { ILogService } from 'vs/platform/log/common/log'; -export function merge(localGloablState: IGlobalState, remoteGlobalState: IGlobalState | null, lastSyncGlobalState: IGlobalState | null): { local?: IGlobalState, remote?: IGlobalState } { - if (!remoteGlobalState) { - return { remote: localGloablState }; - } - - const { local: localArgv, remote: remoteArgv } = doMerge(localGloablState.argv, remoteGlobalState.argv, lastSyncGlobalState ? lastSyncGlobalState.argv : null); - const { local: localStorage, remote: remoteStorage } = doMerge(localGloablState.storage, remoteGlobalState.storage, lastSyncGlobalState ? lastSyncGlobalState.storage : null); - const local: IGlobalState | undefined = localArgv || localStorage ? { argv: localArgv || localGloablState.argv, storage: localStorage || localGloablState.storage } : undefined; - const remote: IGlobalState | undefined = remoteArgv || remoteStorage ? { argv: remoteArgv || remoteGlobalState.argv, storage: remoteStorage || remoteGlobalState.storage } : undefined; - - return { local, remote }; +export interface IMergeResult { + local: { added: IStringDictionary, removed: string[], updated: IStringDictionary }; + remote: IStringDictionary | null; + skipped: string[]; } -function doMerge(local: IStringDictionary, remote: IStringDictionary, base: IStringDictionary | null): { local?: IStringDictionary, remote?: IStringDictionary } { - const localToRemote = compare(local, remote); +export function merge(localStorage: IStringDictionary, remoteStorage: IStringDictionary | null, baseStorage: IStringDictionary | null, storageKeys: ReadonlyArray, previouslySkipped: string[], logService: ILogService): IMergeResult { + if (!remoteStorage) { + return { remote: localStorage, local: { added: {}, removed: [], updated: {} }, skipped: [] }; + } + + const localToRemote = compare(localStorage, remoteStorage); if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) { // No changes found between local and remote. - return {}; + return { remote: null, local: { added: {}, removed: [], updated: {} }, skipped: [] }; } - const baseToRemote = base ? compare(base, remote) : { added: Object.keys(remote).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; - if (baseToRemote.added.size === 0 && baseToRemote.removed.size === 0 && baseToRemote.updated.size === 0) { - // No changes found between base and remote. - return { remote: local }; - } + const baseToRemote = baseStorage ? compare(baseStorage, remoteStorage) : { added: Object.keys(remoteStorage).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + const baseToLocal = baseStorage ? compare(baseStorage, localStorage) : { added: Object.keys(localStorage).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; - const baseToLocal = base ? compare(base, local) : { added: Object.keys(local).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; - if (baseToLocal.added.size === 0 && baseToLocal.removed.size === 0 && baseToLocal.updated.size === 0) { - // No changes found between base and local. - return { local: remote }; - } - - const merged = objects.deepClone(local); + const local: { added: IStringDictionary, removed: string[], updated: IStringDictionary } = { added: {}, removed: [], updated: {} }; + const remote: IStringDictionary = objects.deepClone(remoteStorage); + const skipped: string[] = []; // Added in remote for (const key of values(baseToRemote.added)) { - merged[key] = remote[key]; + const remoteValue = remoteStorage[key]; + const storageKey = storageKeys.filter(storageKey => storageKey.key === key)[0]; + if (!storageKey) { + skipped.push(key); + logService.info(`GlobalState: Skipped adding ${key} in local storage as it is not registered.`); + continue; + } + if (storageKey.version !== remoteValue.version) { + logService.info(`GlobalState: Skipped adding ${key} in local storage. Local version '${storageKey.version}' and remote version '${remoteValue.version} are not same.`); + continue; + } + const localValue = localStorage[key]; + if (localValue && localValue.value === remoteValue.value) { + continue; + } + if (baseToLocal.added.has(key)) { + local.updated[key] = remoteValue; + } else { + local.added[key] = remoteValue; + } } // Updated in Remote for (const key of values(baseToRemote.updated)) { - merged[key] = remote[key]; + const remoteValue = remoteStorage[key]; + const storageKey = storageKeys.filter(storageKey => storageKey.key === key)[0]; + if (!storageKey) { + skipped.push(key); + logService.info(`GlobalState: Skipped updating ${key} in local storage as is not registered.`); + continue; + } + if (storageKey.version !== remoteValue.version) { + logService.info(`GlobalState: Skipped updating ${key} in local storage. Local version '${storageKey.version}' and remote version '${remoteValue.version} are not same.`); + continue; + } + const localValue = localStorage[key]; + if (localValue && localValue.value === remoteValue.value) { + continue; + } + local.updated[key] = remoteValue; } - // Removed in remote & local + // Removed in remote for (const key of values(baseToRemote.removed)) { - // Got removed in local - if (baseToLocal.removed.has(key)) { - delete merged[key]; + const storageKey = storageKeys.filter(storageKey => storageKey.key === key)[0]; + if (!storageKey) { + logService.info(`GlobalState: Skipped removing ${key} in local storage. It is not registered to sync.`); + continue; + } + local.removed.push(key); + } + + // Added in local + for (const key of values(baseToLocal.added)) { + if (!baseToRemote.added.has(key)) { + remote[key] = localStorage[key]; } } - return { local: merged, remote: merged }; + // Updated in local + for (const key of values(baseToLocal.updated)) { + if (baseToRemote.updated.has(key) || baseToRemote.removed.has(key)) { + continue; + } + const remoteValue = remote[key]; + const localValue = localStorage[key]; + if (localValue.version < remoteValue.version) { + logService.info(`GlobalState: Skipped updating ${key} in remote storage. Local version '${localValue.version}' and remote version '${remoteValue.version} are not same.`); + continue; + } + remote[key] = localValue; + } + + // Removed in local + for (const key of values(baseToLocal.removed)) { + // do not remove from remote if it is updated in remote + if (baseToRemote.updated.has(key)) { + continue; + } + + const storageKey = storageKeys.filter(storageKey => storageKey.key === key)[0]; + // do not remove from remote if storage key is not found + if (!storageKey) { + skipped.push(key); + logService.info(`GlobalState: Skipped removing ${key} in remote storage. It is not registered to sync.`); + continue; + } + + const remoteValue = remote[key]; + // do not remove from remote if local data version is old + if (storageKey.version < remoteValue.version) { + logService.info(`GlobalState: Skipped updating ${key} in remote storage. Local version '${storageKey.version}' and remote version '${remoteValue.version} are not same.`); + continue; + } + + // add to local if it was skipped before + if (previouslySkipped.indexOf(key) !== -1) { + local.added[key] = remote[key]; + continue; + } + + delete remote[key]; + } + + return { local, remote: areSame(remote, remoteStorage) ? null : remote, skipped }; } function compare(from: IStringDictionary, to: IStringDictionary): { added: Set, removed: Set, updated: Set } { @@ -84,3 +164,8 @@ function compare(from: IStringDictionary, to: IStringDictionary): { ad return { added, removed, updated }; } +function areSame(a: IStringDictionary, b: IStringDictionary): boolean { + const { added, removed, updated } = compare(a, b); + return added.size === 0 && removed.size === 0 && updated.size === 0; +} + diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index 8e00f07a9ae..aad86c57f60 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -3,11 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncResource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, ISyncResourceHandle, IStorageValue, ISyncPreviewResult } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { dirname } from 'vs/base/common/resources'; +import { dirname, joinPath, basename } from 'vs/base/common/resources'; import { IFileService } from 'vs/platform/files/common/files'; import { IStringDictionary } from 'vs/base/common/collections'; import { edit } from 'vs/platform/userDataSync/common/content'; @@ -16,86 +16,120 @@ import { parse } from 'vs/base/common/json'; import { AbstractSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { URI } from 'vs/base/common/uri'; +import { format } from 'vs/base/common/jsonFormatter'; +import { applyEdits } from 'vs/base/common/jsonEdit'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageKeysSyncRegistryService, IStorageKey } from 'vs/platform/userDataSync/common/storageKeys'; +import { equals } from 'vs/base/common/arrays'; +const argvStoragePrefx = 'globalState.argv.'; const argvProperties: string[] = ['locale']; -interface ISyncPreviewResult { - readonly local: IGlobalState | undefined; - readonly remote: IGlobalState | undefined; +interface IGlobalSyncPreviewResult extends ISyncPreviewResult { + readonly local: { added: IStringDictionary, removed: string[], updated: IStringDictionary }; + readonly remote: IStringDictionary | null; + readonly skippedStorageKeys: string[]; readonly localUserData: IGlobalState; readonly remoteUserData: IRemoteUserData; - readonly lastSyncUserData: IRemoteUserData | null; + readonly lastSyncUserData: ILastSyncUserData | null; +} + +interface ILastSyncUserData extends IRemoteUserData { + skippedStorageKeys: string[] | undefined; } export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { - readonly resourceKey: ResourceKey = 'globalState'; protected readonly version: number = 1; constructor( @IFileService fileService: IFileService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, @IConfigurationService configurationService: IConfigurationService, + @IStorageService private readonly storageService: IStorageService, + @IStorageKeysSyncRegistryService private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, ) { - super(SyncSource.GlobalState, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); + super(SyncResource.GlobalState, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); this._register(this.fileService.watch(dirname(this.environmentService.argvResource))); - this._register(Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.environmentService.argvResource))(() => this._onDidChangeLocal.fire())); + this._register( + Event.any( + /* Locale change */ + Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.environmentService.argvResource)), + /* Storage change */ + Event.filter(this.storageService.onDidChangeStorage, e => storageKeysSyncRegistryService.storageKeys.some(({ key }) => e.key === key)), + /* Storage key registered */ + this.storageKeysSyncRegistryService.onDidChangeStorageKeys + )((() => this._onDidChangeLocal.fire())) + ); } async pull(): Promise { - if (!this.enabled) { - this.logService.info('UI State: Skipped pulling ui state as it is disabled.'); + if (!this.isEnabled()) { + this.logService.info(`${this.syncResourceLogLabel}: Skipped pulling ui state as it is disabled.`); return; } this.stop(); try { - this.logService.info('UI State: Started pulling ui state...'); + this.logService.info(`${this.syncResourceLogLabel}: Started pulling ui state...`); this.setStatus(SyncStatus.Syncing); - const lastSyncUserData = await this.getLastSyncUserData(); + const lastSyncUserData = await this.getLastSyncUserData(); const remoteUserData = await this.getRemoteUserData(lastSyncUserData); if (remoteUserData.syncData !== null) { - const localUserData = await this.getLocalGlobalState(); - const local: IGlobalState = JSON.parse(remoteUserData.syncData.content); - await this.apply({ local, remote: undefined, remoteUserData, localUserData, lastSyncUserData }); + const localGlobalState = await this.getLocalGlobalState(); + const remoteGlobalState: IGlobalState = JSON.parse(remoteUserData.syncData.content); + const { local, remote, skipped } = merge(localGlobalState.storage, remoteGlobalState.storage, null, this.getSyncStorageKeys(), lastSyncUserData?.skippedStorageKeys || [], this.logService); + await this.apply({ + local, remote, remoteUserData, localUserData: localGlobalState, lastSyncUserData, + skippedStorageKeys: skipped, + hasLocalChanged: Object.keys(local.added).length > 0 || Object.keys(local.updated).length > 0 || local.removed.length > 0, + hasRemoteChanged: remote !== null + }); } // No remote exists to pull else { - this.logService.info('UI State: Remote UI state does not exist.'); + this.logService.info(`${this.syncResourceLogLabel}: Remote UI state does not exist.`); } - this.logService.info('UI State: Finished pulling UI state.'); + this.logService.info(`${this.syncResourceLogLabel}: Finished pulling UI state.`); } finally { this.setStatus(SyncStatus.Idle); } } async push(): Promise { - if (!this.enabled) { - this.logService.info('UI State: Skipped pushing UI State as it is disabled.'); + if (!this.isEnabled()) { + this.logService.info(`${this.syncResourceLogLabel}: Skipped pushing UI State as it is disabled.`); return; } this.stop(); try { - this.logService.info('UI State: Started pushing UI State...'); + this.logService.info(`${this.syncResourceLogLabel}: Started pushing UI State...`); this.setStatus(SyncStatus.Syncing); const localUserData = await this.getLocalGlobalState(); - const lastSyncUserData = await this.getLastSyncUserData(); + const lastSyncUserData = await this.getLastSyncUserData(); const remoteUserData = await this.getRemoteUserData(lastSyncUserData); - await this.apply({ local: undefined, remote: localUserData, remoteUserData, localUserData, lastSyncUserData }, true); + await this.apply({ + local: { added: {}, removed: [], updated: {} }, remote: localUserData.storage, remoteUserData, localUserData, lastSyncUserData, + skippedStorageKeys: [], + hasLocalChanged: false, + hasRemoteChanged: true + }, true); - this.logService.info('UI State: Finished pushing UI State.'); + this.logService.info(`${this.syncResourceLogLabel}: Finished pushing UI State.`); } finally { this.setStatus(SyncStatus.Idle); } @@ -104,14 +138,37 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs async stop(): Promise { } - accept(content: string): Promise { - throw new Error('UI State: Conflicts should not occur'); + async getAssociatedResources({ uri }: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]> { + return [{ resource: joinPath(uri, 'globalState.json') }]; + } + + async resolveContent(uri: URI): Promise { + let content = await super.resolveContent(uri); + if (content) { + return content; + } + content = await super.resolveContent(dirname(uri)); + if (content) { + const syncData = this.parseSyncData(content); + if (syncData) { + switch (basename(uri)) { + case 'globalState.json': + const edits = format(syncData.content, undefined, {}); + return applyEdits(syncData.content, edits); + } + } + } + return null; + } + + async acceptConflict(conflict: URI, content: string): Promise { + throw new Error(`${this.syncResourceLogLabel}: Conflicts should not occur`); } async hasLocalData(): Promise { try { - const localGloablState = await this.getLocalGlobalState(); - if (localGloablState.argv['locale'] !== 'en') { + const { storage } = await this.getLocalGlobalState(); + if (Object.keys(storage).length > 1 || storage[`${argvStoragePrefx}.locale`]?.value !== 'en') { return true; } } catch (error) { @@ -120,83 +177,80 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs return false; } - async getRemoteContent(): Promise { - return null; + protected async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise { + const result = await this.generatePreview(remoteUserData, lastSyncUserData); + await this.apply(result); + return SyncStatus.Idle; } - protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { - try { - const result = await this.getPreview(remoteUserData, lastSyncUserData); - await this.apply(result); - this.logService.trace('UI State: Finished synchronizing ui state.'); - } catch (e) { - this.setStatus(SyncStatus.Idle); - throw e; - } finally { - this.setStatus(SyncStatus.Idle); - } - } - - private async getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, ): Promise { + protected async generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise { const remoteGlobalState: IGlobalState = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null; - const lastSyncGlobalState = lastSyncUserData && lastSyncUserData.syncData ? JSON.parse(lastSyncUserData.syncData.content) : null; + const lastSyncGlobalState: IGlobalState = lastSyncUserData && lastSyncUserData.syncData ? JSON.parse(lastSyncUserData.syncData.content) : null; const localGloablState = await this.getLocalGlobalState(); if (remoteGlobalState) { - this.logService.trace('UI State: Merging remote ui state with local ui state...'); + this.logService.trace(`${this.syncResourceLogLabel}: Merging remote ui state with local ui state...`); } else { - this.logService.trace('UI State: Remote ui state does not exist. Synchronizing ui state for the first time.'); + this.logService.trace(`${this.syncResourceLogLabel}: Remote ui state does not exist. Synchronizing ui state for the first time.`); } - const { local, remote } = merge(localGloablState, remoteGlobalState, lastSyncGlobalState); + const { local, remote, skipped } = merge(localGloablState.storage, remoteGlobalState ? remoteGlobalState.storage : null, lastSyncGlobalState ? lastSyncGlobalState.storage : null, this.getSyncStorageKeys(), lastSyncUserData?.skippedStorageKeys || [], this.logService); - return { local, remote, remoteUserData, localUserData: localGloablState, lastSyncUserData }; + return { + local, remote, remoteUserData, localUserData: localGloablState, lastSyncUserData, + skippedStorageKeys: skipped, + hasLocalChanged: Object.keys(local.added).length > 0 || Object.keys(local.updated).length > 0 || local.removed.length > 0, + hasRemoteChanged: remote !== null + }; } - private async apply({ local, remote, remoteUserData, lastSyncUserData, localUserData }: ISyncPreviewResult, forcePush?: boolean): Promise { + private async apply({ local, remote, remoteUserData, lastSyncUserData, localUserData, hasLocalChanged, hasRemoteChanged, skippedStorageKeys }: IGlobalSyncPreviewResult, forcePush?: boolean): Promise { - const hasChanges = local || remote; - - if (!hasChanges) { - this.logService.info('UI State: No changes found during synchronizing ui state.'); + if (!hasLocalChanged && !hasRemoteChanged) { + this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing ui state.`); } - if (local) { + if (hasLocalChanged) { // update local - this.logService.trace('UI State: Updating local ui state...'); - await this.backupLocal(VSBuffer.fromString(JSON.stringify(localUserData, null, '\t'))); + this.logService.trace(`${this.syncResourceLogLabel}: Updating local ui state...`); + await this.backupLocal(JSON.stringify(localUserData)); await this.writeLocalGlobalState(local); - this.logService.info('UI State: Updated local ui state'); + this.logService.info(`${this.syncResourceLogLabel}: Updated local ui state`); } - if (remote) { + if (hasRemoteChanged) { // update remote - this.logService.trace('UI State: Updating remote ui state...'); - const content = JSON.stringify(remote); + this.logService.trace(`${this.syncResourceLogLabel}: Updating remote ui state...`); + const content = JSON.stringify({ storage: remote }); remoteUserData = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref); - this.logService.info('UI State: Updated remote ui state'); + this.logService.info(`${this.syncResourceLogLabel}: Updated remote ui state`); } - if (lastSyncUserData?.ref !== remoteUserData.ref) { + if (lastSyncUserData?.ref !== remoteUserData.ref || !equals(lastSyncUserData.skippedStorageKeys, skippedStorageKeys)) { // update last sync - this.logService.trace('UI State: Updating last synchronized ui state...'); - await this.updateLastSyncUserData(remoteUserData); - this.logService.info('UI State: Updated last synchronized ui state'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized ui state...`); + await this.updateLastSyncUserData(remoteUserData, { skippedStorageKeys }); + this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized ui state`); } } private async getLocalGlobalState(): Promise { - const argv: IStringDictionary = {}; - const storage: IStringDictionary = {}; + const storage: IStringDictionary = {}; const argvContent: string = await this.getLocalArgvContent(); const argvValue: IStringDictionary = parse(argvContent); for (const argvProperty of argvProperties) { if (argvValue[argvProperty] !== undefined) { - argv[argvProperty] = argvValue[argvProperty]; + storage[`${argvStoragePrefx}${argvProperty}`] = { version: 1, value: argvValue[argvProperty] }; } } - return { argv, storage }; + for (const { key, version } of this.storageKeysSyncRegistryService.storageKeys) { + const value = this.storageService.get(key, StorageScope.GLOBAL); + if (value) { + storage[key] = { version, value }; + } + } + return { storage }; } private async getLocalArgvContent(): Promise { @@ -207,15 +261,59 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs return '{}'; } - private async writeLocalGlobalState(globalState: IGlobalState): Promise { - const argvContent = await this.getLocalArgvContent(); - let content = argvContent; - for (const argvProperty of Object.keys(globalState.argv)) { - content = edit(content, [argvProperty], globalState.argv[argvProperty], {}); + private async writeLocalGlobalState({ added, removed, updated }: { added: IStringDictionary, updated: IStringDictionary, removed: string[] }): Promise { + const argv: IStringDictionary = {}; + const updatedStorage: IStringDictionary = {}; + const handleUpdatedStorage = (keys: string[], storage?: IStringDictionary): void => { + for (const key of keys) { + if (key.startsWith(argvStoragePrefx)) { + argv[key.substring(argvStoragePrefx.length)] = storage ? storage[key].value : undefined; + continue; + } + if (storage) { + const storageValue = storage[key]; + if (storageValue.value !== String(this.storageService.get(key, StorageScope.GLOBAL))) { + updatedStorage[key] = storageValue.value; + } + } else { + if (this.storageService.get(key, StorageScope.GLOBAL) !== undefined) { + updatedStorage[key] = undefined; + } + } + } + }; + handleUpdatedStorage(Object.keys(added), added); + handleUpdatedStorage(Object.keys(updated), updated); + handleUpdatedStorage(removed); + if (Object.keys(argv).length) { + this.logService.trace(`${this.syncResourceLogLabel}: Updating locale...`); + await this.updateArgv(argv); + this.logService.info(`${this.syncResourceLogLabel}: Updated locale`); } - if (argvContent !== content) { - await this.fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(content)); + const updatedStorageKeys: string[] = Object.keys(updatedStorage); + if (updatedStorageKeys.length) { + this.logService.trace(`${this.syncResourceLogLabel}: Updating global state...`); + for (const key of Object.keys(updatedStorage)) { + this.storageService.store(key, updatedStorage[key], StorageScope.GLOBAL); + } + this.logService.info(`${this.syncResourceLogLabel}: Updated global state`, Object.keys(updatedStorage)); } } + private async updateArgv(argv: IStringDictionary): Promise { + const argvContent = await this.getLocalArgvContent(); + let content = argvContent; + for (const argvProperty of Object.keys(argv)) { + content = edit(content, [argvProperty], argv[argvProperty], {}); + } + if (argvContent !== content) { + this.logService.trace(`${this.syncResourceLogLabel}: Updating locale...`); + await this.fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(content)); + this.logService.info(`${this.syncResourceLogLabel}: Updated locale.`); + } + } + + private getSyncStorageKeys(): IStorageKey[] { + return [...this.storageKeysSyncRegistryService.storageKeys, ...argvProperties.map(argvProprety => ({ key: `${argvStoragePrefx}${argvProprety}`, version: 1 }))]; + } } diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index 8d1d78409fd..ed6943af053 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncSource, IUserDataSynchroniser, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncResource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, USER_DATA_SYNC_SCHEME, PREVIEW_DIR_NAME, ISyncResourceHandle } from 'vs/platform/userDataSync/common/userDataSync'; import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge'; import { VSBuffer } from 'vs/base/common/buffer'; import { parse } from 'vs/base/common/json'; @@ -19,6 +19,7 @@ import { isNonEmptyArray } from 'vs/base/common/arrays'; import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { URI } from 'vs/base/common/uri'; +import { joinPath, isEqual, dirname, basename } from 'vs/base/common/resources'; interface ISyncContent { mac?: string; @@ -29,33 +30,34 @@ interface ISyncContent { export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser { - readonly resourceKey: ResourceKey = 'keybindings'; - protected get conflictsPreviewResource(): URI { return this.environmentService.keybindingsSyncPreviewResource; } protected readonly version: number = 1; + protected readonly localPreviewResource: URI = joinPath(this.syncFolder, PREVIEW_DIR_NAME, 'keybindings.json'); + protected readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME }); constructor( @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IConfigurationService configurationService: IConfigurationService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @IFileService fileService: IFileService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IEnvironmentService environmentService: IEnvironmentService, @IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService, @ITelemetryService telemetryService: ITelemetryService, ) { - super(environmentService.keybindingsResource, SyncSource.Keybindings, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); + super(environmentService.keybindingsResource, SyncResource.Keybindings, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); } async pull(): Promise { - if (!this.enabled) { - this.logService.info('Keybindings: Skipped pulling keybindings as it is disabled.'); + if (!this.isEnabled()) { + this.logService.info(`${this.syncResourceLogLabel}: Skipped pulling keybindings as it is disabled.`); return; } this.stop(); try { - this.logService.info('Keybindings: Started pulling keybindings...'); + this.logService.info(`${this.syncResourceLogLabel}: Started pulling keybindings...`); this.setStatus(SyncStatus.Syncing); const lastSyncUserData = await this.getLastSyncUserData(); @@ -78,10 +80,10 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem // No remote exists to pull else { - this.logService.info('Keybindings: Remote keybindings does not exist.'); + this.logService.info(`${this.syncResourceLogLabel}: Remote keybindings does not exist.`); } - this.logService.info('Keybindings: Finished pulling keybindings.'); + this.logService.info(`${this.syncResourceLogLabel}: Finished pulling keybindings.`); } finally { this.setStatus(SyncStatus.Idle); } @@ -89,15 +91,15 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem } async push(): Promise { - if (!this.enabled) { - this.logService.info('Keybindings: Skipped pushing keybindings as it is disabled.'); + if (!this.isEnabled()) { + this.logService.info(`${this.syncResourceLogLabel}: Skipped pushing keybindings as it is disabled.`); return; } this.stop(); try { - this.logService.info('Keybindings: Started pushing keybindings...'); + this.logService.info(`${this.syncResourceLogLabel}: Started pushing keybindings...`); this.setStatus(SyncStatus.Syncing); const fileContent = await this.getLocalFileContent(); @@ -119,18 +121,20 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem // No local exists to push else { - this.logService.info('Keybindings: Local keybindings does not exist.'); + this.logService.info(`${this.syncResourceLogLabel}: Local keybindings does not exist.`); } - this.logService.info('Keybindings: Finished pushing keybindings.'); + this.logService.info(`${this.syncResourceLogLabel}: Finished pushing keybindings.`); } finally { this.setStatus(SyncStatus.Idle); } } - async accept(content: string): Promise { - if (this.status === SyncStatus.HasConflicts) { + async acceptConflict(conflict: URI, content: string): Promise { + if (this.status === SyncStatus.HasConflicts + && (isEqual(this.localPreviewResource, conflict) || isEqual(this.remotePreviewResource, conflict)) + ) { const preview = await this.syncPreviewResultPromise!; this.cancel(); this.syncPreviewResultPromise = createCancelablePromise(async () => ({ ...preview, content })); @@ -156,34 +160,52 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem return false; } - async getRemoteContent(preview?: boolean): Promise { - const content = await super.getRemoteContent(preview); + async getAssociatedResources({ uri }: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]> { + return [{ resource: joinPath(uri, 'keybindings.json'), comparableResource: this.file }]; + } + + async resolveContent(uri: URI): Promise { + if (isEqual(this.remotePreviewResource, uri)) { + return this.getConflictContent(uri); + } + let content = await super.resolveContent(uri); + if (content) { + return content; + } + content = await super.resolveContent(dirname(uri)); + if (content) { + const syncData = this.parseSyncData(content); + if (syncData) { + switch (basename(uri)) { + case 'keybindings.json': + return this.getKeybindingsContentFromSyncContent(syncData.content); + } + } + } + return null; + } + + protected async getConflictContent(conflictResource: URI): Promise { + const content = await super.getConflictContent(conflictResource); return content !== null ? this.getKeybindingsContentFromSyncContent(content) : null; } - protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { + protected async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { try { const result = await this.getPreview(remoteUserData, lastSyncUserData); if (result.hasConflicts) { - this.logService.info('Keybindings: Detected conflicts while synchronizing keybindings.'); - this.setStatus(SyncStatus.HasConflicts); - return; - } - try { - await this.apply(); - this.logService.trace('Keybindings: Finished synchronizing keybindings...'); - } finally { - this.setStatus(SyncStatus.Idle); + return SyncStatus.HasConflicts; } + await this.apply(); + return SyncStatus.Idle; } catch (e) { this.syncPreviewResultPromise = null; - this.setStatus(SyncStatus.Idle); if (e instanceof UserDataSyncError) { switch (e.code) { case UserDataSyncErrorCode.LocalPreconditionFailed: // Rejected as there is a new local version. Syncing again. - this.logService.info('Keybindings: Failed to synchronize keybindings as there is a new local version available. Synchronizing again...'); - return this.sync(remoteUserData.ref); + this.logService.info(`${this.syncResourceLogLabel}: Failed to synchronize keybindings as there is a new local version available. Synchronizing again...`); + return this.performSync(remoteUserData, lastSyncUserData); } } throw e; @@ -199,35 +221,38 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem if (content !== null) { if (this.hasErrors(content)) { - throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync keybindings as there are errors/warning in keybindings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source); + throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync keybindings as there are errors/warning in keybindings file."), UserDataSyncErrorCode.LocalInvalidContent, this.resource); } if (hasLocalChanged) { - this.logService.trace('Keybindings: Updating local keybindings...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating local keybindings...`); + if (fileContent) { + await this.backupLocal(this.toSyncContent(fileContent.value.toString(), null)); + } await this.updateLocalFileContent(content, fileContent); - this.logService.info('Keybindings: Updated local keybindings'); + this.logService.info(`${this.syncResourceLogLabel}: Updated local keybindings`); } if (hasRemoteChanged) { - this.logService.trace('Keybindings: Updating remote keybindings...'); - const remoteContents = this.updateSyncContent(content, remoteUserData.syncData ? remoteUserData.syncData.content : null); + this.logService.trace(`${this.syncResourceLogLabel}: Updating remote keybindings...`); + const remoteContents = this.toSyncContent(content, remoteUserData.syncData ? remoteUserData.syncData.content : null); remoteUserData = await this.updateRemoteUserData(remoteContents, forcePush ? null : remoteUserData.ref); - this.logService.info('Keybindings: Updated remote keybindings'); + this.logService.info(`${this.syncResourceLogLabel}: Updated remote keybindings`); } // Delete the preview try { - await this.fileService.del(this.conflictsPreviewResource); + await this.fileService.del(this.localPreviewResource); } catch (e) { /* ignore */ } } else { - this.logService.info('Keybindings: No changes found during synchronizing keybindings.'); + this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing keybindings.`); } if (lastSyncUserData?.ref !== remoteUserData.ref && (content !== null || fileContent !== null)) { - this.logService.trace('Keybindings: Updating last synchronized keybindings...'); - const lastSyncContent = this.updateSyncContent(content !== null ? content : fileContent!.value.toString(), null); + this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized keybindings...`); + const lastSyncContent = this.toSyncContent(content !== null ? content : fileContent!.value.toString(), null); await this.updateLastSyncUserData({ ref: remoteUserData.ref, syncData: { version: remoteUserData.syncData!.version, content: lastSyncContent } }); - this.logService.info('Keybindings: Updated last synchronized keybindings'); + this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized keybindings`); } this.syncPreviewResultPromise = null; @@ -240,7 +265,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem return this.syncPreviewResultPromise; } - private async generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise { + protected async generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken = CancellationToken.None): Promise { const remoteContent = remoteUserData.syncData ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null; const lastSyncContent = lastSyncUserData && lastSyncUserData.syncData ? this.getKeybindingsContentFromSyncContent(lastSyncUserData.syncData.content) : null; // Get file content last to get the latest @@ -255,14 +280,14 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem if (remoteContent) { const localContent: string = fileContent ? fileContent.value.toString() : '[]'; if (this.hasErrors(localContent)) { - throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync keybindings as there are errors/warning in keybindings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source); + throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync keybindings as there are errors/warning in keybindings file."), UserDataSyncErrorCode.LocalInvalidContent, this.resource); } if (!lastSyncContent // First time sync || lastSyncContent !== localContent // Local has forwarded || lastSyncContent !== remoteContent // Remote has forwarded ) { - this.logService.trace('Keybindings: Merging remote keybindings with local keybindings...'); + this.logService.trace(`${this.syncResourceLogLabel}: Merging remote keybindings with local keybindings...`); const result = await merge(localContent, remoteContent, lastSyncContent, formattingOptions, this.userDataSyncUtilService); // Sync only if there are changes if (result.hasChanges) { @@ -276,15 +301,17 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem // First time syncing to remote else if (fileContent) { - this.logService.trace('Keybindings: Remote keybindings does not exist. Synchronizing keybindings for the first time.'); + this.logService.trace(`${this.syncResourceLogLabel}: Remote keybindings does not exist. Synchronizing keybindings for the first time.`); content = fileContent.value.toString(); hasRemoteChanged = true; } if (content && !token.isCancellationRequested) { - await this.fileService.writeFile(this.environmentService.keybindingsSyncPreviewResource, VSBuffer.fromString(content)); + await this.fileService.writeFile(this.localPreviewResource, VSBuffer.fromString(content)); } + this.setConflicts(hasConflicts && !token.isCancellationRequested ? [{ local: this.localPreviewResource, remote: this.remotePreviewResource }] : []); + return { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged, hasConflicts }; } @@ -308,7 +335,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem } } - private updateSyncContent(keybindingsContent: string, syncContent: string | null): string { + private toSyncContent(keybindingsContent: string, syncContent: string | null): string { let parsed: ISyncContent = {}; try { parsed = JSON.parse(syncContent || '{}'); diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 4dcce8bb0ee..e159e8188c3 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -4,26 +4,25 @@ *--------------------------------------------------------------------------------------------*/ import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, CONFIGURATION_SYNC_STORE_KEY, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, USER_DATA_SYNC_SCHEME, PREVIEW_DIR_NAME, ISyncResourceHandle } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { parse } from 'vs/base/common/json'; import { localize } from 'vs/nls'; -import { Emitter, Event } from 'vs/base/common/event'; +import { Event } from 'vs/base/common/event'; import { createCancelablePromise } from 'vs/base/common/async'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CancellationToken } from 'vs/base/common/cancellation'; import { updateIgnoredSettings, merge, getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; -import * as arrays from 'vs/base/common/arrays'; -import * as objects from 'vs/base/common/objects'; import { isEmptyObject } from 'vs/base/common/types'; import { edit } from 'vs/platform/userDataSync/common/content'; import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { URI } from 'vs/base/common/uri'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { joinPath, isEqual, dirname, basename } from 'vs/base/common/resources'; -interface ISettingsSyncContent { +export interface ISettingsSyncContent { settings: string; } @@ -33,23 +32,19 @@ function isSettingsSyncContent(thing: any): thing is ISettingsSyncContent { && Object.keys(thing).length === 1; } -export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implements ISettingsSyncService { +export class SettingsSynchroniser extends AbstractJsonFileSynchroniser { _serviceBrand: any; - readonly resourceKey: ResourceKey = 'settings'; protected readonly version: number = 1; - protected get conflictsPreviewResource(): URI { return this.environmentService.settingsSyncPreviewResource; } - - private _conflicts: IConflictSetting[] = []; - get conflicts(): IConflictSetting[] { return this._conflicts; } - private _onDidChangeConflicts: Emitter = this._register(new Emitter()); - readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; + protected readonly localPreviewResource: URI = joinPath(this.syncFolder, PREVIEW_DIR_NAME, 'settings.json'); + protected readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME }); constructor( @IFileService fileService: IFileService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IEnvironmentService environmentService: IEnvironmentService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService, @IConfigurationService configurationService: IConfigurationService, @@ -57,7 +52,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement @ITelemetryService telemetryService: ITelemetryService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, ) { - super(environmentService.settingsResource, SyncSource.Settings, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); + super(environmentService.settingsResource, SyncResource.Settings, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); } protected setStatus(status: SyncStatus): void { @@ -67,25 +62,16 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement } } - private setConflicts(conflicts: IConflictSetting[]): void { - if (!arrays.equals(this.conflicts, conflicts, - (a, b) => a.key === b.key && objects.equals(a.localValue, b.localValue) && objects.equals(a.remoteValue, b.remoteValue)) - ) { - this._conflicts = conflicts; - this._onDidChangeConflicts.fire(conflicts); - } - } - async pull(): Promise { - if (!this.enabled) { - this.logService.info('Settings: Skipped pulling settings as it is disabled.'); + if (!this.isEnabled()) { + this.logService.info(`${this.syncResourceLogLabel}: Skipped pulling settings as it is disabled.`); return; } this.stop(); try { - this.logService.info('Settings: Started pulling settings...'); + this.logService.info(`${this.syncResourceLogLabel}: Started pulling settings...`); this.setStatus(SyncStatus.Syncing); const lastSyncUserData = await this.getLastSyncUserData(); @@ -113,25 +99,25 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement // No remote exists to pull else { - this.logService.info('Settings: Remote settings does not exist.'); + this.logService.info(`${this.syncResourceLogLabel}: Remote settings does not exist.`); } - this.logService.info('Settings: Finished pulling settings.'); + this.logService.info(`${this.syncResourceLogLabel}: Finished pulling settings.`); } finally { this.setStatus(SyncStatus.Idle); } } async push(): Promise { - if (!this.enabled) { - this.logService.info('Settings: Skipped pushing settings as it is disabled.'); + if (!this.isEnabled()) { + this.logService.info(`${this.syncResourceLogLabel}: Skipped pushing settings as it is disabled.`); return; } this.stop(); try { - this.logService.info('Settings: Started pushing settings...'); + this.logService.info(`${this.syncResourceLogLabel}: Started pushing settings...`); this.setStatus(SyncStatus.Syncing); const fileContent = await this.getLocalFileContent(); @@ -159,10 +145,10 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement // No local exists to push else { - this.logService.info('Settings: Local settings does not exist.'); + this.logService.info(`${this.syncResourceLogLabel}: Local settings does not exist.`); } - this.logService.info('Settings: Finished pushing settings.'); + this.logService.info(`${this.syncResourceLogLabel}: Finished pushing settings.`); } finally { this.setStatus(SyncStatus.Idle); } @@ -187,13 +173,41 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement return false; } - async getRemoteContent(preview?: boolean): Promise { - let content = await super.getRemoteContent(preview); + async getAssociatedResources({ uri }: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]> { + return [{ resource: joinPath(uri, 'settings.json'), comparableResource: this.file }]; + } + + async resolveContent(uri: URI): Promise { + if (isEqual(this.remotePreviewResource, uri)) { + return this.getConflictContent(uri); + } + let content = await super.resolveContent(uri); + if (content) { + return content; + } + content = await super.resolveContent(dirname(uri)); + if (content) { + const syncData = this.parseSyncData(content); + if (syncData) { + const settingsSyncContent = this.parseSettingsSyncContent(syncData.content); + if (settingsSyncContent) { + switch (basename(uri)) { + case 'settings.json': + return settingsSyncContent.settings; + } + } + } + } + return null; + } + + protected async getConflictContent(conflictResource: URI): Promise { + let content = await super.getConflictContent(conflictResource); if (content !== null) { const settingsSyncContent = this.parseSettingsSyncContent(content); content = settingsSyncContent ? settingsSyncContent.settings : null; } - if (preview && content !== null) { + if (content !== null) { const formatUtils = await this.getFormattingOptions(); // remove ignored settings from the remote content for preview const ignoredSettings = await this.getIgnoredSettings(); @@ -202,8 +216,10 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement return content; } - async accept(content: string): Promise { - if (this.status === SyncStatus.HasConflicts) { + async acceptConflict(conflict: URI, content: string): Promise { + if (this.status === SyncStatus.HasConflicts + && (isEqual(this.localPreviewResource, conflict) || isEqual(this.remotePreviewResource, conflict)) + ) { const preview = await this.syncPreviewResultPromise!; this.cancel(); const formatUtils = await this.getFormattingOptions(); @@ -220,33 +236,26 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement if (this.status === SyncStatus.HasConflicts) { const preview = await this.syncPreviewResultPromise!; this.cancel(); - await this.doSync(preview.remoteUserData, preview.lastSyncUserData, resolvedConflicts); + await this.performSync(preview.remoteUserData, preview.lastSyncUserData, resolvedConflicts); } } - protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: { key: string, value: any | undefined }[] = []): Promise { + protected async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: { key: string, value: any | undefined }[] = []): Promise { try { const result = await this.getPreview(remoteUserData, lastSyncUserData, resolvedConflicts); if (result.hasConflicts) { - this.logService.info('Settings: Detected conflicts while synchronizing settings.'); - this.setStatus(SyncStatus.HasConflicts); - return; - } - try { - await this.apply(); - this.logService.trace('Settings: Finished synchronizing settings.'); - } finally { - this.setStatus(SyncStatus.Idle); + return SyncStatus.HasConflicts; } + await this.apply(); + return SyncStatus.Idle; } catch (e) { this.syncPreviewResultPromise = null; - this.setStatus(SyncStatus.Idle); if (e instanceof UserDataSyncError) { switch (e.code) { case UserDataSyncErrorCode.LocalPreconditionFailed: // Rejected as there is a new local version. Syncing again. - this.logService.info('Settings: Failed to synchronize settings as there is a new local version available. Synchronizing again...'); - return this.sync(remoteUserData.ref); + this.logService.info(`${this.syncResourceLogLabel}: Failed to synchronize settings as there is a new local version available. Synchronizing again...`); + return this.performSync(remoteUserData, lastSyncUserData, resolvedConflicts); } } throw e; @@ -265,9 +274,12 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement this.validateContent(content); if (hasLocalChanged) { - this.logService.trace('Settings: Updating local settings...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating local settings...`); + if (fileContent) { + await this.backupLocal(JSON.stringify(this.toSettingsSyncContent(fileContent.value.toString()))); + } await this.updateLocalFileContent(content, fileContent); - this.logService.info('Settings: Updated local settings'); + this.logService.info(`${this.syncResourceLogLabel}: Updated local settings`); } if (hasRemoteChanged) { const formatUtils = await this.getFormattingOptions(); @@ -275,36 +287,36 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData); const ignoredSettings = await this.getIgnoredSettings(content); content = updateIgnoredSettings(content, remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : '{}', ignoredSettings, formatUtils); - this.logService.trace('Settings: Updating remote settings...'); - remoteUserData = await this.updateRemoteUserData(JSON.stringify({ settings: content }), forcePush ? null : remoteUserData.ref); - this.logService.info('Settings: Updated remote settings'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating remote settings...`); + remoteUserData = await this.updateRemoteUserData(JSON.stringify(this.toSettingsSyncContent(content)), forcePush ? null : remoteUserData.ref); + this.logService.info(`${this.syncResourceLogLabel}: Updated remote settings`); } // Delete the preview try { - await this.fileService.del(this.conflictsPreviewResource); + await this.fileService.del(this.localPreviewResource); } catch (e) { /* ignore */ } } else { - this.logService.info('Settings: No changes found during synchronizing settings.'); + this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing settings.`); } if (lastSyncUserData?.ref !== remoteUserData.ref) { - this.logService.trace('Settings: Updating last synchronized settings...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized settings...`); await this.updateLastSyncUserData(remoteUserData); - this.logService.info('Settings: Updated last synchronized settings'); + this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized settings`); } this.syncPreviewResultPromise = null; } - private getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: { key: string, value: any }[]): Promise { + private getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: { key: string, value: any }[] = []): Promise { if (!this.syncPreviewResultPromise) { this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(remoteUserData, lastSyncUserData, resolvedConflicts, token)); } return this.syncPreviewResultPromise; } - protected async generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: { key: string, value: any }[], token: CancellationToken): Promise { + protected async generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: { key: string, value: any }[] = [], token: CancellationToken = CancellationToken.None): Promise { const fileContent = await this.getLocalFileContent(); const formattingOptions = await this.getFormattingOptions(); const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData); @@ -314,24 +326,22 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement let hasLocalChanged: boolean = false; let hasRemoteChanged: boolean = false; let hasConflicts: boolean = false; - let conflictSettings: IConflictSetting[] = []; if (remoteSettingsSyncContent) { const localContent: string = fileContent ? fileContent.value.toString() : '{}'; this.validateContent(localContent); - this.logService.trace('Settings: Merging remote settings with local settings...'); + this.logService.trace(`${this.syncResourceLogLabel}: Merging remote settings with local settings...`); const ignoredSettings = await this.getIgnoredSettings(); const result = merge(localContent, remoteSettingsSyncContent.settings, lastSettingsSyncContent ? lastSettingsSyncContent.settings : null, ignoredSettings, resolvedConflicts, formattingOptions); content = result.localContent || result.remoteContent; hasLocalChanged = result.localContent !== null; hasRemoteChanged = result.remoteContent !== null; hasConflicts = result.hasConflicts; - conflictSettings = result.conflictsSettings; } // First time syncing to remote else if (fileContent) { - this.logService.trace('Settings: Remote settings does not exist. Synchronizing settings for the first time.'); + this.logService.trace(`${this.syncResourceLogLabel}: Remote settings does not exist. Synchronizing settings for the first time.`); content = fileContent.value.toString(); hasRemoteChanged = true; } @@ -340,10 +350,11 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement // Remove the ignored settings from the preview. const ignoredSettings = await this.getIgnoredSettings(); const previewContent = updateIgnoredSettings(content, '{}', ignoredSettings, formattingOptions); - await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(previewContent)); + await this.fileService.writeFile(this.localPreviewResource, VSBuffer.fromString(previewContent)); } - this.setConflicts(conflictSettings); + this.setConflicts(hasConflicts && !token.isCancellationRequested ? [{ local: this.localPreviewResource, remote: this.remotePreviewResource }] : []); + return { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged, hasConflicts }; } @@ -361,6 +372,10 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement return null; } + private toSettingsSyncContent(settings: string): ISettingsSyncContent { + return { settings }; + } + private _defaultIgnoredSettings: Promise | undefined = undefined; protected async getIgnoredSettings(content?: string): Promise { if (!this._defaultIgnoredSettings) { @@ -378,7 +393,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement private validateContent(content: string): void { if (this.hasErrors(content)) { - throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source); + throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.resource); } } } diff --git a/src/vs/platform/userDataSync/common/snippetsMerge.ts b/src/vs/platform/userDataSync/common/snippetsMerge.ts new file mode 100644 index 00000000000..42a9dfaae05 --- /dev/null +++ b/src/vs/platform/userDataSync/common/snippetsMerge.ts @@ -0,0 +1,202 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { values } from 'vs/base/common/map'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { deepClone } from 'vs/base/common/objects'; + +export interface IMergeResult { + added: IStringDictionary; + updated: IStringDictionary; + removed: string[]; + conflicts: string[]; + remote: IStringDictionary | null; +} + +export function merge(local: IStringDictionary, remote: IStringDictionary | null, base: IStringDictionary | null, resolvedConflicts: IStringDictionary = {}): IMergeResult { + const added: IStringDictionary = {}; + const updated: IStringDictionary = {}; + const removed: Set = new Set(); + + if (!remote) { + return { + added, + removed: values(removed), + updated, + conflicts: [], + remote: local + }; + } + + const localToRemote = compare(local, remote); + if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) { + // No changes found between local and remote. + return { + added, + removed: values(removed), + updated, + conflicts: [], + remote: null + }; + } + + const baseToLocal = compare(base, local); + const baseToRemote = compare(base, remote); + const remoteContent: IStringDictionary = deepClone(remote); + const conflicts: Set = new Set(); + const handledConflicts: Set = new Set(); + const handleConflict = (key: string): void => { + if (handledConflicts.has(key)) { + return; + } + handledConflicts.add(key); + const conflictContent = resolvedConflicts[key]; + + // add to conflicts + if (conflictContent === undefined) { + conflicts.add(key); + } + + // remove the snippet + else if (conflictContent === null) { + delete remote[key]; + if (local[key]) { + removed.add(key); + } + } + + // add/update the snippet + else { + if (local[key]) { + if (local[key] !== conflictContent) { + updated[key] = conflictContent; + } + } else { + added[key] = conflictContent; + } + remoteContent[key] = conflictContent; + } + }; + + // Removed snippets in Local + for (const key of values(baseToLocal.removed)) { + // Conflict - Got updated in remote. + if (baseToRemote.updated.has(key)) { + // Add to local + added[key] = remote[key]; + } + // Remove it in remote + else { + delete remoteContent[key]; + } + } + + // Removed snippets in Remote + for (const key of values(baseToRemote.removed)) { + if (handledConflicts.has(key)) { + continue; + } + // Conflict - Got updated in local + if (baseToLocal.updated.has(key)) { + handleConflict(key); + } + // Also remove in Local + else { + removed.add(key); + } + } + + // Updated snippets in Local + for (const key of values(baseToLocal.updated)) { + if (handledConflicts.has(key)) { + continue; + } + // Got updated in remote + if (baseToRemote.updated.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + handleConflict(key); + } + } else { + remoteContent[key] = local[key]; + } + } + + // Updated snippets in Remote + for (const key of values(baseToRemote.updated)) { + if (handledConflicts.has(key)) { + continue; + } + // Got updated in local + if (baseToLocal.updated.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + handleConflict(key); + } + } else if (local[key] !== undefined) { + updated[key] = remote[key]; + } + } + + // Added snippets in Local + for (const key of values(baseToLocal.added)) { + if (handledConflicts.has(key)) { + continue; + } + // Got added in remote + if (baseToRemote.added.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + handleConflict(key); + } + } else { + remoteContent[key] = local[key]; + } + } + + // Added snippets in remote + for (const key of values(baseToRemote.added)) { + if (handledConflicts.has(key)) { + continue; + } + // Got added in local + if (baseToLocal.added.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + handleConflict(key); + } + } else { + added[key] = remote[key]; + } + } + + return { added, removed: values(removed), updated, conflicts: values(conflicts), remote: areSame(remote, remoteContent) ? null : remoteContent }; +} + +function compare(from: IStringDictionary | null, to: IStringDictionary | null): { added: Set, removed: Set, updated: Set } { + const fromKeys = from ? Object.keys(from) : []; + const toKeys = to ? Object.keys(to) : []; + const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const updated: Set = new Set(); + + for (const key of fromKeys) { + if (removed.has(key)) { + continue; + } + const fromSnippet = from![key]!; + const toSnippet = to![key]!; + if (fromSnippet !== toSnippet) { + updated.add(key); + } + } + + return { added, removed, updated }; +} + +function areSame(a: IStringDictionary, b: IStringDictionary): boolean { + const { added, removed, updated } = compare(a, b); + return added.size === 0 && removed.size === 0 && updated.size === 0; +} diff --git a/src/vs/platform/userDataSync/common/snippetsSync.ts b/src/vs/platform/userDataSync/common/snippetsSync.ts new file mode 100644 index 00000000000..a055d21fd57 --- /dev/null +++ b/src/vs/platform/userDataSync/common/snippetsSync.ts @@ -0,0 +1,427 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSynchroniser, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, Conflict, USER_DATA_SYNC_SCHEME, PREVIEW_DIR_NAME, UserDataSyncError, UserDataSyncErrorCode, ISyncResourceHandle, ISyncPreviewResult } from 'vs/platform/userDataSync/common/userDataSync'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService, FileChangesEvent, IFileStat, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { AbstractSynchroniser, IRemoteUserData, ISyncData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { URI } from 'vs/base/common/uri'; +import { joinPath, extname, relativePath, isEqualOrParent, isEqual, basename, dirname } from 'vs/base/common/resources'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { merge } from 'vs/platform/userDataSync/common/snippetsMerge'; +import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; +import { CancellationToken } from 'vs/base/common/cancellation'; + +interface ISinppetsSyncPreviewResult extends ISyncPreviewResult { + readonly local: IStringDictionary; + readonly remoteUserData: IRemoteUserData; + readonly lastSyncUserData: IRemoteUserData | null; + readonly added: IStringDictionary; + readonly updated: IStringDictionary; + readonly removed: string[]; + readonly conflicts: Conflict[]; + readonly resolvedConflicts: IStringDictionary; + readonly remote: IStringDictionary | null; +} + +export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { + + protected readonly version: number = 1; + private readonly snippetsFolder: URI; + private readonly snippetsPreviewFolder: URI; + private syncPreviewResultPromise: CancelablePromise | null = null; + + constructor( + @IEnvironmentService environmentService: IEnvironmentService, + @IFileService fileService: IFileService, + @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, + @IConfigurationService configurationService: IConfigurationService, + @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, + @ITelemetryService telemetryService: ITelemetryService, + ) { + super(SyncResource.Snippets, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); + this.snippetsFolder = environmentService.snippetsHome; + this.snippetsPreviewFolder = joinPath(this.syncFolder, PREVIEW_DIR_NAME); + this._register(this.fileService.watch(environmentService.userRoamingDataHome)); + this._register(this.fileService.watch(this.snippetsFolder)); + this._register(this.fileService.onDidFilesChange(e => this.onFileChanges(e))); + } + + private onFileChanges(e: FileChangesEvent): void { + if (!e.changes.some(change => isEqualOrParent(change.resource, this.snippetsFolder))) { + return; + } + if (!this.isEnabled()) { + return; + } + // Sync again if local file has changed and current status is in conflicts + if (this.status === SyncStatus.HasConflicts) { + this.syncPreviewResultPromise!.then(result => { + this.cancel(); + this.doSync(result.remoteUserData, result.lastSyncUserData).then(status => this.setStatus(status)); + }); + } + // Otherwise fire change event + else { + this._onDidChangeLocal.fire(); + } + } + + async pull(): Promise { + if (!this.isEnabled()) { + this.logService.info(`${this.syncResourceLogLabel}: Skipped pulling snippets as it is disabled.`); + return; + } + + this.stop(); + + try { + this.logService.info(`${this.syncResourceLogLabel}: Started pulling snippets...`); + this.setStatus(SyncStatus.Syncing); + + const lastSyncUserData = await this.getLastSyncUserData(); + const remoteUserData = await this.getRemoteUserData(lastSyncUserData); + + if (remoteUserData.syncData !== null) { + const local = await this.getSnippetsFileContents(); + const localSnippets = this.toSnippetsContents(local); + const remoteSnippets = this.parseSnippets(remoteUserData.syncData); + const { added, updated, remote, removed } = merge(localSnippets, remoteSnippets, localSnippets); + this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ + added, removed, updated, remote, remoteUserData, local, lastSyncUserData, conflicts: [], resolvedConflicts: {}, + hasLocalChanged: Object.keys(added).length > 0 || removed.length > 0 || Object.keys(updated).length > 0, + hasRemoteChanged: remote !== null + })); + await this.apply(); + } + + // No remote exists to pull + else { + this.logService.info(`${this.syncResourceLogLabel}: Remote snippets does not exist.`); + } + + this.logService.info(`${this.syncResourceLogLabel}: Finished pulling snippets.`); + } finally { + this.setStatus(SyncStatus.Idle); + } + } + + async push(): Promise { + if (!this.isEnabled()) { + this.logService.info(`${this.syncResourceLogLabel}: Skipped pushing snippets as it is disabled.`); + return; + } + + this.stop(); + + try { + this.logService.info(`${this.syncResourceLogLabel}: Started pushing snippets...`); + this.setStatus(SyncStatus.Syncing); + + const local = await this.getSnippetsFileContents(); + const localSnippets = this.toSnippetsContents(local); + const { added, removed, updated, remote } = merge(localSnippets, null, null); + const lastSyncUserData = await this.getLastSyncUserData(); + const remoteUserData = await this.getRemoteUserData(lastSyncUserData); + this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ + added, removed, updated, remote, remoteUserData, local, lastSyncUserData, conflicts: [], resolvedConflicts: {}, + hasLocalChanged: Object.keys(added).length > 0 || removed.length > 0 || Object.keys(updated).length > 0, + hasRemoteChanged: remote !== null + })); + + await this.apply(true); + + this.logService.info(`${this.syncResourceLogLabel}: Finished pushing snippets.`); + } finally { + this.setStatus(SyncStatus.Idle); + } + + } + + async stop(): Promise { + await this.clearConflicts(); + this.cancel(); + this.logService.info(`${this.syncResourceLogLabel}: Stopped synchronizing ${this.syncResourceLogLabel}.`); + this.setStatus(SyncStatus.Idle); + } + + async getAssociatedResources({ uri }: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]> { + let content = await super.resolveContent(uri); + if (content) { + const syncData = this.parseSyncData(content); + if (syncData) { + const snippets = this.parseSnippets(syncData); + const result = []; + for (const snippet of Object.keys(snippets)) { + const resource = joinPath(uri, snippet); + const comparableResource = joinPath(this.snippetsFolder, snippet); + const exists = await this.fileService.exists(comparableResource); + result.push({ resource, comparableResource: exists ? comparableResource : undefined }); + } + return result; + } + } + return []; + } + + async resolveContent(uri: URI): Promise { + if (isEqualOrParent(uri.with({ scheme: this.syncFolder.scheme }), this.snippetsPreviewFolder)) { + return this.getConflictContent(uri); + } + let content = await super.resolveContent(uri); + if (content) { + return content; + } + content = await super.resolveContent(dirname(uri)); + if (content) { + const syncData = this.parseSyncData(content); + if (syncData) { + const snippets = this.parseSnippets(syncData); + return snippets[basename(uri)] || null; + } + } + return null; + } + + protected async getConflictContent(conflictResource: URI): Promise { + if (this.syncPreviewResultPromise) { + const result = await this.syncPreviewResultPromise; + const key = relativePath(this.snippetsPreviewFolder, conflictResource.with({ scheme: this.snippetsPreviewFolder.scheme }))!; + if (conflictResource.scheme === this.snippetsPreviewFolder.scheme) { + return result.local[key] ? result.local[key].value.toString() : null; + } else if (result.remoteUserData && result.remoteUserData.syncData) { + const snippets = this.parseSnippets(result.remoteUserData.syncData); + return snippets[key] || null; + } + } + return null; + } + + async acceptConflict(conflictResource: URI, content: string): Promise { + const conflict = this.conflicts.filter(({ local, remote }) => isEqual(local, conflictResource) || isEqual(remote, conflictResource))[0]; + if (this.status === SyncStatus.HasConflicts && conflict) { + const key = relativePath(this.snippetsPreviewFolder, conflict.local)!; + let previewResult = await this.syncPreviewResultPromise!; + this.cancel(); + previewResult.resolvedConflicts[key] = content || null; + this.syncPreviewResultPromise = createCancelablePromise(token => this.doGeneratePreview(previewResult.local, previewResult.remoteUserData, previewResult.lastSyncUserData, previewResult.resolvedConflicts, token)); + previewResult = await this.syncPreviewResultPromise; + this.setConflicts(previewResult.conflicts); + if (!this.conflicts.length) { + await this.apply(); + this.setStatus(SyncStatus.Idle); + } + } + } + + async hasLocalData(): Promise { + try { + const localSnippets = await this.getSnippetsFileContents(); + if (Object.keys(localSnippets).length) { + return true; + } + } catch (error) { + /* ignore error */ + } + return false; + } + + protected async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { + try { + const previewResult = await this.getPreview(remoteUserData, lastSyncUserData); + this.setConflicts(previewResult.conflicts); + if (this.conflicts.length) { + return SyncStatus.HasConflicts; + } + await this.apply(); + return SyncStatus.Idle; + } catch (e) { + this.syncPreviewResultPromise = null; + if (e instanceof UserDataSyncError) { + switch (e.code) { + case UserDataSyncErrorCode.LocalPreconditionFailed: + // Rejected as there is a new local version. Syncing again. + this.logService.info(`${this.syncResourceLogLabel}: Failed to synchronize snippets as there is a new local version available. Synchronizing again...`); + return this.performSync(remoteUserData, lastSyncUserData); + } + } + throw e; + } + } + + protected getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { + if (!this.syncPreviewResultPromise) { + this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(remoteUserData, lastSyncUserData, token)); + } + return this.syncPreviewResultPromise; + } + + protected cancel(): void { + if (this.syncPreviewResultPromise) { + this.syncPreviewResultPromise.cancel(); + this.syncPreviewResultPromise = null; + } + } + + private async clearConflicts(): Promise { + if (this.conflicts.length) { + await Promise.all(this.conflicts.map(({ local }) => this.fileService.del(local))); + this.setConflicts([]); + } + } + + protected async generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken = CancellationToken.None): Promise { + return this.getSnippetsFileContents() + .then(local => this.doGeneratePreview(local, remoteUserData, lastSyncUserData, {}, token)); + } + + private async doGeneratePreview(local: IStringDictionary, remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: IStringDictionary = {}, token: CancellationToken = CancellationToken.None): Promise { + const localSnippets = this.toSnippetsContents(local); + const remoteSnippets: IStringDictionary | null = remoteUserData.syncData ? this.parseSnippets(remoteUserData.syncData) : null; + const lastSyncSnippets: IStringDictionary | null = lastSyncUserData ? this.parseSnippets(lastSyncUserData.syncData!) : null; + + if (remoteSnippets) { + this.logService.trace(`${this.syncResourceLogLabel}: Merging remote snippets with local snippets...`); + } else { + this.logService.trace(`${this.syncResourceLogLabel}: Remote snippets does not exist. Synchronizing snippets for the first time.`); + } + + const mergeResult = merge(localSnippets, remoteSnippets, lastSyncSnippets, resolvedConflicts); + + const conflicts: Conflict[] = []; + for (const key of mergeResult.conflicts) { + const localPreview = joinPath(this.snippetsPreviewFolder, key); + conflicts.push({ local: localPreview, remote: localPreview.with({ scheme: USER_DATA_SYNC_SCHEME }) }); + const content = local[key]; + if (!token.isCancellationRequested) { + await this.fileService.writeFile(localPreview, content ? content.value : VSBuffer.fromString('')); + } + } + + for (const conflict of this.conflicts) { + // clear obsolete conflicts + if (!conflicts.some(({ local }) => isEqual(local, conflict.local))) { + try { + await this.fileService.del(conflict.local); + } catch (error) { + // Ignore & log + this.logService.error(error); + } + } + } + + return { + remoteUserData, local, + lastSyncUserData, + added: mergeResult.added, + removed: mergeResult.removed, + updated: mergeResult.updated, + conflicts, + remote: mergeResult.remote, + resolvedConflicts, + hasLocalChanged: Object.keys(mergeResult.added).length > 0 || mergeResult.removed.length > 0 || Object.keys(mergeResult.updated).length > 0, + hasRemoteChanged: mergeResult.remote !== null + }; + } + + private async apply(forcePush?: boolean): Promise { + if (!this.syncPreviewResultPromise) { + return; + } + + let { added, removed, updated, local, remote, remoteUserData, lastSyncUserData, hasLocalChanged, hasRemoteChanged } = await this.syncPreviewResultPromise; + + if (!hasLocalChanged && !hasRemoteChanged) { + this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing snippets.`); + } + + if (hasLocalChanged) { + // back up all snippets + await this.backupLocal(JSON.stringify(this.toSnippetsContents(local))); + await this.updateLocalSnippets(added, removed, updated, local); + } + + if (remote) { + // update remote + this.logService.trace(`${this.syncResourceLogLabel}: Updating remote snippets...`); + const content = JSON.stringify(remote); + remoteUserData = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref); + this.logService.info(`${this.syncResourceLogLabel}: Updated remote snippets`); + } + + if (lastSyncUserData?.ref !== remoteUserData.ref) { + // update last sync + this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized snippets...`); + await this.updateLastSyncUserData(remoteUserData); + this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized snippets`); + } + + this.syncPreviewResultPromise = null; + } + + private async updateLocalSnippets(added: IStringDictionary, removed: string[], updated: IStringDictionary, local: IStringDictionary): Promise { + for (const key of removed) { + const resource = joinPath(this.snippetsFolder, key); + this.logService.trace(`${this.syncResourceLogLabel}: Deleting snippet...`, basename(resource)); + await this.fileService.del(resource); + this.logService.info(`${this.syncResourceLogLabel}: Deleted snippet`, basename(resource)); + } + + for (const key of Object.keys(added)) { + const resource = joinPath(this.snippetsFolder, key); + this.logService.trace(`${this.syncResourceLogLabel}: Creating snippet...`, basename(resource)); + await this.fileService.createFile(resource, VSBuffer.fromString(added[key]), { overwrite: false }); + this.logService.info(`${this.syncResourceLogLabel}: Created snippet`, basename(resource)); + } + + for (const key of Object.keys(updated)) { + const resource = joinPath(this.snippetsFolder, key); + this.logService.trace(`${this.syncResourceLogLabel}: Updating snippet...`, basename(resource)); + await this.fileService.writeFile(resource, VSBuffer.fromString(updated[key]), local[key]); + this.logService.info(`${this.syncResourceLogLabel}: Updated snippet`, basename(resource)); + } + } + + private parseSnippets(syncData: ISyncData): IStringDictionary { + return JSON.parse(syncData.content); + } + + private toSnippetsContents(snippetsFileContents: IStringDictionary): IStringDictionary { + const snippets: IStringDictionary = {}; + for (const key of Object.keys(snippetsFileContents)) { + snippets[key] = snippetsFileContents[key].value.toString(); + } + return snippets; + } + + private async getSnippetsFileContents(): Promise> { + const snippets: IStringDictionary = {}; + let stat: IFileStat; + try { + stat = await this.fileService.resolve(this.snippetsFolder); + } catch (e) { + // No snippets + if (e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { + return snippets; + } else { + throw e; + } + } + for (const entry of stat.children || []) { + const resource = entry.resource; + if (extname(resource) === '.json') { + const key = relativePath(this.snippetsFolder, resource)!; + const content = await this.fileService.readFile(resource); + snippets[key] = content; + } + } + return snippets; + } +} diff --git a/src/vs/platform/userDataSync/common/storageKeys.ts b/src/vs/platform/userDataSync/common/storageKeys.ts new file mode 100644 index 00000000000..43c150d4b3a --- /dev/null +++ b/src/vs/platform/userDataSync/common/storageKeys.ts @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event, Emitter } from 'vs/base/common/event'; +import { values } from 'vs/base/common/map'; +import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export interface IStorageKey { + + readonly key: string; + readonly version: number; + +} + +export const IStorageKeysSyncRegistryService = createDecorator('IStorageKeysSyncRegistryService'); + +export interface IStorageKeysSyncRegistryService { + + _serviceBrand: any; + + /** + * All registered storage keys + */ + readonly storageKeys: ReadonlyArray; + + /** + * Event that is triggered when storage keys are changed + */ + readonly onDidChangeStorageKeys: Event>; + + /** + * Register a storage key that has to be synchronized during sync. + */ + registerStorageKey(key: IStorageKey): void; + +} + +export class StorageKeysSyncRegistryService extends Disposable implements IStorageKeysSyncRegistryService { + + _serviceBrand: any; + + private readonly _storageKeys = new Map(); + get storageKeys(): ReadonlyArray { return values(this._storageKeys); } + + private readonly _onDidChangeStorageKeys: Emitter> = this._register(new Emitter>()); + readonly onDidChangeStorageKeys = this._onDidChangeStorageKeys.event; + + constructor() { + super(); + this._register(toDisposable(() => this._storageKeys.clear())); + } + + registerStorageKey(storageKey: IStorageKey): void { + if (!this._storageKeys.has(storageKey.key)) { + this._storageKeys.set(storageKey.key, storageKey); + this._onDidChangeStorageKeys.fire(this.storageKeys); + } + } + +} diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index b2911d2a364..f88be9c8d93 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -18,7 +18,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IStringDictionary } from 'vs/base/common/collections'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { URI } from 'vs/base/common/uri'; -import { isEqual, joinPath } from 'vs/base/common/resources'; +import { joinPath, isEqualOrParent } from 'vs/base/common/resources'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IProductService } from 'vs/platform/product/common/productService'; import { distinct } from 'vs/base/common/arrays'; @@ -65,7 +65,7 @@ export function registerConfiguration(): IDisposable { description: localize('sync.keybindingsPerPlatform', "Synchronize keybindings per platform."), default: true, scope: ConfigurationScope.APPLICATION, - tags: ['sync'] + tags: ['sync', 'usesOnlineServices'] }, 'sync.ignoredExtensions': { 'type': 'array', @@ -75,7 +75,7 @@ export function registerConfiguration(): IDisposable { 'scope': ConfigurationScope.APPLICATION, uniqueItems: true, disallowSyncIgnore: true, - tags: ['sync'] + tags: ['sync', 'usesOnlineServices'] }, 'sync.ignoredSettings': { 'type': 'array', @@ -86,7 +86,7 @@ export function registerConfiguration(): IDisposable { additionalProperties: true, uniqueItems: true, disallowSyncIgnore: true, - tags: ['sync'] + tags: ['sync', 'usesOnlineServices'] } } }); @@ -125,7 +125,7 @@ export interface IUserDataSyncStore { } export function getUserDataSyncStore(productService: IProductService, configurationService: IConfigurationService): IUserDataSyncStore | undefined { - const value = productService[CONFIGURATION_SYNC_STORE_KEY] || configurationService.getValue<{ url: string, authenticationProviderId: string }>(CONFIGURATION_SYNC_STORE_KEY); + const value = configurationService.getValue<{ url: string, authenticationProviderId: string }>(CONFIGURATION_SYNC_STORE_KEY) || productService[CONFIGURATION_SYNC_STORE_KEY]; if (value && value.url && value.authenticationProviderId) { return { url: joinPath(URI.parse(value.url), 'v1'), @@ -135,22 +135,44 @@ export function getUserDataSyncStore(productService: IProductService, configurat return undefined; } -export const ALL_RESOURCE_KEYS: ResourceKey[] = ['settings', 'keybindings', 'extensions', 'globalState']; -export type ResourceKey = 'settings' | 'keybindings' | 'extensions' | 'globalState'; +export const enum SyncResource { + Settings = 'settings', + Keybindings = 'keybindings', + Snippets = 'snippets', + Extensions = 'extensions', + GlobalState = 'globalState' +} +export const ALL_SYNC_RESOURCES: SyncResource[] = [SyncResource.Settings, SyncResource.Keybindings, SyncResource.Snippets, SyncResource.Extensions, SyncResource.GlobalState]; export interface IUserDataManifest { - latest?: Record + latest?: Record session: string; } +export interface IResourceRefHandle { + ref: string; + created: number; +} + export const IUserDataSyncStoreService = createDecorator('IUserDataSyncStoreService'); export interface IUserDataSyncStoreService { _serviceBrand: undefined; readonly userDataSyncStore: IUserDataSyncStore | undefined; - read(key: ResourceKey, oldValue: IUserData | null, source?: SyncSource): Promise; - write(key: ResourceKey, content: string, ref: string | null, source?: SyncSource): Promise; + read(resource: SyncResource, oldValue: IUserData | null): Promise; + write(resource: SyncResource, content: string, ref: string | null): Promise; manifest(): Promise; clear(): Promise; + getAllRefs(resource: SyncResource): Promise; + resolveContent(resource: SyncResource, ref: string): Promise; + delete(resource: SyncResource): Promise; +} + +export const IUserDataSyncBackupStoreService = createDecorator('IUserDataSyncBackupStoreService'); +export interface IUserDataSyncBackupStoreService { + _serviceBrand: undefined; + backup(resource: SyncResource, content: string): Promise; + getAllRefs(resource: SyncResource): Promise; + resolveContent(resource: SyncResource, ref?: string): Promise; } //#endregion @@ -179,9 +201,9 @@ export enum UserDataSyncErrorCode { export class UserDataSyncError extends Error { - constructor(message: string, public readonly code: UserDataSyncErrorCode, public readonly source?: SyncSource) { + constructor(message: string, public readonly code: UserDataSyncErrorCode, public readonly resource?: SyncResource) { super(message); - this.name = `${this.code} (UserDataSyncError) ${this.source}`; + this.name = `${this.code} (UserDataSyncError) ${this.resource}`; } static toUserDataSyncError(error: Error): UserDataSyncError { @@ -190,7 +212,7 @@ export class UserDataSyncError extends Error { } const match = /^(.+) \(UserDataSyncError\) (.+)?$/.exec(error.name); if (match && match[1]) { - return new UserDataSyncError(error.message, match[1], match[2]); + return new UserDataSyncError(error.message, match[1], match[2]); } return new UserDataSyncError(error.message, UserDataSyncErrorCode.Unknown); } @@ -209,16 +231,13 @@ export interface ISyncExtension { disabled?: boolean; } -export interface IGlobalState { - argv: IStringDictionary; - storage: IStringDictionary; +export interface IStorageValue { + version: number; + value: string; } -export const enum SyncSource { - Settings = 'Settings', - Keybindings = 'Keybindings', - Extensions = 'Extensions', - GlobalState = 'GlobalState' +export interface IGlobalState { + storage: IStringDictionary; } export const enum SyncStatus { @@ -228,12 +247,25 @@ export const enum SyncStatus { HasConflicts = 'hasConflicts', } +export interface ISyncResourceHandle { + created: number; + uri: URI; +} + +export type Conflict = { remote: URI, local: URI }; + +export interface ISyncPreviewResult { + readonly hasLocalChanged: boolean; + readonly hasRemoteChanged: boolean; +} + export interface IUserDataSynchroniser { - readonly resourceKey: ResourceKey; - readonly source: SyncSource; + readonly resource: SyncResource; readonly status: SyncStatus; readonly onDidChangeStatus: Event; + readonly conflicts: Conflict[]; + readonly onDidChangeConflicts: Event; readonly onDidChangeLocal: Event; pull(): Promise; @@ -241,12 +273,17 @@ export interface IUserDataSynchroniser { sync(ref?: string): Promise; stop(): Promise; + getSyncPreview(): Promise hasPreviouslySynced(): Promise hasLocalData(): Promise; resetLocal(): Promise; - getRemoteContent(preivew?: boolean): Promise; - accept(content: string): Promise; + resolveContent(resource: URI): Promise; + acceptConflict(conflictResource: URI, content: string): Promise; + + getRemoteSyncResourceHandles(): Promise; + getLocalSyncResourceHandles(): Promise; + getAssociatedResources(syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]>; } //#endregion @@ -258,15 +295,17 @@ export interface IUserDataSyncEnablementService { _serviceBrand: any; readonly onDidChangeEnablement: Event; - readonly onDidChangeResourceEnablement: Event<[ResourceKey, boolean]>; + readonly onDidChangeResourceEnablement: Event<[SyncResource, boolean]>; isEnabled(): boolean; setEnablement(enabled: boolean): void; - isResourceEnabled(key: ResourceKey): boolean; - setResourceEnablement(key: ResourceKey, enabled: boolean): void; + isResourceEnabled(resource: SyncResource): boolean; + setResourceEnablement(resource: SyncResource, enabled: boolean): void; } +export type SyncResourceConflicts = { syncResource: SyncResource, conflicts: Conflict[] }; + export const IUserDataSyncService = createDecorator('IUserDataSyncService'); export interface IUserDataSyncService { _serviceBrand: any; @@ -274,11 +313,11 @@ export interface IUserDataSyncService { readonly status: SyncStatus; readonly onDidChangeStatus: Event; - readonly conflictsSources: SyncSource[]; - readonly onDidChangeConflicts: Event; + readonly conflicts: SyncResourceConflicts[]; + readonly onDidChangeConflicts: Event; - readonly onDidChangeLocal: Event; - readonly onSyncErrors: Event<[SyncSource, UserDataSyncError][]>; + readonly onDidChangeLocal: Event; + readonly onSyncErrors: Event<[SyncResource, UserDataSyncError][]>; readonly lastSyncTime: number | undefined; readonly onDidChangeLastSyncTime: Event; @@ -290,8 +329,12 @@ export interface IUserDataSyncService { resetLocal(): Promise; isFirstTimeSyncWithMerge(): Promise; - getRemoteContent(source: SyncSource, preview: boolean): Promise; - accept(source: SyncSource, content: string): Promise; + resolveContent(resource: URI): Promise; + acceptConflict(conflictResource: URI, content: string): Promise; + + getLocalSyncResourceHandles(resource: SyncResource): Promise; + getRemoteSyncResourceHandles(resource: SyncResource): Promise; + getAssociatedResources(resource: SyncResource, syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]>; } export const IUserDataAutoSyncService = createDecorator('IUserDataAutoSyncService'); @@ -318,32 +361,17 @@ export interface IConflictSetting { remoteValue: any | undefined; } -export const ISettingsSyncService = createDecorator('ISettingsSyncService'); -export interface ISettingsSyncService extends IUserDataSynchroniser { - _serviceBrand: any; - readonly onDidChangeConflicts: Event; - readonly conflicts: IConflictSetting[]; - resolveSettingsConflicts(resolvedConflicts: { key: string, value: any | undefined }[]): Promise; -} - //#endregion +export const USER_DATA_SYNC_SCHEME = 'vscode-userdata-sync'; export const CONTEXT_SYNC_STATE = new RawContextKey('syncStatus', SyncStatus.Uninitialized); export const CONTEXT_SYNC_ENABLEMENT = new RawContextKey('syncEnabled', false); -export const USER_DATA_SYNC_SCHEME = 'vscode-userdata-sync'; -export function toRemoteContentResource(source: SyncSource): URI { - return URI.from({ scheme: USER_DATA_SYNC_SCHEME, path: `${source}/remoteContent` }); -} -export function getSyncSourceFromRemoteContentResource(uri: URI): SyncSource | undefined { - return [SyncSource.Settings, SyncSource.Keybindings, SyncSource.Extensions, SyncSource.GlobalState].filter(source => isEqual(uri, toRemoteContentResource(source)))[0]; -} -export function getSyncSourceFromPreviewResource(uri: URI, environmentService: IEnvironmentService): SyncSource | undefined { - if (isEqual(uri, environmentService.settingsSyncPreviewResource)) { - return SyncSource.Settings; +export const PREVIEW_DIR_NAME = 'preview'; +export function getSyncResourceFromLocalPreview(localPreview: URI, environmentService: IEnvironmentService): SyncResource | undefined { + if (localPreview.scheme === USER_DATA_SYNC_SCHEME) { + return undefined; } - if (isEqual(uri, environmentService.keybindingsSyncPreviewResource)) { - return SyncSource.Keybindings; - } - return undefined; + localPreview = localPreview.with({ scheme: environmentService.userDataSyncHome.scheme }); + return ALL_SYNC_RESOURCES.filter(syncResource => isEqualOrParent(localPreview, joinPath(environmentService.userDataSyncHome, syncResource, PREVIEW_DIR_NAME)))[0]; } diff --git a/src/vs/platform/userDataSync/common/userDataSyncBackupStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncBackupStoreService.ts new file mode 100644 index 00000000000..8f9333c9600 --- /dev/null +++ b/src/vs/platform/userDataSync/common/userDataSyncBackupStoreService.ts @@ -0,0 +1,109 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, } from 'vs/base/common/lifecycle'; +import { IUserDataSyncLogService, ALL_SYNC_RESOURCES, IUserDataSyncBackupStoreService, IResourceRefHandle, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { joinPath } from 'vs/base/common/resources'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IFileService, IFileStat } from 'vs/platform/files/common/files'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { toLocalISOString } from 'vs/base/common/date'; +import { VSBuffer } from 'vs/base/common/buffer'; + +export class UserDataSyncBackupStoreService extends Disposable implements IUserDataSyncBackupStoreService { + + _serviceBrand: any; + + constructor( + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IFileService private readonly fileService: IFileService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + ) { + super(); + ALL_SYNC_RESOURCES.forEach(resourceKey => this.cleanUpBackup(resourceKey)); + } + + async getAllRefs(resource: SyncResource): Promise { + const folder = joinPath(this.environmentService.userDataSyncHome, resource); + const stat = await this.fileService.resolve(folder); + if (stat.children) { + const all = stat.children.filter(stat => stat.isFile && /^\d{8}T\d{6}(\.json)?$/.test(stat.name)).sort().reverse(); + return all.map(stat => ({ + ref: stat.name, + created: this.getCreationTime(stat) + })); + } + return []; + } + + async resolveContent(resource: SyncResource, ref?: string): Promise { + if (!ref) { + const refs = await this.getAllRefs(resource); + if (refs.length) { + ref = refs[refs.length - 1].ref; + } + } + if (ref) { + const file = joinPath(this.environmentService.userDataSyncHome, resource, ref); + const content = await this.fileService.readFile(file); + return content.value.toString(); + } + return null; + } + + async backup(resourceKey: SyncResource, content: string): Promise { + const folder = joinPath(this.environmentService.userDataSyncHome, resourceKey); + const resource = joinPath(folder, `${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}.json`); + try { + await this.fileService.writeFile(resource, VSBuffer.fromString(content)); + } catch (e) { + this.logService.error(e); + } + try { + this.cleanUpBackup(resourceKey); + } catch (e) { /* Ignore */ } + } + + private async cleanUpBackup(resource: SyncResource): Promise { + const folder = joinPath(this.environmentService.userDataSyncHome, resource); + try { + try { + if (!(await this.fileService.exists(folder))) { + return; + } + } catch (e) { + return; + } + const stat = await this.fileService.resolve(folder); + if (stat.children) { + const all = stat.children.filter(stat => stat.isFile && /^\d{8}T\d{6}(\.json)?$/.test(stat.name)).sort(); + const backUpMaxAge = 1000 * 60 * 60 * 24 * (this.configurationService.getValue('sync.localBackupDuration') || 30 /* Default 30 days */); + let toDelete = all.filter(stat => Date.now() - this.getCreationTime(stat) > backUpMaxAge); + const remaining = all.length - toDelete.length; + if (remaining < 10) { + toDelete = toDelete.slice(10 - remaining); + } + await Promise.all(toDelete.map(stat => { + this.logService.info('Deleting from backup', stat.resource.path); + this.fileService.del(stat.resource); + })); + } + } catch (e) { + this.logService.error(e); + } + } + + private getCreationTime(stat: IFileStat) { + return stat.ctime || new Date( + parseInt(stat.name.substring(0, 4)), + parseInt(stat.name.substring(4, 6)) - 1, + parseInt(stat.name.substring(6, 8)), + parseInt(stat.name.substring(9, 11)), + parseInt(stat.name.substring(11, 13)), + parseInt(stat.name.substring(13, 15)) + ).getTime(); + } +} diff --git a/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts b/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts index 7adbf97471b..250dcbe66eb 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts @@ -3,18 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncEnablementService, ResourceKey, ALL_RESOURCE_KEYS } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncEnablementService, ALL_SYNC_RESOURCES, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; import { IStorageService, IWorkspaceStorageChangeEvent, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; type SyncEnablementClassification = { enabled?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; }; const enablementKey = 'sync.enable'; -function getEnablementKey(resourceKey: ResourceKey) { return `${enablementKey}.${resourceKey}`; } +function getEnablementKey(resource: SyncResource) { return `${enablementKey}.${resource}`; } export class UserDataSyncEnablementService extends Disposable implements IUserDataSyncEnablementService { @@ -23,14 +24,23 @@ export class UserDataSyncEnablementService extends Disposable implements IUserDa private _onDidChangeEnablement = new Emitter(); readonly onDidChangeEnablement: Event = this._onDidChangeEnablement.event; - private _onDidChangeResourceEnablement = new Emitter<[ResourceKey, boolean]>(); - readonly onDidChangeResourceEnablement: Event<[ResourceKey, boolean]> = this._onDidChangeResourceEnablement.event; + private _onDidChangeResourceEnablement = new Emitter<[SyncResource, boolean]>(); + readonly onDidChangeResourceEnablement: Event<[SyncResource, boolean]> = this._onDidChangeResourceEnablement.event; constructor( @IStorageService private readonly storageService: IStorageService, @ITelemetryService private readonly telemetryService: ITelemetryService, + @IEnvironmentService environmentService: IEnvironmentService, ) { super(); + switch (environmentService.args['sync']) { + case 'on': + this.setEnablement(true); + break; + case 'off': + this.setEnablement(false); + break; + } this._register(storageService.onDidChangeStorage(e => this.onDidStorageChange(e))); } @@ -45,13 +55,13 @@ export class UserDataSyncEnablementService extends Disposable implements IUserDa } } - isResourceEnabled(resourceKey: ResourceKey): boolean { - return this.storageService.getBoolean(getEnablementKey(resourceKey), StorageScope.GLOBAL, true); + isResourceEnabled(resource: SyncResource): boolean { + return this.storageService.getBoolean(getEnablementKey(resource), StorageScope.GLOBAL, true); } - setResourceEnablement(resourceKey: ResourceKey, enabled: boolean): void { - if (this.isResourceEnabled(resourceKey) !== enabled) { - const resourceEnablementKey = getEnablementKey(resourceKey); + setResourceEnablement(resource: SyncResource, enabled: boolean): void { + if (this.isResourceEnabled(resource) !== enabled) { + const resourceEnablementKey = getEnablementKey(resource); this.telemetryService.publicLog2<{ enabled: boolean }, SyncEnablementClassification>(resourceEnablementKey, { enabled }); this.storageService.store(resourceEnablementKey, enabled, StorageScope.GLOBAL); } @@ -63,7 +73,7 @@ export class UserDataSyncEnablementService extends Disposable implements IUserDa this._onDidChangeEnablement.fire(this.isEnabled()); return; } - const resourceKey = ALL_RESOURCE_KEYS.filter(resourceKey => getEnablementKey(resourceKey) === workspaceStorageChangeEvent.key)[0]; + const resourceKey = ALL_SYNC_RESOURCES.filter(resourceKey => getEnablementKey(resourceKey) === workspaceStorageChangeEvent.key)[0]; if (resourceKey) { this._onDidChangeResourceEnablement.fire([resourceKey, this.isEnabled()]); return; diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index a2b8c25e64d..1aeeecf920f 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -4,11 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { Event } from 'vs/base/common/event'; -import { IUserDataSyncService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync'; +import { Event, Emitter } from 'vs/base/common/event'; +import { IUserDataSyncService, IUserDataSyncUtilService, IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync'; import { URI } from 'vs/base/common/uri'; import { IStringDictionary } from 'vs/base/common/collections'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; +import { IStorageKeysSyncRegistryService, IStorageKey } from 'vs/platform/userDataSync/common/storageKeys'; +import { Disposable } from 'vs/base/common/lifecycle'; export class UserDataSyncChannel implements IServerChannel { @@ -27,47 +29,18 @@ export class UserDataSyncChannel implements IServerChannel { call(context: any, command: string, args?: any): Promise { switch (command) { - case '_getInitialData': return Promise.resolve([this.service.status, this.service.conflictsSources, this.service.lastSyncTime]); - case 'sync': return this.service.sync(); - case 'accept': return this.service.accept(args[0], args[1]); + case '_getInitialData': return Promise.resolve([this.service.status, this.service.conflicts, this.service.lastSyncTime]); case 'pull': return this.service.pull(); + case 'sync': return this.service.sync(); case 'stop': this.service.stop(); return Promise.resolve(); case 'reset': return this.service.reset(); case 'resetLocal': return this.service.resetLocal(); - case 'getRemoteContent': return this.service.getRemoteContent(args[0], args[1]); case 'isFirstTimeSyncWithMerge': return this.service.isFirstTimeSyncWithMerge(); - } - throw new Error('Invalid call'); - } -} - -export class SettingsSyncChannel implements IServerChannel { - - constructor(private readonly service: ISettingsSyncService) { } - - listen(_: unknown, event: string): Event { - switch (event) { - case 'onDidChangeStatus': return this.service.onDidChangeStatus; - case 'onDidChangeLocal': return this.service.onDidChangeLocal; - case 'onDidChangeConflicts': return this.service.onDidChangeConflicts; - } - throw new Error(`Event not found: ${event}`); - } - - call(context: any, command: string, args?: any): Promise { - switch (command) { - case 'sync': return this.service.sync(); - case 'accept': return this.service.accept(args[0]); - case 'pull': return this.service.pull(); - case 'push': return this.service.push(); - case '_getInitialStatus': return Promise.resolve(this.service.status); - case '_getInitialConflicts': return Promise.resolve(this.service.conflicts); - case 'stop': this.service.stop(); return Promise.resolve(); - case 'resetLocal': return this.service.resetLocal(); - case 'hasPreviouslySynced': return this.service.hasPreviouslySynced(); - case 'hasLocalData': return this.service.hasLocalData(); - case 'resolveSettingsConflicts': return this.service.resolveSettingsConflicts(args[0]); - case 'getRemoteContent': return this.service.getRemoteContent(args[0]); + case 'acceptConflict': return this.service.acceptConflict(URI.revive(args[0]), args[1]); + case 'resolveContent': return this.service.resolveContent(URI.revive(args[0])); + case 'getLocalSyncResourceHandles': return this.service.getLocalSyncResourceHandles(args[0]); + case 'getRemoteSyncResourceHandles': return this.service.getRemoteSyncResourceHandles(args[0]); + case 'getAssociatedResources': return this.service.getAssociatedResources(args[0], { created: args[1].created, uri: URI.revive(args[1].uri) }); } throw new Error('Invalid call'); } @@ -131,3 +104,50 @@ export class UserDataSyncUtilServiceClient implements IUserDataSyncUtilService { } +export class StorageKeysSyncRegistryChannel implements IServerChannel { + + constructor(private readonly service: IStorageKeysSyncRegistryService) { } + + listen(_: unknown, event: string): Event { + switch (event) { + case 'onDidChangeStorageKeys': return this.service.onDidChangeStorageKeys; + } + throw new Error(`Event not found: ${event}`); + } + + call(context: any, command: string, args?: any): Promise { + switch (command) { + case '_getInitialData': return Promise.resolve(this.service.storageKeys); + case 'registerStorageKey': return Promise.resolve(this.service.registerStorageKey(args[0])); + } + throw new Error('Invalid call'); + } +} + +export class StorageKeysSyncRegistryChannelClient extends Disposable implements IStorageKeysSyncRegistryService { + + _serviceBrand: undefined; + + private _storageKeys: ReadonlyArray = []; + get storageKeys(): ReadonlyArray { return this._storageKeys; } + private readonly _onDidChangeStorageKeys: Emitter> = this._register(new Emitter>()); + readonly onDidChangeStorageKeys = this._onDidChangeStorageKeys.event; + + constructor(private readonly channel: IChannel) { + super(); + this.channel.call('_getInitialData').then(storageKeys => { + this.updateStorageKeys(storageKeys); + this._register(this.channel.listen>('onDidChangeStorageKeys')(storageKeys => this.updateStorageKeys(storageKeys))); + }); + } + + private async updateStorageKeys(storageKeys: ReadonlyArray): Promise { + this._storageKeys = storageKeys; + this._onDidChangeStorageKeys.fire(this.storageKeys); + } + + registerStorageKey(storageKey: IStorageKey): void { + this.channel.call('registerStorageKey', [storageKey]); + } + +} diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 4d0a2619afe..1c1bd056006 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncSource, ISettingsSyncService, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncResource, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError, SyncResourceConflicts, ISyncResourceHandle } from 'vs/platform/userDataSync/common/userDataSync'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Emitter, Event } from 'vs/base/common/event'; @@ -15,6 +15,10 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { equals } from 'vs/base/common/arrays'; import { localize } from 'vs/nls'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { URI } from 'vs/base/common/uri'; +import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; +import { isEqual } from 'vs/base/common/resources'; +import { SnippetsSynchroniser } from 'vs/platform/userDataSync/common/snippetsSync'; type SyncErrorClassification = { source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; @@ -34,47 +38,51 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ private _onDidChangeStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; - readonly onDidChangeLocal: Event; + readonly onDidChangeLocal: Event; - private _conflictsSources: SyncSource[] = []; - get conflictsSources(): SyncSource[] { return this._conflictsSources; } - private _onDidChangeConflicts: Emitter = this._register(new Emitter()); - readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; + private _conflicts: SyncResourceConflicts[] = []; + get conflicts(): SyncResourceConflicts[] { return this._conflicts; } + private _onDidChangeConflicts: Emitter = this._register(new Emitter()); + readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; - private _syncErrors: [SyncSource, UserDataSyncError][] = []; - private _onSyncErrors: Emitter<[SyncSource, UserDataSyncError][]> = this._register(new Emitter<[SyncSource, UserDataSyncError][]>()); - readonly onSyncErrors: Event<[SyncSource, UserDataSyncError][]> = this._onSyncErrors.event; + private _syncErrors: [SyncResource, UserDataSyncError][] = []; + private _onSyncErrors: Emitter<[SyncResource, UserDataSyncError][]> = this._register(new Emitter<[SyncResource, UserDataSyncError][]>()); + readonly onSyncErrors: Event<[SyncResource, UserDataSyncError][]> = this._onSyncErrors.event; private _lastSyncTime: number | undefined = undefined; get lastSyncTime(): number | undefined { return this._lastSyncTime; } private _onDidChangeLastSyncTime: Emitter = this._register(new Emitter()); readonly onDidChangeLastSyncTime: Event = this._onDidChangeLastSyncTime.event; + private readonly settingsSynchroniser: SettingsSynchroniser; private readonly keybindingsSynchroniser: KeybindingsSynchroniser; + private readonly snippetsSynchroniser: SnippetsSynchroniser; private readonly extensionsSynchroniser: ExtensionsSynchroniser; private readonly globalStateSynchroniser: GlobalStateSynchroniser; constructor( @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @ISettingsSyncService private readonly settingsSynchroniser: ISettingsSyncService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IStorageService private readonly storageService: IStorageService, + @IStorageService private readonly storageService: IStorageService ) { super(); + this.settingsSynchroniser = this._register(this.instantiationService.createInstance(SettingsSynchroniser)); this.keybindingsSynchroniser = this._register(this.instantiationService.createInstance(KeybindingsSynchroniser)); + this.snippetsSynchroniser = this._register(this.instantiationService.createInstance(SnippetsSynchroniser)); this.globalStateSynchroniser = this._register(this.instantiationService.createInstance(GlobalStateSynchroniser)); this.extensionsSynchroniser = this._register(this.instantiationService.createInstance(ExtensionsSynchroniser)); - this.synchronisers = [this.settingsSynchroniser, this.keybindingsSynchroniser, this.globalStateSynchroniser, this.extensionsSynchroniser]; + this.synchronisers = [this.settingsSynchroniser, this.keybindingsSynchroniser, this.snippetsSynchroniser, this.globalStateSynchroniser, this.extensionsSynchroniser]; this.updateStatus(); if (this.userDataSyncStoreService.userDataSyncStore) { this._register(Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeStatus, () => undefined)))(() => this.updateStatus())); + this._register(Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeConflicts, () => undefined)))(() => this.updateConflicts())); } this._lastSyncTime = this.storageService.getNumber(LAST_SYNC_TIME_KEY, StorageScope.GLOBAL, undefined); - this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeLocal, () => s.source))); + this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeLocal, () => s.resource))); } async pull(): Promise { @@ -83,7 +91,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ try { await synchroniser.pull(); } catch (e) { - this.handleSyncError(e, synchroniser.source); + this.handleSyncError(e, synchroniser.resource); } } this.updateLastSyncTime(); @@ -95,7 +103,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ try { await synchroniser.push(); } catch (e) { - this.handleSyncError(e, synchroniser.source); + this.handleSyncError(e, synchroniser.resource); } } this.updateLastSyncTime(); @@ -128,10 +136,10 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ for (const synchroniser of this.synchronisers) { try { - await synchroniser.sync(manifest && manifest.latest ? manifest.latest[synchroniser.resourceKey] : undefined); + await synchroniser.sync(manifest && manifest.latest ? manifest.latest[synchroniser.resource] : undefined); } catch (e) { - this.handleSyncError(e, synchroniser.source); - this._syncErrors.push([synchroniser.source, UserDataSyncError.toUserDataSyncError(e)]); + this.handleSyncError(e, synchroniser.resource); + this._syncErrors.push([synchroniser.resource, UserDataSyncError.toUserDataSyncError(e)]); } } @@ -170,22 +178,37 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } } - async accept(source: SyncSource, content: string): Promise { + async acceptConflict(conflict: URI, content: string): Promise { await this.checkEnablement(); - const synchroniser = this.getSynchroniser(source); - await synchroniser.accept(content); + const syncResourceConflict = this.conflicts.filter(({ conflicts }) => conflicts.some(({ local, remote }) => isEqual(conflict, local) || isEqual(conflict, remote)))[0]; + if (syncResourceConflict) { + const synchroniser = this.getSynchroniser(syncResourceConflict.syncResource); + await synchroniser.acceptConflict(conflict, content); + } } - async getRemoteContent(source: SyncSource, preview: boolean): Promise { - await this.checkEnablement(); + async resolveContent(resource: URI): Promise { for (const synchroniser of this.synchronisers) { - if (synchroniser.source === source) { - return synchroniser.getRemoteContent(preview); + const content = await synchroniser.resolveContent(resource); + if (content) { + return content; } } return null; } + getRemoteSyncResourceHandles(resource: SyncResource): Promise { + return this.getSynchroniser(resource).getRemoteSyncResourceHandles(); + } + + getLocalSyncResourceHandles(resource: SyncResource): Promise { + return this.getSynchroniser(resource).getLocalSyncResourceHandles(); + } + + getAssociatedResources(resource: SyncResource, syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]> { + return this.getSynchroniser(resource).getAssociatedResources(syncResourceHandle); + } + async isFirstTimeSyncWithMerge(): Promise { await this.checkEnablement(); if (!await this.userDataSyncStoreService.manifest()) { @@ -194,7 +217,16 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ if (await this.hasPreviouslySynced()) { return false; } - return await this.hasLocalData(); + if (!(await this.hasLocalData())) { + return false; + } + for (const synchroniser of [this.settingsSynchroniser, this.keybindingsSynchroniser, this.snippetsSynchroniser, this.extensionsSynchroniser]) { + const preview = await synchroniser.getSyncPreview(); + if (preview.hasLocalChanged || preview.hasRemoteChanged) { + return true; + } + } + return false; } async reset(): Promise { @@ -211,7 +243,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ try { synchroniser.resetLocal(); } catch (e) { - this.logService.error(`${synchroniser.source}: ${toErrorMessage(e)}`); + this.logService.error(`${synchroniser.resource}: ${toErrorMessage(e)}`); this.logService.error(e); } } @@ -256,15 +288,19 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } private updateStatus(): void { - const conflictsSources = this.computeConflictsSources(); - if (!equals(this._conflictsSources, conflictsSources)) { - this._conflictsSources = this.computeConflictsSources(); - this._onDidChangeConflicts.fire(conflictsSources); - } + this.updateConflicts(); const status = this.computeStatus(); this.setStatus(status); } + private updateConflicts(): void { + const conflicts = this.computeConflicts(); + if (!equals(this._conflicts, conflicts, (a, b) => a.syncResource === b.syncResource && equals(a.conflicts, b.conflicts, (a, b) => isEqual(a.local, b.local) && isEqual(a.remote, b.remote)))) { + this._conflicts = this.computeConflicts(); + this._onDidChangeConflicts.fire(conflicts); + } + } + private computeStatus(): SyncStatus { if (!this.userDataSyncStoreService.userDataSyncStore) { return SyncStatus.Uninitialized; @@ -286,7 +322,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } } - private handleSyncError(e: Error, source: SyncSource): void { + private handleSyncError(e: Error, source: SyncResource): void { if (e instanceof UserDataSyncStoreError) { switch (e.code) { case UserDataSyncErrorCode.TooLarge: @@ -298,17 +334,13 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this.logService.error(`${source}: ${toErrorMessage(e)}`); } - private computeConflictsSources(): SyncSource[] { - return this.synchronisers.filter(s => s.status === SyncStatus.HasConflicts).map(s => s.source); + private computeConflicts(): SyncResourceConflicts[] { + return this.synchronisers.filter(s => s.status === SyncStatus.HasConflicts) + .map(s => ({ syncResource: s.resource, conflicts: s.conflicts })); } - private getSynchroniser(source: SyncSource): IUserDataSynchroniser { - switch (source) { - case SyncSource.Settings: return this.settingsSynchroniser; - case SyncSource.Keybindings: return this.keybindingsSynchroniser; - case SyncSource.Extensions: return this.extensionsSynchroniser; - case SyncSource.GlobalState: return this.globalStateSynchroniser; - } + getSynchroniser(source: SyncResource): IUserDataSynchroniser { + return this.synchronisers.filter(s => s.resource === source)[0]; } private async checkEnablement(): Promise { diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index 2280e1acc28..d292ef24ef9 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -4,20 +4,27 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, } from 'vs/base/common/lifecycle'; -import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, getUserDataSyncStore, SyncSource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, getUserDataSyncStore, SyncResource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest, IResourceRefHandle } from 'vs/platform/userDataSync/common/userDataSync'; import { IRequestService, asText, isSuccess, asJson } from 'vs/platform/request/common/request'; -import { joinPath } from 'vs/base/common/resources'; +import { joinPath, relativePath } from 'vs/base/common/resources'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IHeaders, IRequestOptions, IRequestContext } from 'vs/base/parts/request/common/request'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; import { IProductService } from 'vs/platform/product/common/productService'; +import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { assign } from 'vs/base/common/objects'; + export class UserDataSyncStoreService extends Disposable implements IUserDataSyncStoreService { _serviceBrand: any; readonly userDataSyncStore: IUserDataSyncStore | undefined; + private readonly commonHeadersPromise: Promise<{ [key: string]: string; }>; constructor( @IProductService productService: IProductService, @@ -25,17 +32,82 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn @IRequestService private readonly requestService: IRequestService, @IAuthenticationTokenService private readonly authTokenService: IAuthenticationTokenService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + @IEnvironmentService environmentService: IEnvironmentService, + @IFileService fileService: IFileService, + @IStorageService storageService: IStorageService, ) { super(); this.userDataSyncStore = getUserDataSyncStore(productService, configurationService); + this.commonHeadersPromise = getServiceMachineId(environmentService, fileService, storageService) + .then(uuid => { + const headers: IHeaders = { + 'X-Sync-Client-Id': productService.version, + }; + if (uuid) { + headers['X-Sync-Machine-Id'] = uuid; + } + return headers; + }); } - async read(key: string, oldValue: IUserData | null, source?: SyncSource): Promise { + async getAllRefs(resource: SyncResource): Promise { if (!this.userDataSyncStore) { throw new Error('No settings sync store url configured.'); } - const url = joinPath(this.userDataSyncStore.url, 'resource', key, 'latest').toString(); + const uri = joinPath(this.userDataSyncStore.url, 'resource', resource); + const headers: IHeaders = {}; + + const context = await this.request({ type: 'GET', url: uri.toString(), headers }, undefined, CancellationToken.None); + + if (!isSuccess(context)) { + throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, undefined); + } + + const result = await asJson<{ url: string, created: number }[]>(context) || []; + return result.map(({ url, created }) => ({ ref: relativePath(uri, uri.with({ path: url }))!, created: created * 1000 /* Server returns in seconds */ })); + } + + async resolveContent(resource: SyncResource, ref: string): Promise { + if (!this.userDataSyncStore) { + throw new Error('No settings sync store url configured.'); + } + + const url = joinPath(this.userDataSyncStore.url, 'resource', resource, ref).toString(); + const headers: IHeaders = {}; + headers['Cache-Control'] = 'no-cache'; + + const context = await this.request({ type: 'GET', url, headers }, undefined, CancellationToken.None); + + if (!isSuccess(context)) { + throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, undefined); + } + + const content = await asText(context); + return content; + } + + async delete(resource: SyncResource): Promise { + if (!this.userDataSyncStore) { + throw new Error('No settings sync store url configured.'); + } + + const url = joinPath(this.userDataSyncStore.url, 'resource', resource).toString(); + const headers: IHeaders = {}; + + const context = await this.request({ type: 'DELETE', url, headers }, undefined, CancellationToken.None); + + if (!isSuccess(context)) { + throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, undefined); + } + } + + async read(resource: SyncResource, oldValue: IUserData | null): Promise { + if (!this.userDataSyncStore) { + throw new Error('No settings sync store url configured.'); + } + + const url = joinPath(this.userDataSyncStore.url, 'resource', resource, 'latest').toString(); const headers: IHeaders = {}; // Disable caching as they are cached by synchronisers headers['Cache-Control'] = 'no-cache'; @@ -43,7 +115,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn headers['If-None-Match'] = oldValue.ref; } - const context = await this.request({ type: 'GET', url, headers }, source, CancellationToken.None); + const context = await this.request({ type: 'GET', url, headers }, resource, CancellationToken.None); if (context.res.statusCode === 304) { // There is no new value. Hence return the old value. @@ -51,37 +123,37 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn } if (!isSuccess(context)) { - throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, source); + throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, resource); } const ref = context.res.headers['etag']; if (!ref) { - throw new UserDataSyncStoreError('Server did not return the ref', UserDataSyncErrorCode.NoRef, source); + throw new UserDataSyncStoreError('Server did not return the ref', UserDataSyncErrorCode.NoRef, resource); } const content = await asText(context); return { ref, content }; } - async write(key: string, data: string, ref: string | null, source?: SyncSource): Promise { + async write(resource: SyncResource, data: string, ref: string | null): Promise { if (!this.userDataSyncStore) { throw new Error('No settings sync store url configured.'); } - const url = joinPath(this.userDataSyncStore.url, 'resource', key).toString(); + const url = joinPath(this.userDataSyncStore.url, 'resource', resource).toString(); const headers: IHeaders = { 'Content-Type': 'text/plain' }; if (ref) { headers['If-Match'] = ref; } - const context = await this.request({ type: 'POST', url, data, headers }, source, CancellationToken.None); + const context = await this.request({ type: 'POST', url, data, headers }, resource, CancellationToken.None); if (!isSuccess(context)) { - throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, source); + throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, resource); } const newRef = context.res.headers['etag']; if (!newRef) { - throw new UserDataSyncStoreError('Server did not return the ref', UserDataSyncErrorCode.NoRef, source); + throw new UserDataSyncStoreError('Server did not return the ref', UserDataSyncErrorCode.NoRef, resource); } return newRef; } @@ -117,13 +189,16 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn } } - private async request(options: IRequestOptions, source: SyncSource | undefined, token: CancellationToken): Promise { + private async request(options: IRequestOptions, source: SyncResource | undefined, token: CancellationToken): Promise { const authToken = await this.authTokenService.getToken(); if (!authToken) { throw new UserDataSyncStoreError('No Auth Token Available', UserDataSyncErrorCode.Unauthorized, source); } - options.headers = options.headers || {}; - options.headers['authorization'] = `Bearer ${authToken}`; + + const commonHeaders = await this.commonHeadersPromise; + options.headers = assign(options.headers || {}, commonHeaders, { + 'authorization': `Bearer ${authToken}`, + }); this.logService.trace('Sending request to server', { url: options.url, type: options.type, headers: { ...options.headers, ...{ authorization: undefined } } }); diff --git a/src/vs/platform/userDataSync/test/common/globalStateMerge.test.ts b/src/vs/platform/userDataSync/test/common/globalStateMerge.test.ts new file mode 100644 index 00000000000..e89763a4d9a --- /dev/null +++ b/src/vs/platform/userDataSync/test/common/globalStateMerge.test.ts @@ -0,0 +1,380 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { merge } from 'vs/platform/userDataSync/common/globalStateMerge'; +import { NullLogService } from 'vs/platform/log/common/log'; + +suite('GlobalStateMerge', () => { + + test('merge when local and remote are same with one value', async () => { + const local = { 'a': { version: 1, value: 'a' } }; + const remote = { 'a': { version: 1, value: 'a' } }; + + const actual = merge(local, remote, null, [{ key: 'a', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, null); + }); + + test('merge when local and remote are same with multiple entries', async () => { + const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; + const remote = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; + + const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, null); + }); + + test('merge when local and remote are same with multiple entries in different order', async () => { + const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; + const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }; + + const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, null); + }); + + test('merge when local and remote are same with different base content', async () => { + const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; + const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }; + const base = { 'b': { version: 1, value: 'a' } }; + + const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, null); + }); + + test('merge when a new entry is added to remote', async () => { + const local = { 'a': { version: 1, value: 'a' } }; + const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }; + + const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, { 'b': { version: 1, value: 'b' } }); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, null); + }); + + test('merge when multiple new entries are added to remote', async () => { + const local = {}; + const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }; + + const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, null); + }); + + test('merge when new entry is added to remote from base and local has not changed', async () => { + const local = { 'a': { version: 1, value: 'a' } }; + const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }; + + const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, { 'b': { version: 1, value: 'b' } }); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, null); + }); + + test('merge when an entry is removed from remote from base and local has not changed', async () => { + const local = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }; + const remote = { 'a': { version: 1, value: 'a' } }; + + const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, ['b']); + assert.deepEqual(actual.remote, null); + }); + + test('merge when all entries are removed from base and local has not changed', async () => { + const local = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }; + const remote = {}; + + const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, ['b', 'a']); + assert.deepEqual(actual.remote, null); + }); + + test('merge when an entry is updated in remote from base and local has not changed', async () => { + const local = { 'a': { version: 1, value: 'a' } }; + const remote = { 'a': { version: 1, value: 'b' } }; + + const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'b' } }); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, null); + }); + + test('merge when remote has moved forwarded with multiple changes and local stays with base', async () => { + const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; + const remote = { 'a': { version: 1, value: 'd' }, 'c': { version: 1, value: 'c' } }; + + const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, { 'c': { version: 1, value: 'c' } }); + assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'd' } }); + assert.deepEqual(actual.local.removed, ['b']); + assert.deepEqual(actual.remote, null); + }); + + test('merge when new entries are added to local', async () => { + const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; + const remote = { 'a': { version: 1, value: 'a' } }; + + const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, local); + }); + + test('merge when multiple new entries are added to local from base and remote is not changed', async () => { + const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' }, 'c': { version: 1, value: 'c' } }; + const remote = { 'a': { version: 1, value: 'a' } }; + + const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, local); + }); + + test('merge when an entry is removed from local from base and remote has not changed', async () => { + const local = { 'a': { version: 1, value: 'a' } }; + const remote = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; + + const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, local); + }); + + test('merge when an entry is updated in local from base and remote has not changed', async () => { + const local = { 'a': { version: 1, value: 'b' } }; + const remote = { 'a': { version: 1, value: 'a' } }; + + const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, local); + }); + + test('merge when local has moved forwarded with multiple changes and remote stays with base', async () => { + const local = { 'a': { version: 1, value: 'd' }, 'b': { version: 1, value: 'b' } }; + const remote = { 'a': { version: 1, value: 'a' }, 'c': { version: 1, value: 'c' } }; + + const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, local); + }); + + test('merge when local and remote with one entry but different value', async () => { + const local = { 'a': { version: 1, value: 'a' } }; + const remote = { 'a': { version: 1, value: 'b' } }; + + const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'b' } }); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, null); + }); + + test('merge when the entry is removed in remote but updated in local and a new entry is added in remote', async () => { + const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; + const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'd' } }; + const remote = { 'a': { version: 1, value: 'a' }, 'c': { version: 1, value: 'c' } }; + + const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, { 'c': { version: 1, value: 'c' } }); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, ['b']); + assert.deepEqual(actual.remote, null); + }); + + test('merge with single entry and local is empty', async () => { + const base = { 'a': { version: 1, value: 'a' } }; + const local = {}; + const remote = { 'a': { version: 1, value: 'b' } }; + + const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'b' } }); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, null); + }); + + test('merge when local and remote has moved forwareded with conflicts', async () => { + const base = { 'a': { version: 1, value: 'a' } }; + const local = { 'a': { version: 1, value: 'd' } }; + const remote = { 'a': { version: 1, value: 'b' } }; + + const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'b' } }); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, null); + }); + + test('merge when a new entry is added to remote but not a registered key', async () => { + const local = { 'a': { version: 1, value: 'a' } }; + const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }; + + const actual = merge(local, remote, null, [{ key: 'a', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, null); + }); + + test('merge when a new entry is added to remote but different version', async () => { + const local = { 'a': { version: 1, value: 'a' } }; + const remote = { 'b': { version: 2, value: 'b' }, 'a': { version: 1, value: 'a' } }; + + const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, null); + }); + + test('merge when an entry is updated to remote but not a registered key', async () => { + const local = { 'a': { version: 1, value: 'a' } }; + const remote = { 'a': { version: 1, value: 'b' } }; + + const actual = merge(local, remote, local, [], [], new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, null); + }); + + test('merge when a new entry is updated to remote but different version', async () => { + const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; + const remote = { 'b': { version: 2, value: 'b' }, 'a': { version: 1, value: 'a' } }; + + const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, null); + }); + + test('merge when a local value is update with lower version', async () => { + const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'c' } }; + const remote = { 'b': { version: 2, value: 'b' }, 'a': { version: 1, value: 'a' } }; + + const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, null); + }); + + test('merge when a local value is update with higher version', async () => { + const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 2, value: 'c' } }; + const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }; + + const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 2 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, local); + }); + + test('merge when a local value is removed but not registered', async () => { + const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; + const local = { 'a': { version: 1, value: 'a' } }; + const remote = { 'b': { version: 2, value: 'b' }, 'a': { version: 1, value: 'a' } }; + + const actual = merge(local, remote, base, [{ key: 'a', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, null); + }); + + test('merge when a local value is removed with lower version', async () => { + const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; + const local = { 'a': { version: 1, value: 'a' } }; + const remote = { 'b': { version: 2, value: 'b' }, 'a': { version: 1, value: 'a' } }; + + const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, null); + }); + + test('merge when a local value is removed with higher version', async () => { + const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; + const local = { 'a': { version: 1, value: 'a' } }; + const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }; + + const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 2 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, local); + }); + + test('merge when a local value is not yet registered', async () => { + const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } }; + const local = { 'a': { version: 1, value: 'a' } }; + const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } }; + + const actual = merge(local, remote, base, [{ key: 'a', version: 1 }], [], new NullLogService()); + + assert.deepEqual(actual.local.added, {}); + assert.deepEqual(actual.local.updated, {}); + assert.deepEqual(actual.local.removed, []); + assert.deepEqual(actual.remote, null); + }); + +}); diff --git a/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts b/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts new file mode 100644 index 00000000000..9f86b9354a5 --- /dev/null +++ b/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts @@ -0,0 +1,224 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IUserDataSyncStoreService, IUserDataSyncService, SyncResource, SyncStatus, IGlobalState } from 'vs/platform/userDataSync/common/userDataSync'; +import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ISyncData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { GlobalStateSynchroniser } from 'vs/platform/userDataSync/common/globalStateSync'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; + + +suite('GlobalStateSync', () => { + + const disposableStore = new DisposableStore(); + const server = new UserDataSyncTestServer(); + let testClient: UserDataSyncClient; + let client2: UserDataSyncClient; + + let testObject: GlobalStateSynchroniser; + + setup(async () => { + testClient = disposableStore.add(new UserDataSyncClient(server)); + await testClient.setUp(true); + let storageKeysSyncRegistryService = testClient.instantiationService.get(IStorageKeysSyncRegistryService); + storageKeysSyncRegistryService.registerStorageKey({ key: 'a', version: 1 }); + storageKeysSyncRegistryService.registerStorageKey({ key: 'b', version: 1 }); + testObject = (testClient.instantiationService.get(IUserDataSyncService) as UserDataSyncService).getSynchroniser(SyncResource.GlobalState) as GlobalStateSynchroniser; + disposableStore.add(toDisposable(() => testClient.instantiationService.get(IUserDataSyncStoreService).clear())); + + client2 = disposableStore.add(new UserDataSyncClient(server)); + await client2.setUp(true); + storageKeysSyncRegistryService = client2.instantiationService.get(IStorageKeysSyncRegistryService); + storageKeysSyncRegistryService.registerStorageKey({ key: 'a', version: 1 }); + storageKeysSyncRegistryService.registerStorageKey({ key: 'b', version: 1 }); + }); + + teardown(() => disposableStore.clear()); + + test('first time sync - outgoing to server (no state)', async () => { + updateStorage('a', 'value1', testClient); + await updateLocale(testClient); + + await testObject.sync(); + assert.equal(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + + const { content } = await testClient.read(testObject.resource); + assert.ok(content !== null); + const actual = parseGlobalState(content!); + assert.deepEqual(actual.storage, { 'globalState.argv.locale': { version: 1, value: 'en' }, 'a': { version: 1, value: 'value1' } }); + }); + + test('first time sync - incoming from server (no state)', async () => { + updateStorage('a', 'value1', client2); + await updateLocale(client2); + await client2.sync(); + + await testObject.sync(); + assert.equal(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + + assert.equal(readStorage('a', testClient), 'value1'); + assert.equal(await readLocale(testClient), 'en'); + }); + + test('first time sync when storage exists', async () => { + updateStorage('a', 'value1', client2); + await client2.sync(); + + updateStorage('b', 'value2', testClient); + await testObject.sync(); + assert.equal(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + + assert.equal(readStorage('a', testClient), 'value1'); + assert.equal(readStorage('b', testClient), 'value2'); + + const { content } = await testClient.read(testObject.resource); + assert.ok(content !== null); + const actual = parseGlobalState(content!); + assert.deepEqual(actual.storage, { 'a': { version: 1, value: 'value1' }, 'b': { version: 1, value: 'value2' } }); + }); + + test('first time sync when storage exists - has conflicts', async () => { + updateStorage('a', 'value1', client2); + await client2.sync(); + + updateStorage('a', 'value2', client2); + await testObject.sync(); + + assert.equal(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + + assert.equal(readStorage('a', testClient), 'value1'); + + const { content } = await testClient.read(testObject.resource); + assert.ok(content !== null); + const actual = parseGlobalState(content!); + assert.deepEqual(actual.storage, { 'a': { version: 1, value: 'value1' } }); + }); + + test('sync adding a storage value', async () => { + updateStorage('a', 'value1', testClient); + await testObject.sync(); + + updateStorage('b', 'value2', testClient); + await testObject.sync(); + assert.equal(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + + assert.equal(readStorage('a', testClient), 'value1'); + assert.equal(readStorage('b', testClient), 'value2'); + + const { content } = await testClient.read(testObject.resource); + assert.ok(content !== null); + const actual = parseGlobalState(content!); + assert.deepEqual(actual.storage, { 'a': { version: 1, value: 'value1' }, 'b': { version: 1, value: 'value2' } }); + }); + + test('sync updating a storage value', async () => { + updateStorage('a', 'value1', testClient); + await testObject.sync(); + + updateStorage('a', 'value2', testClient); + await testObject.sync(); + assert.equal(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + + assert.equal(readStorage('a', testClient), 'value2'); + + const { content } = await testClient.read(testObject.resource); + assert.ok(content !== null); + const actual = parseGlobalState(content!); + assert.deepEqual(actual.storage, { 'a': { version: 1, value: 'value2' } }); + }); + + test('sync removing a storage value', async () => { + updateStorage('a', 'value1', testClient); + updateStorage('b', 'value2', testClient); + await testObject.sync(); + + removeStorage('b', testClient); + await testObject.sync(); + assert.equal(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + + assert.equal(readStorage('a', testClient), 'value1'); + assert.equal(readStorage('b', testClient), undefined); + + const { content } = await testClient.read(testObject.resource); + assert.ok(content !== null); + const actual = parseGlobalState(content!); + assert.deepEqual(actual.storage, { 'a': { version: 1, value: 'value1' } }); + }); + + test('first time sync - push', async () => { + updateStorage('a', 'value1', testClient); + updateStorage('b', 'value2', testClient); + + await testObject.push(); + assert.equal(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + + const { content } = await testClient.read(testObject.resource); + assert.ok(content !== null); + const actual = parseGlobalState(content!); + assert.deepEqual(actual.storage, { 'a': { version: 1, value: 'value1' }, 'b': { version: 1, value: 'value2' } }); + }); + + test('first time sync - pull', async () => { + updateStorage('a', 'value1', client2); + updateStorage('b', 'value2', client2); + await client2.sync(); + + await testObject.pull(); + assert.equal(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + + assert.equal(readStorage('a', testClient), 'value1'); + assert.equal(readStorage('b', testClient), 'value2'); + }); + + function parseGlobalState(content: string): IGlobalState { + const syncData: ISyncData = JSON.parse(content); + return JSON.parse(syncData.content); + } + + async function updateLocale(client: UserDataSyncClient): Promise { + const fileService = client.instantiationService.get(IFileService); + const environmentService = client.instantiationService.get(IEnvironmentService); + await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'en' }))); + } + + function updateStorage(key: string, value: string, client: UserDataSyncClient): void { + const storageService = client.instantiationService.get(IStorageService); + storageService.store(key, value, StorageScope.GLOBAL); + } + + function removeStorage(key: string, client: UserDataSyncClient): void { + const storageService = client.instantiationService.get(IStorageService); + storageService.remove(key, StorageScope.GLOBAL); + } + + function readStorage(key: string, client: UserDataSyncClient): string | undefined { + const storageService = client.instantiationService.get(IStorageService); + return storageService.get(key, StorageScope.GLOBAL); + } + + async function readLocale(client: UserDataSyncClient): Promise { + const fileService = client.instantiationService.get(IFileService); + const environmentService = client.instantiationService.get(IEnvironmentService); + const content = await fileService.readFile(environmentService.argvResource); + return JSON.parse(content.value.toString()).locale; + } + +}); diff --git a/src/vs/platform/userDataSync/test/common/settingsSync.test.ts b/src/vs/platform/userDataSync/test/common/settingsSync.test.ts new file mode 100644 index 00000000000..c0f87712cf1 --- /dev/null +++ b/src/vs/platform/userDataSync/test/common/settingsSync.test.ts @@ -0,0 +1,355 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IUserDataSyncStoreService, IUserDataSyncService, SyncResource, UserDataSyncError, UserDataSyncErrorCode } from 'vs/platform/userDataSync/common/userDataSync'; +import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { SettingsSynchroniser, ISettingsSyncContent } from 'vs/platform/userDataSync/common/settingsSync'; +import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { ISyncData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IConfigurationRegistry, Extensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; + +suite('SettingsSync', () => { + + const disposableStore = new DisposableStore(); + const server = new UserDataSyncTestServer(); + let client: UserDataSyncClient; + + let testObject: SettingsSynchroniser; + + suiteSetup(() => { + Registry.as(Extensions.Configuration).registerConfiguration({ + 'id': 'settingsSync', + 'type': 'object', + 'properties': { + 'settingsSync.machine': { + 'type': 'string', + 'scope': ConfigurationScope.MACHINE + }, + 'settingsSync.machineOverridable': { + 'type': 'string', + 'scope': ConfigurationScope.MACHINE_OVERRIDABLE + } + } + }); + }); + + setup(async () => { + client = disposableStore.add(new UserDataSyncClient(server)); + await client.setUp(); + testObject = (client.instantiationService.get(IUserDataSyncService) as UserDataSyncService).getSynchroniser(SyncResource.Settings) as SettingsSynchroniser; + disposableStore.add(toDisposable(() => client.instantiationService.get(IUserDataSyncStoreService).clear())); + }); + + teardown(() => disposableStore.clear()); + + test('sync for first time to the server', async () => { + const expected = + `{ + // Always + "files.autoSave": "afterDelay", + "files.simpleDialog.enable": true, + + // Workbench + "workbench.colorTheme": "GitHub Sharp", + "workbench.tree.indent": 20, + "workbench.colorCustomizations": { + "editorLineNumber.activeForeground": "#ff0000", + "[GitHub Sharp]": { + "statusBarItem.remoteBackground": "#24292E", + "editorPane.background": "#f3f1f11a" + } + }, + + "gitBranch.base": "remote-repo/master", + + // Experimental + "workbench.view.experimental.allowMovingToNewContainer": true, +}`; + + await updateSettings(expected); + await testObject.sync(); + + const { content } = await client.read(testObject.resource); + assert.ok(content !== null); + const actual = parseSettings(content!); + assert.deepEqual(actual, expected); + }); + + test('do not sync machine settings', async () => { + const settingsContent = + `{ + // Always + "files.autoSave": "afterDelay", + "files.simpleDialog.enable": true, + + // Workbench + "workbench.colorTheme": "GitHub Sharp", + + // Machine + "settingsSync.machine": "someValue", + "settingsSync.machineOverridable": "someValue" +}`; + await updateSettings(settingsContent); + + await testObject.sync(); + + const { content } = await client.read(testObject.resource); + assert.ok(content !== null); + const actual = parseSettings(content!); + assert.deepEqual(actual, `{ + // Always + "files.autoSave": "afterDelay", + "files.simpleDialog.enable": true, + + // Workbench + "workbench.colorTheme": "GitHub Sharp" +}`); + }); + + test('do not sync machine settings when spread across file', async () => { + const settingsContent = + `{ + // Always + "files.autoSave": "afterDelay", + "settingsSync.machine": "someValue", + "files.simpleDialog.enable": true, + + // Workbench + "workbench.colorTheme": "GitHub Sharp", + + // Machine + "settingsSync.machineOverridable": "someValue" +}`; + await updateSettings(settingsContent); + + await testObject.sync(); + + const { content } = await client.read(testObject.resource); + assert.ok(content !== null); + const actual = parseSettings(content!); + assert.deepEqual(actual, `{ + // Always + "files.autoSave": "afterDelay", + "files.simpleDialog.enable": true, + + // Workbench + "workbench.colorTheme": "GitHub Sharp" +}`); + }); + + test('do not sync machine settings when spread across file - 2', async () => { + const settingsContent = + `{ + // Always + "files.autoSave": "afterDelay", + "settingsSync.machine": "someValue", + + // Workbench + "workbench.colorTheme": "GitHub Sharp", + + // Machine + "settingsSync.machineOverridable": "someValue", + "files.simpleDialog.enable": true, +}`; + await updateSettings(settingsContent); + + await testObject.sync(); + + const { content } = await client.read(testObject.resource); + assert.ok(content !== null); + const actual = parseSettings(content!); + assert.deepEqual(actual, `{ + // Always + "files.autoSave": "afterDelay", + + // Workbench + "workbench.colorTheme": "GitHub Sharp", + "files.simpleDialog.enable": true, +}`); + }); + + test('sync when all settings are machine settings', async () => { + const settingsContent = + `{ + // Machine + "settingsSync.machine": "someValue", + "settingsSync.machineOverridable": "someValue" +}`; + await updateSettings(settingsContent); + + await testObject.sync(); + + const { content } = await client.read(testObject.resource); + assert.ok(content !== null); + const actual = parseSettings(content!); + assert.deepEqual(actual, `{ +}`); + }); + + test('sync when all settings are machine settings with trailing comma', async () => { + const settingsContent = + `{ + // Machine + "settingsSync.machine": "someValue", + "settingsSync.machineOverridable": "someValue", +}`; + await updateSettings(settingsContent); + + await testObject.sync(); + + const { content } = await client.read(testObject.resource); + assert.ok(content !== null); + const actual = parseSettings(content!); + assert.deepEqual(actual, `{ + , +}`); + }); + + test('do not sync ignored settings', async () => { + const settingsContent = + `{ + // Always + "files.autoSave": "afterDelay", + "files.simpleDialog.enable": true, + + // Editor + "editor.fontFamily": "Fira Code", + + // Terminal + "terminal.integrated.shell.osx": "some path", + + // Workbench + "workbench.colorTheme": "GitHub Sharp", + + // Ignored + "sync.ignoredSettings": [ + "editor.fontFamily", + "terminal.integrated.shell.osx" + ] +}`; + await updateSettings(settingsContent); + + await testObject.sync(); + + const { content } = await client.read(testObject.resource); + assert.ok(content !== null); + const actual = parseSettings(content!); + assert.deepEqual(actual, `{ + // Always + "files.autoSave": "afterDelay", + "files.simpleDialog.enable": true, + + // Workbench + "workbench.colorTheme": "GitHub Sharp", + + // Ignored + "sync.ignoredSettings": [ + "editor.fontFamily", + "terminal.integrated.shell.osx" + ] +}`); + }); + + test('do not sync ignored and machine settings', async () => { + const settingsContent = + `{ + // Always + "files.autoSave": "afterDelay", + "files.simpleDialog.enable": true, + + // Editor + "editor.fontFamily": "Fira Code", + + // Terminal + "terminal.integrated.shell.osx": "some path", + + // Workbench + "workbench.colorTheme": "GitHub Sharp", + + // Ignored + "sync.ignoredSettings": [ + "editor.fontFamily", + "terminal.integrated.shell.osx" + ], + + // Machine + "settingsSync.machine": "someValue", +}`; + await updateSettings(settingsContent); + + await testObject.sync(); + + const { content } = await client.read(testObject.resource); + assert.ok(content !== null); + const actual = parseSettings(content!); + assert.deepEqual(actual, `{ + // Always + "files.autoSave": "afterDelay", + "files.simpleDialog.enable": true, + + // Workbench + "workbench.colorTheme": "GitHub Sharp", + + // Ignored + "sync.ignoredSettings": [ + "editor.fontFamily", + "terminal.integrated.shell.osx" + ], +}`); + }); + + test('sync throws invalid content error', async () => { + const expected = + `{ + // Always + "files.autoSave": "afterDelay", + "files.simpleDialog.enable": true, + + // Workbench + "workbench.colorTheme": "GitHub Sharp", + "workbench.tree.indent": 20, + "workbench.colorCustomizations": { + "editorLineNumber.activeForeground": "#ff0000", + "[GitHub Sharp]": { + "statusBarItem.remoteBackground": "#24292E", + "editorPane.background": "#f3f1f11a" + } + } + + "gitBranch.base": "remote-repo/master", + + // Experimental + "workbench.view.experimental.allowMovingToNewContainer": true, +}`; + + await updateSettings(expected); + + try { + await testObject.sync(); + assert.fail('should fail with invalid content error'); + } catch (e) { + assert.ok(e instanceof UserDataSyncError); + assert.deepEqual((e).code, UserDataSyncErrorCode.LocalInvalidContent); + } + }); + + function parseSettings(content: string): string { + const syncData: ISyncData = JSON.parse(content); + const settingsSyncContent: ISettingsSyncContent = JSON.parse(syncData.content); + return settingsSyncContent.settings; + } + + async function updateSettings(content: string): Promise { + await client.instantiationService.get(IFileService).writeFile(client.instantiationService.get(IEnvironmentService).settingsResource, VSBuffer.fromString(content)); + } + + +}); diff --git a/src/vs/platform/userDataSync/test/common/snippetsMerge.test.ts b/src/vs/platform/userDataSync/test/common/snippetsMerge.test.ts new file mode 100644 index 00000000000..5a396c4a6da --- /dev/null +++ b/src/vs/platform/userDataSync/test/common/snippetsMerge.test.ts @@ -0,0 +1,436 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { merge } from 'vs/platform/userDataSync/common/snippetsMerge'; + +const tsSnippet1 = `{ + + // Place your snippets for TypeScript here. Each snippet is defined under a snippet name and has a prefix, body and + // description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are: + // $1, $2 for tab stops, $0 for the final cursor position, Placeholders with the + // same ids are connected. + "Print to console": { + // Example: + "prefix": "log", + "body": [ + "console.log('$1');", + "$2" + ], + "description": "Log output to console", + } + +}`; + +const tsSnippet2 = `{ + + // Place your snippets for TypeScript here. Each snippet is defined under a snippet name and has a prefix, body and + // description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are: + // $1, $2 for tab stops, $0 for the final cursor position, Placeholders with the + // same ids are connected. + "Print to console": { + // Example: + "prefix": "log", + "body": [ + "console.log('$1');", + "$2" + ], + "description": "Log output to console always", + } + +}`; + +const htmlSnippet1 = `{ +/* + // Place your snippets for HTML here. Each snippet is defined under a snippet name and has a prefix, body and + // description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. + // Example: + "Print to console": { + "prefix": "log", + "body": [ + "console.log('$1');", + "$2" + ], + "description": "Log output to console" + } +*/ +"Div": { + "prefix": "div", + "body": [ + "
", + "", + "
" + ], + "description": "New div" + } +}`; + +const htmlSnippet2 = `{ +/* + // Place your snippets for HTML here. Each snippet is defined under a snippet name and has a prefix, body and + // description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. + // Example: + "Print to console": { + "prefix": "log", + "body": [ + "console.log('$1');", + "$2" + ], + "description": "Log output to console" + } +*/ +"Div": { + "prefix": "div", + "body": [ + "
", + "", + "
" + ], + "description": "New div changed" + } +}`; + +const cSnippet = `{ + // Place your snippets for c here. Each snippet is defined under a snippet name and has a prefix, body and + // description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are: + // $1, $2 for tab stops, $0 for the final cursor position.Placeholders with the + // same ids are connected. + // Example: + "Print to console": { + "prefix": "log", + "body": [ + "console.log('$1');", + "$2" + ], + "description": "Log output to console" + } +}`; + +suite('SnippetsMerge', () => { + + test('merge when local and remote are same with one snippet', async () => { + const local = { 'html.json': htmlSnippet1 }; + const remote = { 'html.json': htmlSnippet1 }; + + const actual = merge(local, remote, null); + + assert.deepEqual(actual.added, {}); + assert.deepEqual(actual.updated, {}); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.conflicts, []); + assert.equal(actual.remote, null); + }); + + test('merge when local and remote are same with multiple entries', async () => { + const local = { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }; + const remote = { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }; + + const actual = merge(local, remote, null); + + assert.deepEqual(actual.added, {}); + assert.deepEqual(actual.updated, {}); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.conflicts, []); + assert.equal(actual.remote, null); + }); + + test('merge when local and remote are same with multiple entries in different order', async () => { + const local = { 'typescript.json': tsSnippet1, 'html.json': htmlSnippet1 }; + const remote = { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }; + + const actual = merge(local, remote, null); + + assert.deepEqual(actual.added, {}); + assert.deepEqual(actual.updated, {}); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.conflicts, []); + assert.equal(actual.remote, null); + }); + + test('merge when local and remote are same with different base content', async () => { + const local = { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }; + const remote = { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }; + const base = { 'html.json': htmlSnippet2, 'typescript.json': tsSnippet2 }; + + const actual = merge(local, remote, base); + + assert.deepEqual(actual.added, {}); + assert.deepEqual(actual.updated, {}); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.conflicts, []); + assert.equal(actual.remote, null); + }); + + test('merge when a new entry is added to remote', async () => { + const local = { 'html.json': htmlSnippet1 }; + const remote = { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }; + + const actual = merge(local, remote, null); + + assert.deepEqual(actual.added, { 'typescript.json': tsSnippet1 }); + assert.deepEqual(actual.updated, {}); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.conflicts, []); + assert.equal(actual.remote, null); + }); + + test('merge when multiple new entries are added to remote', async () => { + const local = {}; + const remote = { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }; + + const actual = merge(local, remote, null); + + assert.deepEqual(actual.added, remote); + assert.deepEqual(actual.updated, {}); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.conflicts, []); + assert.equal(actual.remote, null); + }); + + test('merge when new entry is added to remote from base and local has not changed', async () => { + const local = { 'html.json': htmlSnippet1 }; + const remote = { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }; + + const actual = merge(local, remote, local); + + assert.deepEqual(actual.added, { 'typescript.json': tsSnippet1 }); + assert.deepEqual(actual.updated, {}); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.conflicts, []); + assert.equal(actual.remote, null); + }); + + test('merge when an entry is removed from remote from base and local has not changed', async () => { + const local = { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }; + const remote = { 'html.json': htmlSnippet1 }; + + const actual = merge(local, remote, local); + + assert.deepEqual(actual.added, {}); + assert.deepEqual(actual.updated, {}); + assert.deepEqual(actual.removed, ['typescript.json']); + assert.deepEqual(actual.conflicts, []); + assert.equal(actual.remote, null); + }); + + test('merge when all entries are removed from base and local has not changed', async () => { + const local = { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }; + const remote = {}; + + const actual = merge(local, remote, local); + + assert.deepEqual(actual.added, {}); + assert.deepEqual(actual.updated, {}); + assert.deepEqual(actual.removed, ['html.json', 'typescript.json']); + assert.deepEqual(actual.conflicts, []); + assert.equal(actual.remote, null); + }); + + test('merge when an entry is updated in remote from base and local has not changed', async () => { + const local = { 'html.json': htmlSnippet1 }; + const remote = { 'html.json': htmlSnippet2 }; + + const actual = merge(local, remote, local); + + assert.deepEqual(actual.added, {}); + assert.deepEqual(actual.updated, { 'html.json': htmlSnippet2 }); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.conflicts, []); + assert.equal(actual.remote, null); + }); + + test('merge when remote has moved forwarded with multiple changes and local stays with base', async () => { + const local = { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }; + const remote = { 'html.json': htmlSnippet2, 'c.json': cSnippet }; + + const actual = merge(local, remote, local); + + assert.deepEqual(actual.added, { 'c.json': cSnippet }); + assert.deepEqual(actual.updated, { 'html.json': htmlSnippet2 }); + assert.deepEqual(actual.removed, ['typescript.json']); + assert.deepEqual(actual.conflicts, []); + assert.deepEqual(actual.remote, null); + }); + + test('merge when a new entries are added to local', async () => { + const local = { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1, 'c.json': cSnippet }; + const remote = { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }; + + const actual = merge(local, remote, null); + + assert.deepEqual(actual.added, {}); + assert.deepEqual(actual.updated, {}); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.conflicts, []); + assert.deepEqual(actual.remote, local); + }); + + test('merge when multiple new entries are added to local from base and remote is not changed', async () => { + const local = { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1, 'c.json': cSnippet }; + const remote = { 'typescript.json': tsSnippet1 }; + + const actual = merge(local, remote, remote); + + assert.deepEqual(actual.added, {}); + assert.deepEqual(actual.updated, {}); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.conflicts, []); + assert.deepEqual(actual.remote, { 'typescript.json': tsSnippet1, 'html.json': htmlSnippet1, 'c.json': cSnippet }); + }); + + test('merge when an entry is removed from local from base and remote has not changed', async () => { + const local = { 'html.json': htmlSnippet1 }; + const remote = { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }; + + const actual = merge(local, remote, remote); + + assert.deepEqual(actual.added, {}); + assert.deepEqual(actual.updated, {}); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.conflicts, []); + assert.deepEqual(actual.remote, local); + }); + + test('merge when an entry is updated in local from base and remote has not changed', async () => { + const local = { 'html.json': htmlSnippet2, 'typescript.json': tsSnippet1 }; + const remote = { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }; + + const actual = merge(local, remote, remote); + + assert.deepEqual(actual.added, {}); + assert.deepEqual(actual.updated, {}); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.conflicts, []); + assert.deepEqual(actual.remote, local); + }); + + test('merge when local has moved forwarded with multiple changes and remote stays with base', async () => { + const local = { 'html.json': htmlSnippet2, 'c.json': cSnippet }; + const remote = { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }; + + const actual = merge(local, remote, remote); + + assert.deepEqual(actual.added, {}); + assert.deepEqual(actual.updated, {}); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.conflicts, []); + assert.deepEqual(actual.remote, local); + }); + + test('merge when local and remote with one entry but different value', async () => { + const local = { 'html.json': htmlSnippet1 }; + const remote = { 'html.json': htmlSnippet2 }; + + const actual = merge(local, remote, null); + + assert.deepEqual(actual.added, {}); + assert.deepEqual(actual.updated, {}); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.conflicts, ['html.json']); + assert.deepEqual(actual.remote, null); + }); + + test('merge when the entry is removed in remote but updated in local and a new entry is added in remote', async () => { + const base = { 'html.json': htmlSnippet1 }; + const local = { 'html.json': htmlSnippet2 }; + const remote = { 'typescript.json': tsSnippet1 }; + + const actual = merge(local, remote, base); + + assert.deepEqual(actual.added, { 'typescript.json': tsSnippet1 }); + assert.deepEqual(actual.updated, {}); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.conflicts, ['html.json']); + assert.deepEqual(actual.remote, null); + }); + + test('merge with single entry and local is empty', async () => { + const base = { 'html.json': htmlSnippet1 }; + const local = {}; + const remote = { 'html.json': htmlSnippet2 }; + + const actual = merge(local, remote, base); + + assert.deepEqual(actual.added, { 'html.json': htmlSnippet2 }); + assert.deepEqual(actual.updated, {}); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.conflicts, []); + assert.deepEqual(actual.remote, null); + }); + + test('merge when local and remote has moved forwareded with conflicts', async () => { + const base = { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }; + const local = { 'html.json': htmlSnippet2, 'c.json': cSnippet }; + const remote = { 'typescript.json': tsSnippet2 }; + + const actual = merge(local, remote, base); + + assert.deepEqual(actual.added, { 'typescript.json': tsSnippet2 }); + assert.deepEqual(actual.updated, {}); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.conflicts, ['html.json']); + assert.deepEqual(actual.remote, { 'typescript.json': tsSnippet2, 'c.json': cSnippet }); + }); + + test('merge when local and remote has moved forwareded with resolved conflicts - update', async () => { + const base = { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }; + const local = { 'html.json': htmlSnippet2, 'c.json': cSnippet }; + const remote = { 'typescript.json': tsSnippet2 }; + const resolvedConflicts = { 'html.json': htmlSnippet2 }; + + const actual = merge(local, remote, base, resolvedConflicts); + + assert.deepEqual(actual.added, { 'typescript.json': tsSnippet2 }); + assert.deepEqual(actual.updated, {}); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.conflicts, []); + assert.deepEqual(actual.remote, { 'typescript.json': tsSnippet2, 'html.json': htmlSnippet2, 'c.json': cSnippet }); + }); + + test('merge when local and remote has moved forwareded with resolved conflicts - remove', async () => { + const base = { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }; + const local = { 'html.json': htmlSnippet2, 'c.json': cSnippet }; + const remote = { 'typescript.json': tsSnippet2 }; + const resolvedConflicts = { 'html.json': null }; + + const actual = merge(local, remote, base, resolvedConflicts); + + assert.deepEqual(actual.added, { 'typescript.json': tsSnippet2 }); + assert.deepEqual(actual.updated, {}); + assert.deepEqual(actual.removed, ['html.json']); + assert.deepEqual(actual.conflicts, []); + assert.deepEqual(actual.remote, { 'typescript.json': tsSnippet2, 'c.json': cSnippet }); + }); + + test('merge when local and remote has moved forwareded with multiple conflicts', async () => { + const base = { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }; + const local = { 'html.json': htmlSnippet2, 'typescript.json': tsSnippet2, 'c.json': cSnippet }; + const remote = { 'c.json': cSnippet }; + + const actual = merge(local, remote, base); + + assert.deepEqual(actual.added, {}); + assert.deepEqual(actual.updated, {}); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.conflicts, ['html.json', 'typescript.json']); + assert.deepEqual(actual.remote, null); + }); + + test('merge when local and remote has moved forwareded with multiple conflicts and resolving one conflict', async () => { + const base = { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }; + const local = { 'html.json': htmlSnippet2, 'typescript.json': tsSnippet2, 'c.json': cSnippet }; + const remote = { 'c.json': cSnippet }; + const resolvedConflicts = { 'html.json': htmlSnippet1 }; + + const actual = merge(local, remote, base, resolvedConflicts); + + assert.deepEqual(actual.added, {}); + assert.deepEqual(actual.updated, { 'html.json': htmlSnippet1 }); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.conflicts, ['typescript.json']); + assert.deepEqual(actual.remote, { 'c.json': cSnippet, 'html.json': htmlSnippet1 }); + }); + +}); diff --git a/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts b/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts new file mode 100644 index 00000000000..664d2654d95 --- /dev/null +++ b/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts @@ -0,0 +1,614 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IUserDataSyncStoreService, IUserDataSyncService, SyncResource, SyncStatus, Conflict, USER_DATA_SYNC_SCHEME, PREVIEW_DIR_NAME } from 'vs/platform/userDataSync/common/userDataSync'; +import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { ISyncData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { SnippetsSynchroniser } from 'vs/platform/userDataSync/common/snippetsSync'; +import { joinPath } from 'vs/base/common/resources'; +import { IStringDictionary } from 'vs/base/common/collections'; + +const tsSnippet1 = `{ + + // Place your snippets for TypeScript here. Each snippet is defined under a snippet name and has a prefix, body and + // description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are: + // $1, $2 for tab stops, $0 for the final cursor position, Placeholders with the + // same ids are connected. + "Print to console": { + // Example: + "prefix": "log", + "body": [ + "console.log('$1');", + "$2" + ], + "description": "Log output to console", + } + +}`; + +const tsSnippet2 = `{ + + // Place your snippets for TypeScript here. Each snippet is defined under a snippet name and has a prefix, body and + // description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are: + // $1, $2 for tab stops, $0 for the final cursor position, Placeholders with the + // same ids are connected. + "Print to console": { + // Example: + "prefix": "log", + "body": [ + "console.log('$1');", + "$2" + ], + "description": "Log output to console always", + } + +}`; + +const htmlSnippet1 = `{ +/* + // Place your snippets for HTML here. Each snippet is defined under a snippet name and has a prefix, body and + // description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. + // Example: + "Print to console": { + "prefix": "log", + "body": [ + "console.log('$1');", + "$2" + ], + "description": "Log output to console" + } +*/ +"Div": { + "prefix": "div", + "body": [ + "
", + "", + "
" + ], + "description": "New div" + } +}`; + +const htmlSnippet2 = `{ +/* + // Place your snippets for HTML here. Each snippet is defined under a snippet name and has a prefix, body and + // description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. + // Example: + "Print to console": { + "prefix": "log", + "body": [ + "console.log('$1');", + "$2" + ], + "description": "Log output to console" + } +*/ +"Div": { + "prefix": "div", + "body": [ + "
", + "", + "
" + ], + "description": "New div changed" + } +}`; + +const htmlSnippet3 = `{ +/* + // Place your snippets for HTML here. Each snippet is defined under a snippet name and has a prefix, body and + // description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. + // Example: + "Print to console": { + "prefix": "log", + "body": [ + "console.log('$1');", + "$2" + ], + "description": "Log output to console" + } +*/ +"Div": { + "prefix": "div", + "body": [ + "
", + "", + "
" + ], + "description": "New div changed again" + } +}`; + +suite('SnippetsSync', () => { + + const disposableStore = new DisposableStore(); + const server = new UserDataSyncTestServer(); + let testClient: UserDataSyncClient; + let client2: UserDataSyncClient; + + let testObject: SnippetsSynchroniser; + + setup(async () => { + testClient = disposableStore.add(new UserDataSyncClient(server)); + await testClient.setUp(true); + testObject = (testClient.instantiationService.get(IUserDataSyncService) as UserDataSyncService).getSynchroniser(SyncResource.Snippets) as SnippetsSynchroniser; + disposableStore.add(toDisposable(() => testClient.instantiationService.get(IUserDataSyncStoreService).clear())); + + client2 = disposableStore.add(new UserDataSyncClient(server)); + await client2.setUp(true); + }); + + teardown(() => disposableStore.clear()); + + test('first time sync - outgoing to server (no snippets)', async () => { + await updateSnippet('html.json', htmlSnippet1, testClient); + await updateSnippet('typescript.json', tsSnippet1, testClient); + + await testObject.sync(); + assert.equal(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + + const { content } = await testClient.read(testObject.resource); + assert.ok(content !== null); + const actual = parseSnippets(content!); + assert.deepEqual(actual, { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }); + }); + + test('first time sync - incoming from server (no snippets)', async () => { + await updateSnippet('html.json', htmlSnippet1, client2); + await updateSnippet('typescript.json', tsSnippet1, client2); + await client2.sync(); + + await testObject.sync(); + assert.equal(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + + const actual1 = await readSnippet('html.json', testClient); + assert.equal(actual1, htmlSnippet1); + const actual2 = await readSnippet('typescript.json', testClient); + assert.equal(actual2, tsSnippet1); + }); + + test('first time sync when snippets exists', async () => { + await updateSnippet('html.json', htmlSnippet1, client2); + await client2.sync(); + + await updateSnippet('typescript.json', tsSnippet1, testClient); + await testObject.sync(); + assert.equal(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + + const actual1 = await readSnippet('html.json', testClient); + assert.equal(actual1, htmlSnippet1); + const actual2 = await readSnippet('typescript.json', testClient); + assert.equal(actual2, tsSnippet1); + + const { content } = await testClient.read(testObject.resource); + assert.ok(content !== null); + const actual = parseSnippets(content!); + assert.deepEqual(actual, { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }); + }); + + test('first time sync when snippets exists - has conflicts', async () => { + await updateSnippet('html.json', htmlSnippet1, client2); + await client2.sync(); + + await updateSnippet('html.json', htmlSnippet2, testClient); + await testObject.sync(); + + assert.equal(testObject.status, SyncStatus.HasConflicts); + const environmentService = testClient.instantiationService.get(IEnvironmentService); + const local = joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'); + assertConflicts(testObject.conflicts, [{ local, remote: local.with({ scheme: USER_DATA_SYNC_SCHEME }) }]); + }); + + test('first time sync when snippets exists - has conflicts and accept conflicts', async () => { + await updateSnippet('html.json', htmlSnippet1, client2); + await client2.sync(); + + await updateSnippet('html.json', htmlSnippet2, testClient); + await testObject.sync(); + const conflicts = testObject.conflicts; + await testObject.acceptConflict(conflicts[0].local, htmlSnippet1); + + assert.equal(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + const fileService = testClient.instantiationService.get(IFileService); + assert.ok(!await fileService.exists(conflicts[0].local)); + + const actual1 = await readSnippet('html.json', testClient); + assert.equal(actual1, htmlSnippet1); + + const { content } = await testClient.read(testObject.resource); + assert.ok(content !== null); + const actual = parseSnippets(content!); + assert.deepEqual(actual, { 'html.json': htmlSnippet1 }); + }); + + test('first time sync when snippets exists - has multiple conflicts', async () => { + await updateSnippet('html.json', htmlSnippet1, client2); + await updateSnippet('typescript.json', tsSnippet1, client2); + await client2.sync(); + + await updateSnippet('html.json', htmlSnippet2, testClient); + await updateSnippet('typescript.json', tsSnippet2, testClient); + await testObject.sync(); + + assert.equal(testObject.status, SyncStatus.HasConflicts); + const environmentService = testClient.instantiationService.get(IEnvironmentService); + const local1 = joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'); + const local2 = joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'); + assertConflicts(testObject.conflicts, [ + { local: local1, remote: local1.with({ scheme: USER_DATA_SYNC_SCHEME }) }, + { local: local2, remote: local2.with({ scheme: USER_DATA_SYNC_SCHEME }) } + ]); + }); + + test('first time sync when snippets exists - has multiple conflicts and accept one conflict', async () => { + await updateSnippet('html.json', htmlSnippet1, client2); + await updateSnippet('typescript.json', tsSnippet1, client2); + await client2.sync(); + + await updateSnippet('html.json', htmlSnippet2, testClient); + await updateSnippet('typescript.json', tsSnippet2, testClient); + await testObject.sync(); + + let conflicts = testObject.conflicts; + await testObject.acceptConflict(conflicts[0].local, htmlSnippet2); + const fileService = testClient.instantiationService.get(IFileService); + assert.ok(!await fileService.exists(conflicts[0].local)); + + conflicts = testObject.conflicts; + assert.equal(testObject.status, SyncStatus.HasConflicts); + const environmentService = testClient.instantiationService.get(IEnvironmentService); + const local = joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'); + assertConflicts(testObject.conflicts, [{ local, remote: local.with({ scheme: USER_DATA_SYNC_SCHEME }) }]); + }); + + test('first time sync when snippets exists - has multiple conflicts and accept all conflicts', async () => { + await updateSnippet('html.json', htmlSnippet1, client2); + await updateSnippet('typescript.json', tsSnippet1, client2); + await client2.sync(); + + await updateSnippet('html.json', htmlSnippet2, testClient); + await updateSnippet('typescript.json', tsSnippet2, testClient); + await testObject.sync(); + + const conflicts = testObject.conflicts; + await testObject.acceptConflict(conflicts[0].local, htmlSnippet2); + await testObject.acceptConflict(conflicts[1].local, tsSnippet1); + + assert.equal(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + const fileService = testClient.instantiationService.get(IFileService); + assert.ok(!await fileService.exists(conflicts[0].local)); + assert.ok(!await fileService.exists(conflicts[1].local)); + + const actual1 = await readSnippet('html.json', testClient); + assert.equal(actual1, htmlSnippet2); + const actual2 = await readSnippet('typescript.json', testClient); + assert.equal(actual2, tsSnippet1); + + const { content } = await testClient.read(testObject.resource); + assert.ok(content !== null); + const actual = parseSnippets(content!); + assert.deepEqual(actual, { 'html.json': htmlSnippet2, 'typescript.json': tsSnippet1 }); + }); + + test('sync adding a snippet', async () => { + await updateSnippet('html.json', htmlSnippet1, testClient); + await testObject.sync(); + + await updateSnippet('typescript.json', tsSnippet1, testClient); + await testObject.sync(); + assert.equal(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + + const actual1 = await readSnippet('html.json', testClient); + assert.equal(actual1, htmlSnippet1); + const actual2 = await readSnippet('typescript.json', testClient); + assert.equal(actual2, tsSnippet1); + + const { content } = await testClient.read(testObject.resource); + assert.ok(content !== null); + const actual = parseSnippets(content!); + assert.deepEqual(actual, { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }); + }); + + test('sync adding a snippet - accept', async () => { + await updateSnippet('html.json', htmlSnippet1, client2); + await client2.sync(); + await testObject.sync(); + + await updateSnippet('typescript.json', tsSnippet1, client2); + await client2.sync(); + + await testObject.sync(); + assert.equal(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + + const actual1 = await readSnippet('html.json', testClient); + assert.equal(actual1, htmlSnippet1); + const actual2 = await readSnippet('typescript.json', testClient); + assert.equal(actual2, tsSnippet1); + }); + + test('sync updating a snippet', async () => { + await updateSnippet('html.json', htmlSnippet1, testClient); + await testObject.sync(); + + await updateSnippet('html.json', htmlSnippet2, testClient); + await testObject.sync(); + assert.equal(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + + const actual1 = await readSnippet('html.json', testClient); + assert.equal(actual1, htmlSnippet2); + + const { content } = await testClient.read(testObject.resource); + assert.ok(content !== null); + const actual = parseSnippets(content!); + assert.deepEqual(actual, { 'html.json': htmlSnippet2 }); + }); + + test('sync updating a snippet - accept', async () => { + await updateSnippet('html.json', htmlSnippet1, client2); + await client2.sync(); + await testObject.sync(); + + await updateSnippet('html.json', htmlSnippet2, client2); + await client2.sync(); + + await testObject.sync(); + assert.equal(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + + const actual1 = await readSnippet('html.json', testClient); + assert.equal(actual1, htmlSnippet2); + }); + + test('sync updating a snippet - conflict', async () => { + await updateSnippet('html.json', htmlSnippet1, client2); + await client2.sync(); + await testObject.sync(); + + await updateSnippet('html.json', htmlSnippet2, client2); + await client2.sync(); + + await updateSnippet('html.json', htmlSnippet3, testClient); + await testObject.sync(); + assert.equal(testObject.status, SyncStatus.HasConflicts); + const environmentService = testClient.instantiationService.get(IEnvironmentService); + const local = joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'); + assertConflicts(testObject.conflicts, [{ local, remote: local.with({ scheme: USER_DATA_SYNC_SCHEME }) }]); + }); + + test('sync updating a snippet - resolve conflict', async () => { + await updateSnippet('html.json', htmlSnippet1, client2); + await client2.sync(); + await testObject.sync(); + + await updateSnippet('html.json', htmlSnippet2, client2); + await client2.sync(); + + await updateSnippet('html.json', htmlSnippet3, testClient); + await testObject.sync(); + await testObject.acceptConflict(testObject.conflicts[0].local, htmlSnippet2); + + assert.equal(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + + const actual1 = await readSnippet('html.json', testClient); + assert.equal(actual1, htmlSnippet2); + + const { content } = await testClient.read(testObject.resource); + assert.ok(content !== null); + const actual = parseSnippets(content!); + assert.deepEqual(actual, { 'html.json': htmlSnippet2 }); + }); + + test('sync removing a snippet', async () => { + await updateSnippet('html.json', htmlSnippet1, testClient); + await updateSnippet('typescript.json', tsSnippet1, testClient); + await testObject.sync(); + + await removeSnippet('html.json', testClient); + await testObject.sync(); + assert.equal(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + + const actual1 = await readSnippet('typescript.json', testClient); + assert.equal(actual1, tsSnippet1); + const actual2 = await readSnippet('html.json', testClient); + assert.equal(actual2, null); + + const { content } = await testClient.read(testObject.resource); + assert.ok(content !== null); + const actual = parseSnippets(content!); + assert.deepEqual(actual, { 'typescript.json': tsSnippet1 }); + }); + + test('sync removing a snippet - accept', async () => { + await updateSnippet('html.json', htmlSnippet1, client2); + await updateSnippet('typescript.json', tsSnippet1, client2); + await client2.sync(); + await testObject.sync(); + + await removeSnippet('html.json', client2); + await client2.sync(); + + await testObject.sync(); + assert.equal(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + + const actual1 = await readSnippet('typescript.json', testClient); + assert.equal(actual1, tsSnippet1); + const actual2 = await readSnippet('html.json', testClient); + assert.equal(actual2, null); + }); + + test('sync removing a snippet locally and updating it remotely', async () => { + await updateSnippet('html.json', htmlSnippet1, client2); + await updateSnippet('typescript.json', tsSnippet1, client2); + await client2.sync(); + await testObject.sync(); + + await updateSnippet('html.json', htmlSnippet2, client2); + await client2.sync(); + + await removeSnippet('html.json', testClient); + await testObject.sync(); + + assert.equal(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + + const actual1 = await readSnippet('typescript.json', testClient); + assert.equal(actual1, tsSnippet1); + const actual2 = await readSnippet('html.json', testClient); + assert.equal(actual2, htmlSnippet2); + }); + + test('sync removing a snippet - conflict', async () => { + await updateSnippet('html.json', htmlSnippet1, client2); + await updateSnippet('typescript.json', tsSnippet1, client2); + await client2.sync(); + await testObject.sync(); + + await removeSnippet('html.json', client2); + await client2.sync(); + + await updateSnippet('html.json', htmlSnippet2, testClient); + await testObject.sync(); + + assert.equal(testObject.status, SyncStatus.HasConflicts); + const environmentService = testClient.instantiationService.get(IEnvironmentService); + const local = joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'); + assertConflicts(testObject.conflicts, [{ local, remote: local.with({ scheme: USER_DATA_SYNC_SCHEME }) }]); + }); + + test('sync removing a snippet - resolve conflict', async () => { + await updateSnippet('html.json', htmlSnippet1, client2); + await updateSnippet('typescript.json', tsSnippet1, client2); + await client2.sync(); + await testObject.sync(); + + await removeSnippet('html.json', client2); + await client2.sync(); + + await updateSnippet('html.json', htmlSnippet2, testClient); + await testObject.sync(); + await testObject.acceptConflict(testObject.conflicts[0].local, htmlSnippet3); + + assert.equal(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + + const actual1 = await readSnippet('typescript.json', testClient); + assert.equal(actual1, tsSnippet1); + const actual2 = await readSnippet('html.json', testClient); + assert.equal(actual2, htmlSnippet3); + + const { content } = await testClient.read(testObject.resource); + assert.ok(content !== null); + const actual = parseSnippets(content!); + assert.deepEqual(actual, { 'typescript.json': tsSnippet1, 'html.json': htmlSnippet3 }); + }); + + test('sync removing a snippet - resolve conflict by removing', async () => { + await updateSnippet('html.json', htmlSnippet1, client2); + await updateSnippet('typescript.json', tsSnippet1, client2); + await client2.sync(); + await testObject.sync(); + + await removeSnippet('html.json', client2); + await client2.sync(); + + await updateSnippet('html.json', htmlSnippet2, testClient); + await testObject.sync(); + await testObject.acceptConflict(testObject.conflicts[0].local, ''); + + assert.equal(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + + const actual1 = await readSnippet('typescript.json', testClient); + assert.equal(actual1, tsSnippet1); + const actual2 = await readSnippet('html.json', testClient); + assert.equal(actual2, null); + + const { content } = await testClient.read(testObject.resource); + assert.ok(content !== null); + const actual = parseSnippets(content!); + assert.deepEqual(actual, { 'typescript.json': tsSnippet1 }); + }); + + test('first time sync - push', async () => { + await updateSnippet('html.json', htmlSnippet1, testClient); + await updateSnippet('typescript.json', tsSnippet1, testClient); + + await testObject.push(); + assert.equal(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + + const { content } = await testClient.read(testObject.resource); + assert.ok(content !== null); + const actual = parseSnippets(content!); + assert.deepEqual(actual, { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }); + }); + + test('first time sync - pull', async () => { + await updateSnippet('html.json', htmlSnippet1, client2); + await updateSnippet('typescript.json', tsSnippet1, client2); + await client2.sync(); + + await testObject.pull(); + assert.equal(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + + const actual1 = await readSnippet('html.json', testClient); + assert.equal(actual1, htmlSnippet1); + const actual2 = await readSnippet('typescript.json', testClient); + assert.equal(actual2, tsSnippet1); + }); + + function parseSnippets(content: string): IStringDictionary { + const syncData: ISyncData = JSON.parse(content); + return JSON.parse(syncData.content); + } + + async function updateSnippet(name: string, content: string, client: UserDataSyncClient): Promise { + const fileService = client.instantiationService.get(IFileService); + const environmentService = client.instantiationService.get(IEnvironmentService); + const snippetsResource = joinPath(environmentService.snippetsHome, name); + await fileService.writeFile(snippetsResource, VSBuffer.fromString(content)); + } + + async function removeSnippet(name: string, client: UserDataSyncClient): Promise { + const fileService = client.instantiationService.get(IFileService); + const environmentService = client.instantiationService.get(IEnvironmentService); + const snippetsResource = joinPath(environmentService.snippetsHome, name); + await fileService.del(snippetsResource); + } + + async function readSnippet(name: string, client: UserDataSyncClient): Promise { + const fileService = client.instantiationService.get(IFileService); + const environmentService = client.instantiationService.get(IEnvironmentService); + const snippetsResource = joinPath(environmentService.snippetsHome, name); + if (await fileService.exists(snippetsResource)) { + const content = await fileService.readFile(snippetsResource); + return content.value.toString(); + } + return null; + } + + function assertConflicts(actual: Conflict[], expected: Conflict[]) { + assert.deepEqual(actual.map(({ local, remote }) => ({ local: local.toString(), remote: remote.toString() })), expected.map(({ local, remote }) => ({ local: local.toString(), remote: remote.toString() }))); + } + +}); diff --git a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts new file mode 100644 index 00000000000..3c2583e4626 --- /dev/null +++ b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts @@ -0,0 +1,204 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IUserDataSyncStoreService, SyncResource, SyncStatus, IUserDataSyncEnablementService, ISyncPreviewResult } from 'vs/platform/userDataSync/common/userDataSync'; +import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { AbstractSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { Barrier } from 'vs/base/common/async'; +import { Emitter, Event } from 'vs/base/common/event'; + +class TestSynchroniser extends AbstractSynchroniser { + + syncBarrier: Barrier = new Barrier(); + syncResult: { status?: SyncStatus, error?: boolean } = {}; + onDoSyncCall: Emitter = this._register(new Emitter()); + + readonly resource: SyncResource = SyncResource.Settings; + protected readonly version: number = 1; + + private cancelled: boolean = false; + + protected async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { + this.cancelled = false; + this.onDoSyncCall.fire(); + await this.syncBarrier.wait(); + + if (this.cancelled) { + return SyncStatus.Idle; + } + + if (this.syncResult.error) { + throw new Error('failed'); + } + + await this.apply(remoteUserData.ref); + return this.syncResult.status || SyncStatus.Idle; + } + + async apply(ref: string): Promise { + ref = await this.userDataSyncStoreService.write(this.resource, '', ref); + await this.updateLastSyncUserData({ ref, syncData: { content: '', version: this.version } }); + } + + async stop(): Promise { + this.cancelled = true; + this.syncBarrier.open(); + } + + protected async generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { + return { hasLocalChanged: false, hasRemoteChanged: false }; + } + +} + +suite('TestSynchronizer', () => { + + const disposableStore = new DisposableStore(); + const server = new UserDataSyncTestServer(); + let client: UserDataSyncClient; + let userDataSyncStoreService: IUserDataSyncStoreService; + + setup(async () => { + client = disposableStore.add(new UserDataSyncClient(server)); + await client.setUp(); + userDataSyncStoreService = client.instantiationService.get(IUserDataSyncStoreService); + disposableStore.add(toDisposable(() => userDataSyncStoreService.clear())); + }); + + teardown(() => disposableStore.clear()); + + test('status is syncing', async () => { + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + + const actual: SyncStatus[] = []; + disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status))); + + const promise = Event.toPromise(testObject.onDoSyncCall.event); + + testObject.sync(); + await promise; + + assert.deepEqual(actual, [SyncStatus.Syncing]); + assert.deepEqual(testObject.status, SyncStatus.Syncing); + + testObject.stop(); + }); + + test('status is set correctly when sync is finished', async () => { + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + testObject.syncBarrier.open(); + + const actual: SyncStatus[] = []; + disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status))); + await testObject.sync(); + + assert.deepEqual(actual, [SyncStatus.Syncing, SyncStatus.Idle]); + assert.deepEqual(testObject.status, SyncStatus.Idle); + }); + + test('status is set correctly when sync has conflicts', async () => { + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + testObject.syncResult = { status: SyncStatus.HasConflicts }; + testObject.syncBarrier.open(); + + const actual: SyncStatus[] = []; + disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status))); + await testObject.sync(); + + assert.deepEqual(actual, [SyncStatus.Syncing, SyncStatus.HasConflicts]); + assert.deepEqual(testObject.status, SyncStatus.HasConflicts); + }); + + test('status is set correctly when sync has errors', async () => { + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + testObject.syncResult = { error: true }; + testObject.syncBarrier.open(); + + const actual: SyncStatus[] = []; + disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status))); + + try { + await testObject.sync(); + assert.fail('Should fail'); + } catch (e) { + assert.deepEqual(actual, [SyncStatus.Syncing, SyncStatus.Idle]); + assert.deepEqual(testObject.status, SyncStatus.Idle); + } + }); + + test('sync should not run if syncing already', async () => { + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + const promise = Event.toPromise(testObject.onDoSyncCall.event); + + testObject.sync(); + await promise; + + const actual: SyncStatus[] = []; + disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status))); + await testObject.sync(); + + assert.deepEqual(actual, []); + assert.deepEqual(testObject.status, SyncStatus.Syncing); + + testObject.stop(); + }); + + test('sync should not run if disabled', async () => { + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + client.instantiationService.get(IUserDataSyncEnablementService).setResourceEnablement(testObject.resource, false); + + const actual: SyncStatus[] = []; + disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status))); + + await testObject.sync(); + + assert.deepEqual(actual, []); + assert.deepEqual(testObject.status, SyncStatus.Idle); + }); + + test('sync should not run if there are conflicts', async () => { + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + testObject.syncResult = { status: SyncStatus.HasConflicts }; + testObject.syncBarrier.open(); + await testObject.sync(); + + const actual: SyncStatus[] = []; + disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status))); + await testObject.sync(); + + assert.deepEqual(actual, []); + assert.deepEqual(testObject.status, SyncStatus.HasConflicts); + }); + + test('request latest data on precondition failure', async () => { + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings); + // Sync once + testObject.syncBarrier.open(); + await testObject.sync(); + testObject.syncBarrier = new Barrier(); + + // update remote data before syncing so that 412 is thrown by server + const disposable = testObject.onDoSyncCall.event(async () => { + disposable.dispose(); + await testObject.apply(ref); + server.reset(); + testObject.syncBarrier.open(); + }); + + // Start sycing + const { ref } = await userDataSyncStoreService.read(testObject.resource, null); + await testObject.sync(ref); + + assert.deepEqual(server.requests, [ + { type: 'POST', url: `${server.url}/v1/resource/${testObject.resource}`, headers: { 'If-Match': ref } }, + { type: 'GET', url: `${server.url}/v1/resource/${testObject.resource}/latest`, headers: {} }, + { type: 'POST', url: `${server.url}/v1/resource/${testObject.resource}`, headers: { 'If-Match': `${parseInt(ref) + 1}` } }, + ]); + }); + + +}); diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index 74a42082391..0b2c11b9cbb 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -6,7 +6,7 @@ import { IRequestService } from 'vs/platform/request/common/request'; import { IRequestOptions, IRequestContext, IHeaders } from 'vs/base/parts/request/common/request'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IUserData, ResourceKey, IUserDataManifest, ALL_RESOURCE_KEYS, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSyncUtilService, IUserDataSyncEnablementService, ISettingsSyncService, IUserDataSyncService, getDefaultIgnoredSettings } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, IUserDataManifest, ALL_SYNC_RESOURCES, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncService, getDefaultIgnoredSettings, IUserDataSyncBackupStoreService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { bufferToStream, VSBuffer } from 'vs/base/common/buffer'; import { generateUuid } from 'vs/base/common/uuid'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; @@ -31,11 +31,12 @@ import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagemen import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; import { Disposable } from 'vs/base/common/lifecycle'; -import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; import { Emitter } from 'vs/base/common/event'; import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; +import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService'; +import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; export class UserDataSyncClient extends Disposable { @@ -52,16 +53,23 @@ export class UserDataSyncClient extends Disposable { const environmentService = this.instantiationService.stub(IEnvironmentService, >{ userDataSyncHome, settingsResource: joinPath(userDataDirectory, 'settings.json'), - settingsSyncPreviewResource: joinPath(userDataSyncHome, 'settings.json'), keybindingsResource: joinPath(userDataDirectory, 'keybindings.json'), - keybindingsSyncPreviewResource: joinPath(userDataSyncHome, 'keybindings.json'), + snippetsHome: joinPath(userDataDirectory, 'snippets'), argvResource: joinPath(userDataDirectory, 'argv.json'), + args: {} }); const logService = new NullLogService(); this.instantiationService.stub(ILogService, logService); - this.instantiationService.stub(IProductService, { _serviceBrand: undefined, ...product }); + this.instantiationService.stub(IProductService, { + _serviceBrand: undefined, ...product, ...{ + 'configurationSync.store': { + url: this.testServer.url, + authenticationProviderId: 'test' + } + } + }); const fileService = this._register(new FileService(logService)); fileService.registerProvider(Schemas.inMemory, new InMemoryFileSystemProvider()); @@ -69,13 +77,6 @@ export class UserDataSyncClient extends Disposable { this.instantiationService.stub(IStorageService, new InMemoryStorageService()); - await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ - 'configurationSync.store': { - url: this.testServer.url, - authenticationProviderId: 'test' - } - }))); - const configurationService = new ConfigurationService(environmentService.settingsResource, fileService); await configurationService.initialize(); this.instantiationService.stub(IConfigurationService, configurationService); @@ -89,8 +90,10 @@ export class UserDataSyncClient extends Disposable { this.instantiationService.stub(IUserDataSyncLogService, logService); this.instantiationService.stub(ITelemetryService, NullTelemetryService); this.instantiationService.stub(IUserDataSyncStoreService, this.instantiationService.createInstance(UserDataSyncStoreService)); + this.instantiationService.stub(IUserDataSyncBackupStoreService, this.instantiationService.createInstance(UserDataSyncBackupStoreService)); this.instantiationService.stub(IUserDataSyncUtilService, new TestUserDataSyncUtilService()); this.instantiationService.stub(IUserDataSyncEnablementService, this.instantiationService.createInstance(UserDataSyncEnablementService)); + this.instantiationService.stub(IStorageKeysSyncRegistryService, this.instantiationService.createInstance(StorageKeysSyncRegistryService)); this.instantiationService.stub(IGlobalExtensionEnablementService, this.instantiationService.createInstance(GlobalExtensionEnablementService)); this.instantiationService.stub(IExtensionManagementService, >{ @@ -103,18 +106,25 @@ export class UserDataSyncClient extends Disposable { async getCompatibleExtension() { return null; } }); - this.instantiationService.stub(ISettingsSyncService, this.instantiationService.createInstance(SettingsSynchroniser)); this.instantiationService.stub(IUserDataSyncService, this.instantiationService.createInstance(UserDataSyncService)); - if (empty) { - await fileService.del(environmentService.settingsResource); - } else { + if (!empty) { + await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({}))); await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString(JSON.stringify([]))); + await fileService.writeFile(joinPath(environmentService.snippetsHome, 'c.json'), VSBuffer.fromString(`{}`)); await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'en' }))); } await configurationService.reloadConfiguration(); } + sync(): Promise { + return this.instantiationService.get(IUserDataSyncService).sync(); + } + + read(resource: SyncResource): Promise { + return this.instantiationService.get(IUserDataSyncStoreService).read(resource, null); + } + } export class UserDataSyncTestServer implements IRequestService { @@ -123,7 +133,7 @@ export class UserDataSyncTestServer implements IRequestService { readonly url: string = 'http://host:3000'; private session: string | null = null; - private readonly data: Map = new Map(); + private readonly data: Map = new Map(); private _requests: { url: string, type: string, headers?: IHeaders }[] = []; get requests(): { url: string, type: string, headers?: IHeaders }[] { return this._requests; } @@ -171,7 +181,7 @@ export class UserDataSyncTestServer implements IRequestService { private async getManifest(headers?: IHeaders): Promise { if (this.session) { - const latest: Record = Object.create({}); + const latest: Record = Object.create({}); const manifest: IUserDataManifest = { session: this.session, latest }; this.data.forEach((value, key) => latest[key] = value.ref); return this.toResponse(200, { 'Content-Type': 'application/json' }, JSON.stringify(manifest)); @@ -180,7 +190,7 @@ export class UserDataSyncTestServer implements IRequestService { } private async getLatestData(resource: string, headers: IHeaders = {}): Promise { - const resourceKey = ALL_RESOURCE_KEYS.find(key => key === resource); + const resourceKey = ALL_SYNC_RESOURCES.find(key => key === resource); if (resourceKey) { const data = this.data.get(resourceKey); if (!data) { @@ -195,16 +205,13 @@ export class UserDataSyncTestServer implements IRequestService { } private async writeData(resource: string, content: string = '', headers: IHeaders = {}): Promise { - if (!headers['If-Match']) { - return this.toResponse(428); - } if (!this.session) { this.session = generateUuid(); } - const resourceKey = ALL_RESOURCE_KEYS.find(key => key === resource); + const resourceKey = ALL_SYNC_RESOURCES.find(key => key === resource); if (resourceKey) { const data = this.data.get(resourceKey); - if (headers['If-Match'] !== (data ? data.ref : '0')) { + if (headers['If-Match'] !== undefined && headers['If-Match'] !== (data ? data.ref : '0')) { return this.toResponse(412); } const ref = `${parseInt(data?.ref || '0') + 1}`; diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts b/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts index 86ca60f8a0a..5c2b51c2840 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts @@ -4,12 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IUserDataSyncService, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IFileService } from 'vs/platform/files/common/files'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { VSBuffer } from 'vs/base/common/buffer'; +import { joinPath } from 'vs/base/common/resources'; suite('UserDataSyncService', () => { @@ -36,6 +37,9 @@ suite('UserDataSyncService', () => { // Keybindings { type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} }, { type: 'POST', url: `${target.url}/v1/resource/keybindings`, headers: { 'If-Match': '0' } }, + // Snippets + { type: 'GET', url: `${target.url}/v1/resource/snippets/latest`, headers: {} }, + { type: 'POST', url: `${target.url}/v1/resource/snippets`, headers: { 'If-Match': '0' } }, // Global state { type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} }, { type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '0' } }, @@ -65,6 +69,9 @@ suite('UserDataSyncService', () => { { type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} }, // Keybindings { type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} }, + // Snippets + { type: 'GET', url: `${target.url}/v1/resource/snippets/latest`, headers: {} }, + { type: 'POST', url: `${target.url}/v1/resource/snippets`, headers: { 'If-Match': '0' } }, // Global state { type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} }, { type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '0' } }, @@ -96,15 +103,17 @@ suite('UserDataSyncService', () => { await testObject.pull(); assert.deepEqual(target.requests, [ - // Manifest + /* first time sync */ { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, - // Settings { type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} }, - // Keybindings { type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} }, - // Global state + { type: 'GET', url: `${target.url}/v1/resource/snippets/latest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} }, + /* pull */ + { type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/resource/snippets/latest`, headers: {} }, { type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} }, - // Extensions { type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} }, ]); @@ -134,15 +143,14 @@ suite('UserDataSyncService', () => { await testObject.pull(); assert.deepEqual(target.requests, [ - // Manifest + /* first time sync */ { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, - // Settings { type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} }, - // Keybindings + /* pull */ + { type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} }, { type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} }, - // Global state + { type: 'GET', url: `${target.url}/v1/resource/snippets/latest`, headers: {} }, { type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} }, - // Extensions { type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} }, ]); @@ -167,16 +175,18 @@ suite('UserDataSyncService', () => { await testObject.sync(); assert.deepEqual(target.requests, [ - // Manifest + /* first time sync */ { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, - { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, - // Settings { type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} }, - // Keybindings { type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} }, - // Global state + { type: 'GET', url: `${target.url}/v1/resource/snippets/latest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} }, + /* sync */ + { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/resource/snippets/latest`, headers: {} }, { type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} }, - // Extensions { type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} }, ]); @@ -198,6 +208,7 @@ suite('UserDataSyncService', () => { await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 14 }))); await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString(JSON.stringify([{ 'command': 'abcd', 'key': 'cmd+c' }]))); await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'de' }))); + await fileService.writeFile(joinPath(environmentService.snippetsHome, 'html.json'), VSBuffer.fromString(`{}`)); const testObject = testClient.instantiationService.get(IUserDataSyncService); // Sync (merge) from the test client @@ -206,19 +217,19 @@ suite('UserDataSyncService', () => { await testObject.sync(); assert.deepEqual(target.requests, [ - // Manifest + /* first time sync */ { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} }, + + /* first time sync */ { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, - // Settings { type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} }, { type: 'POST', url: `${target.url}/v1/resource/settings`, headers: { 'If-Match': '1' } }, - // Keybindings { type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} }, { type: 'POST', url: `${target.url}/v1/resource/keybindings`, headers: { 'If-Match': '1' } }, - // Global state + { type: 'GET', url: `${target.url}/v1/resource/snippets/latest`, headers: {} }, + { type: 'POST', url: `${target.url}/v1/resource/snippets`, headers: { 'If-Match': '1' } }, { type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} }, - { type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '1' } }, - // Extensions { type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} }, ]); @@ -258,6 +269,7 @@ suite('UserDataSyncService', () => { const environmentService = client.instantiationService.get(IEnvironmentService); await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 14 }))); await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString(JSON.stringify([{ 'command': 'abcd', 'key': 'cmd+c' }]))); + await fileService.writeFile(joinPath(environmentService.snippetsHome, 'html.json'), VSBuffer.fromString(`{}`)); await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'de' }))); // Sync from the client @@ -270,6 +282,8 @@ suite('UserDataSyncService', () => { { type: 'POST', url: `${target.url}/v1/resource/settings`, headers: { 'If-Match': '1' } }, // Keybindings { type: 'POST', url: `${target.url}/v1/resource/keybindings`, headers: { 'If-Match': '1' } }, + // Snippets + { type: 'POST', url: `${target.url}/v1/resource/snippets`, headers: { 'If-Match': '1' } }, // Global state { type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '1' } }, ]); @@ -294,6 +308,7 @@ suite('UserDataSyncService', () => { const environmentService = client.instantiationService.get(IEnvironmentService); await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 14 }))); await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString(JSON.stringify([{ 'command': 'abcd', 'key': 'cmd+c' }]))); + await fileService.writeFile(joinPath(environmentService.snippetsHome, 'html.json'), VSBuffer.fromString(`{ "a": "changed" }`)); await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'de' }))); await client.instantiationService.get(IUserDataSyncService).sync(); @@ -308,6 +323,8 @@ suite('UserDataSyncService', () => { { type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: { 'If-None-Match': '1' } }, // Keybindings { type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: { 'If-None-Match': '1' } }, + // Snippets + { type: 'GET', url: `${target.url}/v1/resource/snippets/latest`, headers: { 'If-None-Match': '1' } }, // Global state { type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: { 'If-None-Match': '1' } }, ]); @@ -359,6 +376,9 @@ suite('UserDataSyncService', () => { // Keybindings { type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} }, { type: 'POST', url: `${target.url}/v1/resource/keybindings`, headers: { 'If-Match': '0' } }, + // Snippets + { type: 'GET', url: `${target.url}/v1/resource/snippets/latest`, headers: {} }, + { type: 'POST', url: `${target.url}/v1/resource/snippets`, headers: { 'If-Match': '0' } }, // Global state { type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} }, { type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '0' } }, @@ -454,7 +474,7 @@ suite('UserDataSyncService', () => { await testObject.sync(); disposable.dispose(); - assert.deepEqual(actualStatuses, [SyncStatus.Syncing, SyncStatus.Idle, SyncStatus.Syncing, SyncStatus.Idle, SyncStatus.Syncing, SyncStatus.Idle, SyncStatus.Syncing, SyncStatus.Idle]); + assert.deepEqual(actualStatuses, [SyncStatus.Syncing, SyncStatus.Idle, SyncStatus.Syncing, SyncStatus.Idle, SyncStatus.Syncing, SyncStatus.Idle, SyncStatus.Syncing, SyncStatus.Idle, SyncStatus.Syncing, SyncStatus.Idle]); }); test('test sync conflicts status', async () => { @@ -480,7 +500,7 @@ suite('UserDataSyncService', () => { await testObject.sync(); assert.deepEqual(testObject.status, SyncStatus.HasConflicts); - assert.deepEqual(testObject.conflictsSources, [SyncSource.Settings]); + assert.deepEqual(testObject.conflicts.map(({ syncResource }) => syncResource), [SyncResource.Settings]); }); test('test sync will sync other non conflicted areas', async () => { @@ -549,7 +569,7 @@ suite('UserDataSyncService', () => { await testObject.stop(); assert.deepEqual(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflictsSources, []); + assert.deepEqual(testObject.conflicts, []); }); }); diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 78210255a5f..7d809ddc737 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -3,11 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IProcessEnvironment, isMacintosh, isLinux, isWeb } from 'vs/base/common/platform'; +import { isMacintosh, isLinux, isWeb } from 'vs/base/common/platform'; import { ParsedArgs, IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { ExportData } from 'vs/base/common/performance'; -import { LogLevel } from 'vs/platform/log/common/log'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -17,6 +15,7 @@ export interface IOpenedWindow { folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; + dirty: boolean; } export interface IBaseOpenWindowsOptions { @@ -220,41 +219,17 @@ export interface IAddFoldersRequest { } export interface IWindowConfiguration extends ParsedArgs { - machineId?: string; // NOTE: This is undefined in the web, the telemetry service directly resolves this. - windowId: number; // TODO: should we deprecate this in favor of sessionId? sessionId: string; - logLevel: LogLevel; - mainPid: number; - - appRoot: string; - execPath: string; - isInitialStartup?: boolean; - - userEnv: IProcessEnvironment; - nodeCachedDataDir?: string; - - backupPath?: string; backupWorkspaceResource?: URI; - workspace?: IWorkspaceIdentifier; - folderUri?: ISingleFolderWorkspaceIdentifier; - remoteAuthority?: string; connectionToken?: string; - zoomLevel?: number; - fullscreen?: boolean; - maximized?: boolean; highContrast?: boolean; - accessibilitySupport?: boolean; - partsSplashPath?: string; - - perfEntries: ExportData; filesToOpenOrCreate?: IPath[]; filesToDiff?: IPath[]; - filesToWait?: IPathsToWaitFor; } export interface IRunActionInWindowRequest { diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 9d519473683..b88164b804f 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { OpenContext, IWindowConfiguration, IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; +import { OpenContext, IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; +import { INativeWindowConfiguration } from 'vs/platform/windows/node/window'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -39,7 +40,7 @@ export interface ICodeWindow extends IDisposable { readonly id: number; readonly win: BrowserWindow; - readonly config: IWindowConfiguration | undefined; + readonly config: INativeWindowConfiguration | undefined; readonly openedFolderUri?: URI; readonly openedWorkspace?: IWorkspaceIdentifier; @@ -60,8 +61,8 @@ export interface ICodeWindow extends IDisposable { addTabbedWindow(window: ICodeWindow): void; - load(config: IWindowConfiguration, isReload?: boolean): void; - reload(configuration?: IWindowConfiguration, cli?: ParsedArgs): void; + load(config: INativeWindowConfiguration, isReload?: boolean): void; + reload(configuration?: INativeWindowConfiguration, cli?: ParsedArgs): void; focus(): void; close(): void; @@ -79,6 +80,9 @@ export interface ICodeWindow extends IDisposable { setRepresentedFilename(name: string): void; getRepresentedFilename(): string | undefined; + setDocumentEdited(edited: boolean): void; + isDocumentEdited(): boolean; + handleTitleDoubleClick(): void; updateTouchBar(items: ISerializableCommandAction[][]): void; diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index bc8e53b1e21..8a77432d355 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -18,8 +18,8 @@ import { parseLineAndColumnAware } from 'vs/code/node/paths'; import { ILifecycleMainService, UnloadReason, LifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, IPathsToWaitFor, isFileToOpen, isWorkspaceToOpen, isFolderToOpen, IWindowOpenable, IOpenEmptyWindowOptions, IAddFoldersRequest } from 'vs/platform/windows/common/windows'; -import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderUri } from 'vs/platform/windows/node/window'; +import { IWindowSettings, OpenContext, IPath, IPathsToWaitFor, isFileToOpen, isWorkspaceToOpen, isFolderToOpen, IWindowOpenable, IOpenEmptyWindowOptions, IAddFoldersRequest } from 'vs/platform/windows/common/windows'; +import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderUri, INativeWindowConfiguration } from 'vs/platform/windows/node/window'; import { Emitter } from 'vs/base/common/event'; import product from 'vs/platform/product/common/product'; import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows'; @@ -454,16 +454,14 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // // These are windows to restore because of hot-exit or from previous session (only performed once on startup!) // - let foldersToRestore: URI[] = []; let workspacesToRestore: IWorkspacePathToOpen[] = []; if (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath && !openConfig.cli['disable-restore-windows']) { - let foldersToRestore = this.backupMainService.getFolderBackupPaths(); - foldersToOpen.push(...foldersToRestore.map(f => ({ folderUri: f, remoteAuhority: getRemoteAuthority(f) }))); - // collect from workspaces with hot-exit backups and from previous window session - workspacesToRestore = [...this.backupMainService.getWorkspaceBackups(), ...this.workspacesMainService.getUntitledWorkspacesSync()]; + // Untitled workspaces are always restored + workspacesToRestore = this.workspacesMainService.getUntitledWorkspacesSync(); workspacesToOpen.push(...workspacesToRestore); + // Empty windows with backups are always restored emptyToRestore.push(...this.backupMainService.getEmptyWindowBackupPaths()); } else { emptyToRestore.length = 0; @@ -495,7 +493,6 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic const usedWindow = usedWindows[i]; if ( (usedWindow.openedWorkspace && workspacesToRestore.some(workspace => usedWindow.openedWorkspace && workspace.workspace.id === usedWindow.openedWorkspace.id)) || // skip over restored workspace - (usedWindow.openedFolderUri && foldersToRestore.some(uri => isEqual(uri, usedWindow.openedFolderUri))) || // skip over restored folder (usedWindow.backupPath && emptyToRestore.some(empty => usedWindow.backupPath && empty.backupFolder === basename(usedWindow.backupPath))) // skip over restored empty window ) { continue; @@ -1354,8 +1351,8 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic private openInBrowserWindow(options: IOpenBrowserWindowOptions): ICodeWindow { - // Build IWindowConfiguration from config and options - const configuration: IWindowConfiguration = mixin({}, options.cli); // inherit all properties from CLI + // Build INativeWindowConfiguration from config and options + const configuration: INativeWindowConfiguration = mixin({}, options.cli); // inherit all properties from CLI configuration.appRoot = this.environmentService.appRoot; configuration.machineId = this.machineId; configuration.nodeCachedDataDir = this.environmentService.nodeCachedDataDir; @@ -1482,7 +1479,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic return window; } - private doOpenInBrowserWindow(window: ICodeWindow, configuration: IWindowConfiguration, options: IOpenBrowserWindowOptions): void { + private doOpenInBrowserWindow(window: ICodeWindow, configuration: INativeWindowConfiguration, options: IOpenBrowserWindowOptions): void { // Register window for backups if (!configuration.extensionDevelopmentPath) { @@ -1500,7 +1497,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic window.load(configuration); } - private getNewWindowState(configuration: IWindowConfiguration): INewWindowState { + private getNewWindowState(configuration: INativeWindowConfiguration): INewWindowState { const lastActive = this.getLastActiveWindow(); // Restore state unless we are running extension tests diff --git a/src/vs/platform/windows/node/window.ts b/src/vs/platform/windows/node/window.ts index 08a4e800d3b..8985cf9dbd8 100644 --- a/src/vs/platform/windows/node/window.ts +++ b/src/vs/platform/windows/node/window.ts @@ -3,12 +3,42 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { OpenContext, IOpenWindowOptions } from 'vs/platform/windows/common/windows'; +import { OpenContext, IOpenWindowOptions, IWindowConfiguration, IPathsToWaitFor } from 'vs/platform/windows/common/windows'; import { URI } from 'vs/base/common/uri'; import * as platform from 'vs/base/common/platform'; import * as extpath from 'vs/base/common/extpath'; import { IWorkspaceIdentifier, IResolvedWorkspace, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { isEqual, isEqualOrParent } from 'vs/base/common/resources'; +import { LogLevel } from 'vs/platform/log/common/log'; +import { ExportData } from 'vs/base/common/performance'; + +export interface INativeWindowConfiguration extends IWindowConfiguration { + mainPid: number; + + windowId: number; + machineId: string; + + appRoot: string; + execPath: string; + backupPath?: string; + + nodeCachedDataDir?: string; + partsSplashPath: string; + + workspace?: IWorkspaceIdentifier; + folderUri?: ISingleFolderWorkspaceIdentifier; + + isInitialStartup?: boolean; + logLevel: LogLevel; + zoomLevel?: number; + fullscreen?: boolean; + maximized?: boolean; + accessibilitySupport?: boolean; + perfEntries: ExportData; + + userEnv: platform.IProcessEnvironment; + filesToWait?: IPathsToWaitFor; +} export interface INativeOpenWindowOptions extends IOpenWindowOptions { diffMode?: boolean; diff --git a/src/vs/platform/workspace/common/workspace.ts b/src/vs/platform/workspace/common/workspace.ts index d52bbb4199d..b10c023d005 100644 --- a/src/vs/platform/workspace/common/workspace.ts +++ b/src/vs/platform/workspace/common/workspace.ts @@ -115,7 +115,7 @@ export interface IWorkspaceFolderData { /** * The name of this workspace folder. Defaults to - * the basename its [uri-path](#Uri.path) + * the basename of its [uri-path](#Uri.path) */ readonly name: string; diff --git a/src/vs/platform/workspaces/common/workspaces.ts b/src/vs/platform/workspaces/common/workspaces.ts index b904c106efa..dc4ef3687f1 100644 --- a/src/vs/platform/workspaces/common/workspaces.ts +++ b/src/vs/platform/workspaces/common/workspaces.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { URI, UriComponents } from 'vs/base/common/uri'; import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; -import { extname } from 'vs/base/common/path'; +import { extname, isAbsolute } from 'vs/base/common/path'; import { dirname, resolvePath, isEqualAuthority, isEqualOrParent, relativePath, extname as resourceExtname } from 'vs/base/common/resources'; import * as jsonEdit from 'vs/base/common/jsonEdit'; import * as json from 'vs/base/common/json'; @@ -18,7 +18,7 @@ import { toSlashes } from 'vs/base/common/extpath'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; import { ILogService } from 'vs/platform/log/common/log'; -import { Event as CommonEvent } from 'vs/base/common/event'; +import { Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; export const WORKSPACE_EXTENSION = 'code-workspace'; @@ -31,18 +31,21 @@ export interface IWorkspacesService { _serviceBrand: undefined; - // Management + // Workspaces Management enterWorkspace(path: URI): Promise; createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise; deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise; getWorkspaceIdentifier(workspacePath: URI): Promise; - // History - readonly onRecentlyOpenedChange: CommonEvent; + // Workspaces History + readonly onRecentlyOpenedChange: Event; addRecentlyOpened(recents: IRecent[]): Promise; removeRecentlyOpened(workspaces: URI[]): Promise; clearRecentlyOpened(): Promise; getRecentlyOpened(): Promise; + + // Dirty Workspaces + getDirtyWorkspaces(): Promise>; } export interface IRecentlyOpened { @@ -203,21 +206,22 @@ const SLASH = '/'; * Undefined is returned if the folderURI and the targetConfigFolderURI don't have the same schema or authority * * @param folderURI a workspace folder + * @param forceAbsolute if set, keep the path absolute * @param folderName a workspace name * @param targetConfigFolderURI the folder where the workspace is living in * @param useSlashForPath if set, use forward slashes for file paths on windows */ -export function getStoredWorkspaceFolder(folderURI: URI, folderName: string | undefined, targetConfigFolderURI: URI, useSlashForPath = !isWindows): IStoredWorkspaceFolder { +export function getStoredWorkspaceFolder(folderURI: URI, forceAbsolute: boolean, folderName: string | undefined, targetConfigFolderURI: URI, useSlashForPath = !isWindows): IStoredWorkspaceFolder { if (folderURI.scheme !== targetConfigFolderURI.scheme) { return { name: folderName, uri: folderURI.toString(true) }; } - let folderPath: string | undefined; - if (isEqualOrParent(folderURI, targetConfigFolderURI)) { - // use relative path - folderPath = relativePath(targetConfigFolderURI, folderURI) || '.'; // always uses forward slashes - if (isWindows && folderURI.scheme === Schemas.file && !useSlashForPath) { + let folderPath = !forceAbsolute ? relativePath(targetConfigFolderURI, folderURI) : undefined; + if (folderPath !== undefined) { + if (folderPath.length === 0) { + folderPath = '.'; + } else if (isWindows && folderURI.scheme === Schemas.file && !useSlashForPath) { // Windows gets special treatment: // - use backslahes unless slash is used by other existing folders folderPath = folderPath.replace(/\//g, '\\'); @@ -249,7 +253,7 @@ export function getStoredWorkspaceFolder(folderURI: URI, folderName: string | un * Rewrites the content of a workspace file to be saved at a new location. * Throws an exception if file is not a valid workspace file */ -export function rewriteWorkspaceFileForNewLocation(rawWorkspaceContents: string, configPathURI: URI, targetConfigPathURI: URI) { +export function rewriteWorkspaceFileForNewLocation(rawWorkspaceContents: string, configPathURI: URI, isFromUntitledWorkspace: boolean, targetConfigPathURI: URI) { let storedWorkspace = doParseStoredWorkspace(configPathURI, rawWorkspaceContents); const sourceConfigFolder = dirname(configPathURI); @@ -258,12 +262,17 @@ export function rewriteWorkspaceFileForNewLocation(rawWorkspaceContents: string, const rewrittenFolders: IStoredWorkspaceFolder[] = []; const slashForPath = useSlashForPath(storedWorkspace.folders); - // Rewrite absolute paths to relative paths if the target workspace folder - // is a parent of the location of the workspace file itself. Otherwise keep - // using absolute paths. for (const folder of storedWorkspace.folders) { - let folderURI = isRawFileWorkspaceFolder(folder) ? resolvePath(sourceConfigFolder, folder.path) : URI.parse(folder.uri); - rewrittenFolders.push(getStoredWorkspaceFolder(folderURI, folder.name, targetConfigFolder, slashForPath)); + const folderURI = isRawFileWorkspaceFolder(folder) ? resolvePath(sourceConfigFolder, folder.path) : URI.parse(folder.uri); + let absolute; + if (isFromUntitledWorkspace) { + // if it was an untitled workspace, try to make paths relative + absolute = false; + } else { + // for existing workspaces, preserve whether a path was absolute or relative + absolute = !isRawFileWorkspaceFolder(folder) || isAbsolute(folder.path); + } + rewrittenFolders.push(getStoredWorkspaceFolder(folderURI, absolute, folder.name, targetConfigFolder, slashForPath)); } // Preserve as much of the existing workspace as possible by using jsonEdit diff --git a/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts index f88997659d3..6f88e7b8339 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts @@ -9,7 +9,6 @@ import { IStateService } from 'vs/platform/state/node/state'; import { app, JumpListCategory } from 'electron'; import { ILogService } from 'vs/platform/log/common/log'; import { getBaseLabel, getPathLabel, splitName } from 'vs/base/common/labels'; -import { IPath } from 'vs/platform/windows/common/windows'; import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; import { isWindows, isMacintosh } from 'vs/base/common/platform'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IRecentlyOpened, isRecentWorkspace, isRecentFolder, IRecent, isRecentFile, IRecentFolder, IRecentWorkspace, IRecentFile, toStoreData, restoreRecentlyOpened, RecentlyOpenedStorageData } from 'vs/platform/workspaces/common/workspaces'; @@ -24,6 +23,7 @@ import { exists } from 'vs/base/node/pfs'; import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Disposable } from 'vs/base/common/lifecycle'; +import { ICodeWindow } from 'vs/platform/windows/electron-main/windows'; export const IWorkspacesHistoryMainService = createDecorator('workspacesHistoryMainService'); @@ -34,7 +34,7 @@ export interface IWorkspacesHistoryMainService { readonly onRecentlyOpenedChange: CommonEvent; addRecentlyOpened(recents: IRecent[]): void; - getRecentlyOpened(currentWorkspace?: IWorkspaceIdentifier, currentFolder?: ISingleFolderWorkspaceIdentifier, currentFiles?: IPath[]): IRecentlyOpened; + getRecentlyOpened(include?: ICodeWindow): IRecentlyOpened; removeRecentlyOpened(paths: URI[]): void; clearRecentlyOpened(): void; @@ -241,20 +241,23 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa this._onRecentlyOpenedChange.fire(); } - getRecentlyOpened(currentWorkspace?: IWorkspaceIdentifier, currentFolder?: ISingleFolderWorkspaceIdentifier, currentFiles?: IPath[]): IRecentlyOpened { + getRecentlyOpened(include?: ICodeWindow): IRecentlyOpened { const workspaces: Array = []; const files: IRecentFile[] = []; // Add current workspace to beginning if set + const currentWorkspace = include?.config?.workspace; if (currentWorkspace && !this.workspacesMainService.isUntitledWorkspace(currentWorkspace)) { workspaces.push({ workspace: currentWorkspace }); } + const currentFolder = include?.config?.folderUri; if (currentFolder) { workspaces.push({ folderUri: currentFolder }); } // Add currently files to open to the beginning if any + const currentFiles = include?.config?.filesToOpenOrCreate; if (currentFiles) { for (let currentFile of currentFiles) { const fileUri = currentFile.fileUri; @@ -402,14 +405,14 @@ function location(recent: IRecent): URI { return recent.workspace.configPath; } -function indexOfWorkspace(arr: IRecent[], workspace: IWorkspaceIdentifier): number { - return arrays.firstIndex(arr, w => isRecentWorkspace(w) && w.workspace.id === workspace.id); +function indexOfWorkspace(arr: IRecent[], candidate: IWorkspaceIdentifier): number { + return arr.findIndex(workspace => isRecentWorkspace(workspace) && workspace.workspace.id === candidate.id); } -function indexOfFolder(arr: IRecent[], folderURI: ISingleFolderWorkspaceIdentifier): number { - return arrays.firstIndex(arr, f => isRecentFolder(f) && areResourcesEqual(f.folderUri, folderURI)); +function indexOfFolder(arr: IRecent[], candidate: ISingleFolderWorkspaceIdentifier): number { + return arr.findIndex(folder => isRecentFolder(folder) && areResourcesEqual(folder.folderUri, candidate)); } -function indexOfFile(arr: IRecentFile[], fileURI: URI): number { - return arrays.firstIndex(arr, f => areResourcesEqual(f.fileUri, fileURI)); +function indexOfFile(arr: IRecentFile[], candidate: URI): number { + return arr.findIndex(file => areResourcesEqual(file.fileUri, candidate)); } diff --git a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts index 68dc1d14adb..fb710d10dc7 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts @@ -175,7 +175,7 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain const storedWorkspaceFolder: IStoredWorkspaceFolder[] = []; for (const folder of folders) { - storedWorkspaceFolder.push(getStoredWorkspaceFolder(folder.uri, folder.name, untitledWorkspaceConfigFolder)); + storedWorkspaceFolder.push(getStoredWorkspaceFolder(folder.uri, true, folder.name, untitledWorkspaceConfigFolder)); } return { diff --git a/src/vs/platform/workspaces/electron-main/workspacesService.ts b/src/vs/platform/workspaces/electron-main/workspacesService.ts index 3084a7ea969..c5a12d23e58 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesService.ts @@ -9,6 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService'; +import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; export class WorkspacesService implements AddFirstParameterToFunctions /* only methods, not events */, number /* window ID */> { @@ -17,7 +18,8 @@ export class WorkspacesService implements AddFirstParameterToFunctions { - const window = this.windowsMainService.getWindowById(windowId); - if (window?.config) { - return this.workspacesHistoryMainService.getRecentlyOpened(window.config.workspace, window.config.folderUri, window.config.filesToOpenOrCreate); - } - - return this.workspacesHistoryMainService.getRecentlyOpened(); + return this.workspacesHistoryMainService.getRecentlyOpened(this.windowsMainService.getWindowById(windowId)); } async addRecentlyOpened(windowId: number, recents: IRecent[]): Promise { @@ -72,4 +69,13 @@ export class WorkspacesService implements AddFirstParameterToFunctions> { + return this.backupMainService.getDirtyWorkspaces(); + } + + //#endregion } diff --git a/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts b/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts index f460432d840..2356a099f18 100644 --- a/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts +++ b/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts @@ -11,7 +11,7 @@ import * as pfs from 'vs/base/node/pfs'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { WorkspacesMainService, IStoredWorkspace } from 'vs/platform/workspaces/electron-main/workspacesMainService'; -import { WORKSPACE_EXTENSION, IRawFileWorkspaceFolder, IWorkspaceFolderCreationData, IRawUriWorkspaceFolder, rewriteWorkspaceFileForNewLocation, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { WORKSPACE_EXTENSION, IRawFileWorkspaceFolder, IWorkspaceFolderCreationData, IRawUriWorkspaceFolder, rewriteWorkspaceFileForNewLocation, IWorkspaceIdentifier, IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; import { NullLogService } from 'vs/platform/log/common/log'; import { URI } from 'vs/base/common/uri'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; @@ -56,6 +56,7 @@ export class TestDialogMainService implements IDialogMainService { } export class TestBackupMainService implements IBackupMainService { + _serviceBrand: undefined; isHotExitEnabled(): boolean { @@ -97,6 +98,10 @@ export class TestBackupMainService implements IBackupMainService { unregisterEmptyWindowBackupSync(backupFolder: string): void { throw new Error('Method not implemented.'); } + + async getDirtyWorkspaces(): Promise<(IWorkspaceIdentifier | URI)[]> { + return []; + } } suite('WorkspacesMainService', () => { @@ -109,11 +114,27 @@ suite('WorkspacesMainService', () => { } } - function createWorkspace(folders: string[], names?: string[]) { + function createUntitledWorkspace(folders: string[], names?: string[]) { return service.createUntitledWorkspace(folders.map((folder, index) => ({ uri: URI.file(folder), name: names ? names[index] : undefined } as IWorkspaceFolderCreationData))); } - function createWorkspaceSync(folders: string[], names?: string[]) { + function createWorkspace(workspaceConfigPath: string, folders: (string | URI)[], names?: string[]): void { + + const ws: IStoredWorkspace = { + folders: [] + }; + for (let i = 0; i < folders.length; i++) { + const f = folders[i]; + const s: IStoredWorkspaceFolder = f instanceof URI ? { uri: f.toString() } : { path: f }; + if (names) { + s.name = names[i]; + } + ws.folders.push(s); + } + fs.writeFileSync(workspaceConfigPath, JSON.stringify(ws)); + } + + function createUntitledWorkspaceSync(folders: string[], names?: string[]) { return service.createUntitledWorkspaceSync(folders.map((folder, index) => ({ uri: URI.file(folder), name: names ? names[index] : undefined } as IWorkspaceFolderCreationData))); } @@ -149,7 +170,7 @@ suite('WorkspacesMainService', () => { } test('createWorkspace (folders)', async () => { - const workspace = await createWorkspace([process.cwd(), os.tmpdir()]); + const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()]); assert.ok(workspace); assert.ok(fs.existsSync(workspace.configPath.fsPath)); assert.ok(service.isUntitledWorkspace(workspace)); @@ -163,7 +184,7 @@ suite('WorkspacesMainService', () => { }); test('createWorkspace (folders with name)', async () => { - const workspace = await createWorkspace([process.cwd(), os.tmpdir()], ['currentworkingdirectory', 'tempdir']); + const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()], ['currentworkingdirectory', 'tempdir']); assert.ok(workspace); assert.ok(fs.existsSync(workspace.configPath.fsPath)); assert.ok(service.isUntitledWorkspace(workspace)); @@ -195,7 +216,7 @@ suite('WorkspacesMainService', () => { }); test('createWorkspaceSync (folders)', () => { - const workspace = createWorkspaceSync([process.cwd(), os.tmpdir()]); + const workspace = createUntitledWorkspaceSync([process.cwd(), os.tmpdir()]); assert.ok(workspace); assert.ok(fs.existsSync(workspace.configPath.fsPath)); assert.ok(service.isUntitledWorkspace(workspace)); @@ -210,7 +231,7 @@ suite('WorkspacesMainService', () => { }); test('createWorkspaceSync (folders with names)', () => { - const workspace = createWorkspaceSync([process.cwd(), os.tmpdir()], ['currentworkingdirectory', 'tempdir']); + const workspace = createUntitledWorkspaceSync([process.cwd(), os.tmpdir()], ['currentworkingdirectory', 'tempdir']); assert.ok(workspace); assert.ok(fs.existsSync(workspace.configPath.fsPath)); assert.ok(service.isUntitledWorkspace(workspace)); @@ -243,7 +264,7 @@ suite('WorkspacesMainService', () => { }); test('resolveWorkspaceSync', async () => { - const workspace = await createWorkspace([process.cwd(), os.tmpdir()]); + const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()]); assert.ok(service.resolveLocalWorkspaceSync(workspace.configPath)); // make it a valid workspace path @@ -262,7 +283,7 @@ suite('WorkspacesMainService', () => { }); test('resolveWorkspaceSync (support relative paths)', async () => { - const workspace = await createWorkspace([process.cwd(), os.tmpdir()]); + const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()]); fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: './ticino-playground/lib' }] })); const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); @@ -270,7 +291,7 @@ suite('WorkspacesMainService', () => { }); test('resolveWorkspaceSync (support relative paths #2)', async () => { - const workspace = await createWorkspace([process.cwd(), os.tmpdir()]); + const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()]); fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: './ticino-playground/lib/../other' }] })); const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); @@ -278,7 +299,7 @@ suite('WorkspacesMainService', () => { }); test('resolveWorkspaceSync (support relative paths #3)', async () => { - const workspace = await createWorkspace([process.cwd(), os.tmpdir()]); + const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()]); fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: 'ticino-playground/lib' }] })); const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); @@ -286,7 +307,7 @@ suite('WorkspacesMainService', () => { }); test('resolveWorkspaceSync (support invalid JSON via fault tolerant parsing)', async () => { - const workspace = await createWorkspace([process.cwd(), os.tmpdir()]); + const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()]); fs.writeFileSync(workspace.configPath.fsPath, '{ "folders": [ { "path": "./ticino-playground/lib" } , ] }'); // trailing comma const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); @@ -296,14 +317,15 @@ suite('WorkspacesMainService', () => { test('rewriteWorkspaceFileForNewLocation', async () => { const folder1 = process.cwd(); // absolute path because outside of tmpDir const tmpDir = os.tmpdir(); - const tmpInsideDir = path.join(os.tmpdir(), 'inside'); + const tmpInsideDir = path.join(tmpDir, 'inside'); - const workspace = await createWorkspace([folder1, tmpInsideDir, path.join(tmpInsideDir, 'somefolder')]); - const origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); + const firstConfigPath = path.join(tmpDir, 'myworkspace0.code-workspace'); + createWorkspace(firstConfigPath, [folder1, 'inside', path.join('inside', 'somefolder')]); + const origContent = fs.readFileSync(firstConfigPath).toString(); - let origConfigPath = workspace.configPath; + let origConfigPath = URI.file(firstConfigPath); let workspaceConfigPath = URI.file(path.join(tmpDir, 'inside', 'myworkspace1.code-workspace')); - let newContent = rewriteWorkspaceFileForNewLocation(origContent, origConfigPath, workspaceConfigPath); + let newContent = rewriteWorkspaceFileForNewLocation(origContent, origConfigPath, false, workspaceConfigPath); let ws = (JSON.parse(newContent) as IStoredWorkspace); assert.equal(ws.folders.length, 3); assertPathEquals((ws.folders[0]).path, folder1); // absolute path because outside of tmpdir @@ -312,7 +334,7 @@ suite('WorkspacesMainService', () => { origConfigPath = workspaceConfigPath; workspaceConfigPath = URI.file(path.join(tmpDir, 'myworkspace2.code-workspace')); - newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, workspaceConfigPath); + newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, false, workspaceConfigPath); ws = (JSON.parse(newContent) as IStoredWorkspace); assert.equal(ws.folders.length, 3); assertPathEquals((ws.folders[0]).path, folder1); @@ -321,51 +343,51 @@ suite('WorkspacesMainService', () => { origConfigPath = workspaceConfigPath; workspaceConfigPath = URI.file(path.join(tmpDir, 'other', 'myworkspace2.code-workspace')); - newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, workspaceConfigPath); + newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, false, workspaceConfigPath); ws = (JSON.parse(newContent) as IStoredWorkspace); assert.equal(ws.folders.length, 3); assertPathEquals((ws.folders[0]).path, folder1); - assertPathEquals((ws.folders[1]).path, tmpInsideDir); - assertPathEquals((ws.folders[2]).path, path.join(tmpInsideDir, 'somefolder')); + assertPathEquals((ws.folders[1]).path, isWindows ? '..\\inside' : '../inside'); + assertPathEquals((ws.folders[2]).path, isWindows ? '..\\inside\\somefolder' : '../inside/somefolder'); origConfigPath = workspaceConfigPath; workspaceConfigPath = URI.parse('foo://foo/bar/myworkspace2.code-workspace'); - newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, workspaceConfigPath); + newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, false, workspaceConfigPath); ws = (JSON.parse(newContent) as IStoredWorkspace); assert.equal(ws.folders.length, 3); assert.equal((ws.folders[0]).uri, URI.file(folder1).toString(true)); assert.equal((ws.folders[1]).uri, URI.file(tmpInsideDir).toString(true)); assert.equal((ws.folders[2]).uri, URI.file(path.join(tmpInsideDir, 'somefolder')).toString(true)); - service.deleteUntitledWorkspaceSync(workspace); + fs.unlinkSync(firstConfigPath); }); test('rewriteWorkspaceFileForNewLocation (preserves comments)', async () => { - const workspace = await createWorkspace([process.cwd(), os.tmpdir(), path.join(os.tmpdir(), 'somefolder')]); + const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir(), path.join(os.tmpdir(), 'somefolder')]); const workspaceConfigPath = URI.file(path.join(os.tmpdir(), `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`)); let origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); origContent = `// this is a comment\n${origContent}`; - let newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, workspaceConfigPath); + let newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, false, workspaceConfigPath); assert.equal(0, newContent.indexOf('// this is a comment')); service.deleteUntitledWorkspaceSync(workspace); }); test('rewriteWorkspaceFileForNewLocation (preserves forward slashes)', async () => { - const workspace = await createWorkspace([process.cwd(), os.tmpdir(), path.join(os.tmpdir(), 'somefolder')]); + const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir(), path.join(os.tmpdir(), 'somefolder')]); const workspaceConfigPath = URI.file(path.join(os.tmpdir(), `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`)); let origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); origContent = origContent.replace(/[\\]/g, '/'); // convert backslash to slash - const newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, workspaceConfigPath); + const newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, false, workspaceConfigPath); const ws = (JSON.parse(newContent) as IStoredWorkspace); assert.ok(ws.folders.every(f => (f).path.indexOf('\\') < 0)); service.deleteUntitledWorkspaceSync(workspace); }); - test('rewriteWorkspaceFileForNewLocation (unc paths)', async () => { + test.skip('rewriteWorkspaceFileForNewLocation (unc paths)', async () => { if (!isWindows) { return Promise.resolve(); } @@ -375,10 +397,10 @@ suite('WorkspacesMainService', () => { const folder2Location = '\\\\server\\share2\\some\\path'; const folder3Location = path.join(os.tmpdir(), 'wsloc', 'inner', 'more'); - const workspace = await createWorkspace([folder1Location, folder2Location, folder3Location]); + const workspace = await createUntitledWorkspace([folder1Location, folder2Location, folder3Location]); const workspaceConfigPath = URI.file(path.join(workspaceLocation, `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`)); let origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); - const newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, workspaceConfigPath); + const newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, false, workspaceConfigPath); const ws = (JSON.parse(newContent) as IStoredWorkspace); assertPathEquals((ws.folders[0]).path, folder1Location); assertPathEquals((ws.folders[1]).path, folder2Location); @@ -388,14 +410,14 @@ suite('WorkspacesMainService', () => { }); test('deleteUntitledWorkspaceSync (untitled)', async () => { - const workspace = await createWorkspace([process.cwd(), os.tmpdir()]); + const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()]); assert.ok(fs.existsSync(workspace.configPath.fsPath)); service.deleteUntitledWorkspaceSync(workspace); assert.ok(!fs.existsSync(workspace.configPath.fsPath)); }); test('deleteUntitledWorkspaceSync (saved)', async () => { - const workspace = await createWorkspace([process.cwd(), os.tmpdir()]); + const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()]); service.deleteUntitledWorkspaceSync(workspace); }); @@ -405,14 +427,14 @@ suite('WorkspacesMainService', () => { let untitled = service.getUntitledWorkspacesSync(); assert.equal(untitled.length, 0); - const untitledOne = await createWorkspace([process.cwd(), os.tmpdir()]); + const untitledOne = await createUntitledWorkspace([process.cwd(), os.tmpdir()]); assert.ok(fs.existsSync(untitledOne.configPath.fsPath)); untitled = service.getUntitledWorkspacesSync(); assert.equal(1, untitled.length); assert.equal(untitledOne.id, untitled[0].workspace.id); - const untitledTwo = await createWorkspace([os.tmpdir(), process.cwd()]); + const untitledTwo = await createUntitledWorkspace([os.tmpdir(), process.cwd()]); assert.ok(fs.existsSync(untitledTwo.configPath.fsPath)); assert.ok(fs.existsSync(untitledOne.configPath.fsPath), `Unexpected workspaces count of 1 (expected 2): ${untitledOne.configPath.fsPath} does not exist anymore?`); const untitledHome = dirname(dirname(untitledTwo.configPath)); diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 9e852eca701..150e6f4d369 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -2116,6 +2116,9 @@ declare module 'vscode' { /** * A [command](#Command) this code action executes. + * + * If this command throws an exception, VS Code displays the exception message to users in the editor at the + * current cursor position. */ command?: Command; @@ -2145,8 +2148,8 @@ declare module 'vscode' { * of code action, such as refactorings. * * - If the user has a [keybinding](https://code.visualstudio.com/docs/editor/refactoring#_keybindings-for-code-actions) - * that auto applies a code action and only a disabled code actions are returned, VS Code will show the user a - * message with `reason` in the editor. + * that auto applies a code action and only a disabled code actions are returned, VS Code will show the user an + * error message with `reason` in the editor. */ disabled?: { /** @@ -3147,6 +3150,233 @@ declare module 'vscode' { prepareRename?(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; } + /** + * A semantic tokens legend contains the needed information to decipher + * the integer encoded representation of semantic tokens. + */ + export class SemanticTokensLegend { + /** + * The possible token types. + */ + public readonly tokenTypes: string[]; + /** + * The possible token modifiers. + */ + public readonly tokenModifiers: string[]; + + constructor(tokenTypes: string[], tokenModifiers?: string[]); + } + + /** + * A semantic tokens builder can help with creating a `SemanticTokens` instance + * which contains delta encoded semantic tokens. + */ + export class SemanticTokensBuilder { + + constructor(legend?: SemanticTokensLegend); + + /** + * Add another token. + * + * @param line The token start line number (absolute value). + * @param char The token start character (absolute value). + * @param length The token length in characters. + * @param tokenType The encoded token type. + * @param tokenModifiers The encoded token modifiers. + */ + push(line: number, char: number, length: number, tokenType: number, tokenModifiers?: number): void; + + /** + * Add another token. Use only when providing a legend. + * + * @param range The range of the token. Must be single-line. + * @param tokenType The token type. + * @param tokenModifiers The token modifiers. + */ + push(range: Range, tokenType: string, tokenModifiers?: string[]): void; + + /** + * Finish and create a `SemanticTokens` instance. + */ + build(resultId?: string): SemanticTokens; + } + + /** + * Represents semantic tokens, either in a range or in an entire document. + * @see [provideDocumentSemanticTokens](#DocumentSemanticTokensProvider.provideDocumentSemanticTokens) for an explanation of the format. + * @see [SemanticTokensBuilder](#SemanticTokensBuilder) for a helper to create an instance. + */ + export class SemanticTokens { + /** + * The result id of the tokens. + * + * This is the id that will be passed to `DocumentSemanticTokensProvider.provideDocumentSemanticTokensEdits` (if implemented). + */ + readonly resultId?: string; + /** + * The actual tokens data. + * @see [provideDocumentSemanticTokens](#DocumentSemanticTokensProvider.provideDocumentSemanticTokens) for an explanation of the format. + */ + readonly data: Uint32Array; + + constructor(data: Uint32Array, resultId?: string); + } + + /** + * Represents edits to semantic tokens. + * @see [provideDocumentSemanticTokensEdits](#DocumentSemanticTokensProvider.provideDocumentSemanticTokensEdits) for an explanation of the format. + */ + export class SemanticTokensEdits { + /** + * The result id of the tokens. + * + * This is the id that will be passed to `DocumentSemanticTokensProvider.provideDocumentSemanticTokensEdits` (if implemented). + */ + readonly resultId?: string; + /** + * The edits to the tokens data. + * All edits refer to the initial data state. + */ + readonly edits: SemanticTokensEdit[]; + + constructor(edits: SemanticTokensEdit[], resultId?: string); + } + + /** + * Represents an edit to semantic tokens. + * @see [provideDocumentSemanticTokensEdits](#DocumentSemanticTokensProvider.provideDocumentSemanticTokensEdits) for an explanation of the format. + */ + export class SemanticTokensEdit { + /** + * The start offset of the edit. + */ + readonly start: number; + /** + * The count of elements to remove. + */ + readonly deleteCount: number; + /** + * The elements to insert. + */ + readonly data?: Uint32Array; + + constructor(start: number, deleteCount: number, data?: Uint32Array); + } + + /** + * The document semantic tokens provider interface defines the contract between extensions and + * semantic tokens. + */ + export interface DocumentSemanticTokensProvider { + /** + * An optional event to signal that the semantic tokens from this provider have changed. + */ + onDidChangeSemanticTokens?: Event; + + /** + * Tokens in a file are represented as an array of integers. The position of each token is expressed relative to + * the token before it, because most tokens remain stable relative to each other when edits are made in a file. + * + * --- + * In short, each token takes 5 integers to represent, so a specific token `i` in the file consists of the following array indices: + * - at index `5*i` - `deltaLine`: token line number, relative to the previous token + * - at index `5*i+1` - `deltaStart`: token start character, relative to the previous token (relative to 0 or the previous token's start if they are on the same line) + * - at index `5*i+2` - `length`: the length of the token. A token cannot be multiline. + * - at index `5*i+3` - `tokenType`: will be looked up in `SemanticTokensLegend.tokenTypes`. We currently ask that `tokenType` < 65536. + * - at index `5*i+4` - `tokenModifiers`: each set bit will be looked up in `SemanticTokensLegend.tokenModifiers` + * + * --- + * ### How to encode tokens + * + * Here is an example for encoding a file with 3 tokens in a uint32 array: + * ``` + * { line: 2, startChar: 5, length: 3, tokenType: "property", tokenModifiers: ["private", "static"] }, + * { line: 2, startChar: 10, length: 4, tokenType: "type", tokenModifiers: [] }, + * { line: 5, startChar: 2, length: 7, tokenType: "class", tokenModifiers: [] } + * ``` + * + * 1. First of all, a legend must be devised. This legend must be provided up-front and capture all possible token types. + * For this example, we will choose the following legend which must be passed in when registering the provider: + * ``` + * tokenTypes: ['property', 'type', 'class'], + * tokenModifiers: ['private', 'static'] + * ``` + * + * 2. The first transformation step is to encode `tokenType` and `tokenModifiers` as integers using the legend. Token types are looked + * up by index, so a `tokenType` value of `1` means `tokenTypes[1]`. Multiple token modifiers can be set by using bit flags, + * so a `tokenModifier` value of `3` is first viewed as binary `0b00000011`, which means `[tokenModifiers[0], tokenModifiers[1]]` because + * bits 0 and 1 are set. Using this legend, the tokens now are: + * ``` + * { line: 2, startChar: 5, length: 3, tokenType: 0, tokenModifiers: 3 }, + * { line: 2, startChar: 10, length: 4, tokenType: 1, tokenModifiers: 0 }, + * { line: 5, startChar: 2, length: 7, tokenType: 2, tokenModifiers: 0 } + * ``` + * + * 3. The next step is to represent each token relative to the previous token in the file. In this case, the second token + * is on the same line as the first token, so the `startChar` of the second token is made relative to the `startChar` + * of the first token, so it will be `10 - 5`. The third token is on a different line than the second token, so the + * `startChar` of the third token will not be altered: + * ``` + * { deltaLine: 2, deltaStartChar: 5, length: 3, tokenType: 0, tokenModifiers: 3 }, + * { deltaLine: 0, deltaStartChar: 5, length: 4, tokenType: 1, tokenModifiers: 0 }, + * { deltaLine: 3, deltaStartChar: 2, length: 7, tokenType: 2, tokenModifiers: 0 } + * ``` + * + * 4. Finally, the last step is to inline each of the 5 fields for a token in a single array, which is a memory friendly representation: + * ``` + * // 1st token, 2nd token, 3rd token + * [ 2,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0 ] + * ``` + * + * @see [SemanticTokensBuilder](#SemanticTokensBuilder) for a helper to encode tokens as integers. + * *NOTE*: When doing edits, it is possible that multiple edits occur until VS Code decides to invoke the semantic tokens provider. + * *NOTE*: If the provider cannot temporarily compute semantic tokens, it can indicate this by throwing an error with the message 'Busy'. + */ + provideDocumentSemanticTokens(document: TextDocument, token: CancellationToken): ProviderResult; + + /** + * Instead of always returning all the tokens in a file, it is possible for a `DocumentSemanticTokensProvider` to implement + * this method (`provideDocumentSemanticTokensEdits`) and then return incremental updates to the previously provided semantic tokens. + * + * --- + * ### How tokens change when the document changes + * + * Suppose that `provideDocumentSemanticTokens` has previously returned the following semantic tokens: + * ``` + * // 1st token, 2nd token, 3rd token + * [ 2,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0 ] + * ``` + * + * Also suppose that after some edits, the new semantic tokens in a file are: + * ``` + * // 1st token, 2nd token, 3rd token + * [ 3,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0 ] + * ``` + * It is possible to express these new tokens in terms of an edit applied to the previous tokens: + * ``` + * [ 2,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0 ] // old tokens + * [ 3,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0 ] // new tokens + * + * edit: { start: 0, deleteCount: 1, data: [3] } // replace integer at offset 0 with 3 + * ``` + * + * *NOTE*: If the provider cannot compute `SemanticTokensEdits`, it can "give up" and return all the tokens in the document again. + * *NOTE*: All edits in `SemanticTokensEdits` contain indices in the old integers array, so they all refer to the previous result state. + */ + provideDocumentSemanticTokensEdits?(document: TextDocument, previousResultId: string, token: CancellationToken): ProviderResult; + } + + /** + * The document range semantic tokens provider interface defines the contract between extensions and + * semantic tokens. + */ + export interface DocumentRangeSemanticTokensProvider { + /** + * @see [provideDocumentSemanticTokens](#DocumentSemanticTokensProvider.provideDocumentSemanticTokens). + */ + provideDocumentRangeSemanticTokens(document: TextDocument, range: Range, token: CancellationToken): ProviderResult; + } + /** * Value-object describing what options formatting should use. */ @@ -4975,10 +5205,14 @@ declare module 'vscode' { color: string | ThemeColor | undefined; /** - * The identifier of a command to run on click. The command must be - * [known](#commands.getCommands). + * [`Command`](#Command) or identifier of a command to run on click. + * + * The command must be [known](#commands.getCommands). + * + * Note that if this is a [`Command`](#Command) object, only the [`command`](#Command.command) and [`arguments`](#Command.arguments) + * are used by VS Code. */ - command: string | undefined; + command: string | Command | undefined; /** * Shows the entry in the status bar. @@ -6016,6 +6250,14 @@ declare module 'vscode' { * @param messageOrUri Message or uri. */ constructor(messageOrUri?: string | Uri); + + /** + * A code that identifies this error. + * + * Possible values are names of errors, like [`FileNotFound`](#FileSystemError.FileNotFound), + * or `Unknown` for unspecified errors. + */ + readonly code: string; } /** @@ -6324,7 +6566,7 @@ declare module 'vscode' { } /** - * A webview displays html content, like an iframe. + * Displays html content, similarly to an iframe. */ export interface Webview { /** @@ -6333,9 +6575,29 @@ declare module 'vscode' { options: WebviewOptions; /** - * Contents of the webview. + * HTML contents of the webview. * - * Should be a complete html document. + * This should be a complete, valid html document. Changing this property causes the webview to be reloaded. + * + * Webviews are sandboxed from normal extension process, so all communication with the webview must use + * message passing. To send a message from the extension to the webview, use [`postMessage`](#Webview.postMessage). + * To send message from the webview back to an extension, use the `acquireVsCodeApi` function inside the webview + * to get a handle to VS Code's api and then call `.postMessage()`: + * + * ```html + * + * ``` + * + * To load a resources from the workspace inside a webview, use the `[asWebviewUri](#Webview.asWebviewUri)` method + * and ensure the resource's directory is listed in [`WebviewOptions.localResourceRoots`](#WebviewOptions.localResourceRoots). + * + * Keep in mind that even though webviews are sandboxed, they still allow running scripts and loading arbitrary content, + * so extensions must follow all standard web security best practices when working with webviews. This includes + * properly sanitizing all untrusted input (including content from the workspace) and + * setting a [content security policy](https://aka.ms/vscode-api-webview-csp). */ html: string; @@ -6433,7 +6695,7 @@ declare module 'vscode' { iconPath?: Uri | { light: Uri; dark: Uri }; /** - * Webview belonging to the panel. + * [`Webview`](#Webview) belonging to the panel. */ readonly webview: Webview; @@ -6550,6 +6812,34 @@ declare module 'vscode' { deserializeWebviewPanel(webviewPanel: WebviewPanel, state: any): Thenable; } + /** + * Provider for text based custom editors. + * + * Text based custom editors use a [`TextDocument`](#TextDocument) as their data model. This considerably simplifies + * implementing a custom editor as it allows VS Code to handle many common operations such as + * undo and backup. The provider is responsible for synchronizing text changes between the webview and the `TextDocument`. + */ + export interface CustomTextEditorProvider { + + /** + * Resolve a custom editor for a given text resource. + * + * This is called when a user first opens a resource for a `CustomTextEditorProvider`, or if they reopen an + * existing editor using this `CustomTextEditorProvider`. + * + * To resolve a custom editor, the provider must fill in its initial html content and hook up all + * the event listeners it is interested it. The provider can also hold onto the `WebviewPanel` to use later, + * for example in a command. See [`WebviewPanel`](#WebviewPanel) for additional details. + * + * @param document Document for the resource to resolve. + * @param webviewPanel Webview to resolve. + * @param token A cancellation token that indicates the result is no longer needed. + * + * @return Thenable indicating that the custom editor has been resolved. + */ + resolveCustomTextEditor(document: TextDocument, webviewPanel: WebviewPanel, token: CancellationToken): Thenable | void; + } + /** * The clipboard provides read and write access to the system's clipboard. */ @@ -7386,6 +7676,21 @@ declare module 'vscode' { * @param serializer Webview serializer. */ export function registerWebviewPanelSerializer(viewType: string, serializer: WebviewPanelSerializer): Disposable; + + /** + * Register a provider for custom editors for the `viewType` contributed by the `customEditors` extension point. + * + * When a custom editor is opened, VS Code fires an `onCustomEditor:viewType` activation event. Your extension + * must register a [`CustomTextEditorProvider`](#CustomTextEditorProvider) for `viewType` as part of activation. + * + * @param viewType Unique identifier for the custom editor provider. This should match the `viewType` from the + * `customEditors` contribution point. + * @param provider Provider that resolves custom editors. + * @param options Options for the provider. + * + * @return Disposable that unregisters the provider. + */ + export function registerCustomEditorProvider(viewType: string, provider: CustomTextEditorProvider, options?: { readonly webviewOptions?: WebviewPanelOptions; }): Disposable; } /** @@ -7564,7 +7869,7 @@ declare module 'vscode' { /** * The icon path or [ThemeIcon](#ThemeIcon) for the tree item. * When `falsy`, [Folder Theme Icon](#ThemeIcon.Folder) is assigned, if item is collapsible otherwise [File Theme Icon](#ThemeIcon.File). - * When a [ThemeIcon](#ThemeIcon) is specified, icon is derived from the current file icon theme for the specified theme icon using [resourceUri](#TreeItem.resourceUri) (if provided). + * When a file or folder [ThemeIcon](#ThemeIcon) is specified, icon is derived from the current file icon theme for the specified theme icon using [resourceUri](#TreeItem.resourceUri) (if provided). */ iconPath?: string | Uri | { light: string | Uri; dark: string | Uri } | ThemeIcon; @@ -7578,7 +7883,7 @@ declare module 'vscode' { * The [uri](#Uri) of the resource representing this item. * * Will be used to derive the [label](#TreeItem.label), when it is not provided. - * Will be used to derive the icon from current icon theme, when [iconPath](#TreeItem.iconPath) has [ThemeIcon](#ThemeIcon) value. + * Will be used to derive the icon from current file icon theme, when [iconPath](#TreeItem.iconPath) has [ThemeIcon](#ThemeIcon) value. */ resourceUri?: Uri; @@ -7908,7 +8213,7 @@ declare module 'vscode' { /** * The location at which progress should show. */ - location: ProgressLocation; + location: ProgressLocation | { viewId: string }; /** * A human-readable string which will be used to describe the @@ -9259,6 +9564,38 @@ declare module 'vscode' { */ export function registerRenameProvider(selector: DocumentSelector, provider: RenameProvider): Disposable; + /** + * Register a semantic tokens provider for a whole document. + * + * Multiple providers can be registered for a language. In that case providers are sorted + * by their [score](#languages.match) and the best-matching provider is used. Failure + * of the selected provider will cause a failure of the whole operation. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider A document semantic tokens provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerDocumentSemanticTokensProvider(selector: DocumentSelector, provider: DocumentSemanticTokensProvider, legend: SemanticTokensLegend): Disposable; + + /** + * Register a semantic tokens provider for a document range. + * + * *Note:* If a document has both a `DocumentSemanticTokensProvider` and a `DocumentRangeSemanticTokensProvider`, + * the range provider will be invoked only initially, for the time in which the full document provider takes + * to resolve the first request. Once the full document provider resolves the first request, the semantic tokens + * provided via the range provider will be discarded and from that point forward, only the document provider + * will be used. + * + * Multiple providers can be registered for a language. In that case providers are sorted + * by their [score](#languages.match) and the best-matching provider is used. Failure + * of the selected provider will cause a failure of the whole operation. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider A document range semantic tokens provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerDocumentRangeSemanticTokensProvider(selector: DocumentSelector, provider: DocumentRangeSemanticTokensProvider, legend: SemanticTokensLegend): Disposable; + /** * Register a formatting provider for a document. * diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index d1ccd81b83f..12dce38ecc6 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -35,25 +35,50 @@ declare module 'vscode' { readonly added: string[]; /** - * The ids of the [authenticationProvider](#AuthenticationProvider)s that have been removed.. + * The ids of the [authenticationProvider](#AuthenticationProvider)s that have been removed. */ readonly removed: string[]; } + /** + * An [event](#Event) which fires when an [AuthenticationSession](#AuthenticationSession) is added, removed, or changed. + */ + export interface AuthenticationSessionsChangeEvent { + /** + * The ids of the [AuthenticationSession](#AuthenticationSession)s that have been added. + */ + readonly added: string[]; + + /** + * The ids of the [AuthenticationSession](#AuthenticationSession)s that have been removed. + */ + readonly removed: string[]; + + /** + * The ids of the [AuthenticationSession](#AuthenticationSession)s that have been changed. + */ + readonly changed: string[]; + } + + /** + * **WARNING** When writing an AuthenticationProvider, `id` should be treated as part of your extension's + * API, changing it is a breaking change for all extensions relying on the provider. The id is + * treated case-sensitively. + */ export interface AuthenticationProvider { /** * Used as an identifier for extensions trying to work with a particular - * provider: 'Microsoft', 'GitHub', etc. id must be unique, registering + * provider: 'microsoft', 'github', etc. id must be unique, registering * another provider with the same id will fail. */ readonly id: string; readonly displayName: string; /** - * A [enent](#Event) which fires when the array of sessions has changed, or data + * An [event](#Event) which fires when the array of sessions has changed, or data * within a session has changed. */ - readonly onDidChangeSessions: Event; + readonly onDidChangeSessions: Event; /** * Returns an array of current sessions. @@ -75,7 +100,31 @@ declare module 'vscode' { */ export const onDidChangeAuthenticationProviders: Event; - export const providers: ReadonlyArray; + /** + * An array of the ids of authentication providers that are currently registered. + */ + export const providerIds: string[]; + + /** + * Get existing authentication sessions. Rejects if a provider with providerId is not + * registered, or if the user does not consent to sharing authentication information with + * the extension. + */ + export function getSessions(providerId: string, scopes: string[]): Thenable; + + /** + * Prompt a user to login to create a new authenticaiton session. Rejects if a provider with + * providerId is not registered, or if the user does not consent to sharing authentication + * information with the extension. + */ + export function login(providerId: string, scopes: string[]): Thenable; + + /** + * An [event](#Event) which fires when the array of sessions has changed, or data + * within a session has changed for a provider. Fires with the ids of the providers + * that have had session data change. + */ + export const onDidChangeSessions: Event<{ [providerId: string]: AuthenticationSessionsChangeEvent }>; } //#endregion @@ -107,7 +156,7 @@ declare module 'vscode' { export interface TunnelDescription { remoteAddress: { port: number, host: string }; //The complete local address(ex. localhost:1234) - localAddress: string; + localAddress: { port: number, host: string } | string; } export interface Tunnel extends TunnelDescription { @@ -168,13 +217,6 @@ declare module 'vscode' { */ export let tunnels: Thenable; - /** - * Fired when the list of tunnels has changed. - * @deprecated use onDidChangeTunnels instead - */ - // TODO@alexr - // eslint-disable-next-line vscode-dts-event-naming - export const onDidTunnelsChange: Event; /** * Fired when the list of tunnels has changed. */ @@ -205,215 +247,6 @@ declare module 'vscode' { //#endregion - //#region Semantic tokens: https://github.com/microsoft/vscode/issues/86415 - - export class SemanticTokensLegend { - public readonly tokenTypes: string[]; - public readonly tokenModifiers: string[]; - - constructor(tokenTypes: string[], tokenModifiers: string[]); - } - - export class SemanticTokensBuilder { - constructor(); - push(line: number, char: number, length: number, tokenType: number, tokenModifiers: number): void; - build(): Uint32Array; - } - - export class SemanticTokens { - /** - * The result id of the tokens. - * - * This is the id that will be passed to `DocumentSemanticTokensProvider.provideDocumentSemanticTokensEdits` (if implemented). - */ - readonly resultId?: string; - readonly data: Uint32Array; - - constructor(data: Uint32Array, resultId?: string); - } - - export class SemanticTokensEdits { - /** - * The result id of the tokens. - * - * This is the id that will be passed to `DocumentSemanticTokensProvider.provideDocumentSemanticTokensEdits` (if implemented). - */ - readonly resultId?: string; - readonly edits: SemanticTokensEdit[]; - - constructor(edits: SemanticTokensEdit[], resultId?: string); - } - - export class SemanticTokensEdit { - readonly start: number; - readonly deleteCount: number; - readonly data?: Uint32Array; - - constructor(start: number, deleteCount: number, data?: Uint32Array); - } - - /** - * The document semantic tokens provider interface defines the contract between extensions and - * semantic tokens. - */ - export interface DocumentSemanticTokensProvider { - /** - * A file can contain many tokens, perhaps even hundreds of thousands of tokens. Therefore, to improve - * the memory consumption around describing semantic tokens, we have decided to avoid allocating an object - * for each token and we represent tokens from a file as an array of integers. Furthermore, the position - * of each token is expressed relative to the token before it because most tokens remain stable relative to - * each other when edits are made in a file. - * - * --- - * In short, each token takes 5 integers to represent, so a specific token `i` in the file consists of the following array indices: - * - at index `5*i` - `deltaLine`: token line number, relative to the previous token - * - at index `5*i+1` - `deltaStart`: token start character, relative to the previous token (relative to 0 or the previous token's start if they are on the same line) - * - at index `5*i+2` - `length`: the length of the token. A token cannot be multiline. - * - at index `5*i+3` - `tokenType`: will be looked up in `SemanticTokensLegend.tokenTypes`. We currently ask that `tokenType` < 65536. - * - at index `5*i+4` - `tokenModifiers`: each set bit will be looked up in `SemanticTokensLegend.tokenModifiers` - * - * --- - * ### How to encode tokens - * - * Here is an example for encoding a file with 3 tokens in a uint32 array: - * ``` - * { line: 2, startChar: 5, length: 3, tokenType: "property", tokenModifiers: ["private", "static"] }, - * { line: 2, startChar: 10, length: 4, tokenType: "type", tokenModifiers: [] }, - * { line: 5, startChar: 2, length: 7, tokenType: "class", tokenModifiers: [] } - * ``` - * - * 1. First of all, a legend must be devised. This legend must be provided up-front and capture all possible token types. - * For this example, we will choose the following legend which must be passed in when registering the provider: - * ``` - * tokenTypes: ['property', 'type', 'class'], - * tokenModifiers: ['private', 'static'] - * ``` - * - * 2. The first transformation step is to encode `tokenType` and `tokenModifiers` as integers using the legend. Token types are looked - * up by index, so a `tokenType` value of `1` means `tokenTypes[1]`. Multiple token modifiers can be set by using bit flags, - * so a `tokenModifier` value of `3` is first viewed as binary `0b00000011`, which means `[tokenModifiers[0], tokenModifiers[1]]` because - * bits 0 and 1 are set. Using this legend, the tokens now are: - * ``` - * { line: 2, startChar: 5, length: 3, tokenType: 0, tokenModifiers: 3 }, - * { line: 2, startChar: 10, length: 4, tokenType: 1, tokenModifiers: 0 }, - * { line: 5, startChar: 2, length: 7, tokenType: 2, tokenModifiers: 0 } - * ``` - * - * 3. The next step is to represent each token relative to the previous token in the file. In this case, the second token - * is on the same line as the first token, so the `startChar` of the second token is made relative to the `startChar` - * of the first token, so it will be `10 - 5`. The third token is on a different line than the second token, so the - * `startChar` of the third token will not be altered: - * ``` - * { deltaLine: 2, deltaStartChar: 5, length: 3, tokenType: 0, tokenModifiers: 3 }, - * { deltaLine: 0, deltaStartChar: 5, length: 4, tokenType: 1, tokenModifiers: 0 }, - * { deltaLine: 3, deltaStartChar: 2, length: 7, tokenType: 2, tokenModifiers: 0 } - * ``` - * - * 4. Finally, the last step is to inline each of the 5 fields for a token in a single array, which is a memory friendly representation: - * ``` - * // 1st token, 2nd token, 3rd token - * [ 2,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0 ] - * ``` - */ - provideDocumentSemanticTokens(document: TextDocument, token: CancellationToken): ProviderResult; - - /** - * Instead of always returning all the tokens in a file, it is possible for a `DocumentSemanticTokensProvider` to implement - * this method (`updateSemanticTokens`) and then return incremental updates to the previously provided semantic tokens. - * - * --- - * ### How tokens change when the document changes - * - * Let's look at how tokens might change. - * - * Continuing with the above example, suppose a new line was inserted at the top of the file. - * That would make all the tokens move down by one line (notice how the line has changed for each one): - * ``` - * { line: 3, startChar: 5, length: 3, tokenType: "property", tokenModifiers: ["private", "static"] }, - * { line: 3, startChar: 10, length: 4, tokenType: "type", tokenModifiers: [] }, - * { line: 6, startChar: 2, length: 7, tokenType: "class", tokenModifiers: [] } - * ``` - * The integer encoding of the tokens does not change substantially because of the delta-encoding of positions: - * ``` - * // 1st token, 2nd token, 3rd token - * [ 3,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0 ] - * ``` - * It is possible to express these new tokens in terms of an edit applied to the previous tokens: - * ``` - * [ 2,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0 ] // old tokens - * [ 3,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0 ] // new tokens - * - * edit: { start: 0, deleteCount: 1, data: [3] } // replace integer at offset 0 with 3 - * ``` - * - * Furthermore, let's assume that a new token has appeared on line 4: - * ``` - * { line: 3, startChar: 5, length: 3, tokenType: "property", tokenModifiers: ["private", "static"] }, - * { line: 3, startChar: 10, length: 4, tokenType: "type", tokenModifiers: [] }, - * { line: 4, startChar: 3, length: 5, tokenType: "property", tokenModifiers: ["static"] }, - * { line: 6, startChar: 2, length: 7, tokenType: "class", tokenModifiers: [] } - * ``` - * The integer encoding of the tokens is: - * ``` - * // 1st token, 2nd token, 3rd token, 4th token - * [ 3,5,3,0,3, 0,5,4,1,0, 1,3,5,0,2, 2,2,7,2,0, ] - * ``` - * Again, it is possible to express these new tokens in terms of an edit applied to the previous tokens: - * ``` - * [ 3,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0 ] // old tokens - * [ 3,5,3,0,3, 0,5,4,1,0, 1,3,5,0,2, 2,2,7,2,0, ] // new tokens - * - * edit: { start: 10, deleteCount: 1, data: [1,3,5,0,2,2] } // replace integer at offset 10 with [1,3,5,0,2,2] - * ``` - * - * *NOTE*: When doing edits, it is possible that multiple edits occur until VS Code decides to invoke the semantic tokens provider. - * *NOTE*: If the provider cannot compute `SemanticTokensEdits`, it can "give up" and return all the tokens in the document again. - * *NOTE*: All edits in `SemanticTokensEdits` contain indices in the old integers array, so they all refer to the previous result state. - */ - provideDocumentSemanticTokensEdits?(document: TextDocument, previousResultId: string, token: CancellationToken): ProviderResult; - } - - /** - * The document range semantic tokens provider interface defines the contract between extensions and - * semantic tokens. - */ - export interface DocumentRangeSemanticTokensProvider { - /** - * See [provideDocumentSemanticTokens](#DocumentSemanticTokensProvider.provideDocumentSemanticTokens). - */ - provideDocumentRangeSemanticTokens(document: TextDocument, range: Range, token: CancellationToken): ProviderResult; - } - - export namespace languages { - /** - * Register a semantic tokens provider for a whole document. - * - * Multiple providers can be registered for a language. In that case providers are sorted - * by their [score](#languages.match) and the best-matching provider is used. Failure - * of the selected provider will cause a failure of the whole operation. - * - * @param selector A selector that defines the documents this provider is applicable to. - * @param provider A document semantic tokens provider. - * @return A [disposable](#Disposable) that unregisters this provider when being disposed. - */ - export function registerDocumentSemanticTokensProvider(selector: DocumentSelector, provider: DocumentSemanticTokensProvider, legend: SemanticTokensLegend): Disposable; - - /** - * Register a semantic tokens provider for a document range. - * - * Multiple providers can be registered for a language. In that case providers are sorted - * by their [score](#languages.match) and the best-matching provider is used. Failure - * of the selected provider will cause a failure of the whole operation. - * - * @param selector A selector that defines the documents this provider is applicable to. - * @param provider A document range semantic tokens provider. - * @return A [disposable](#Disposable) that unregisters this provider when being disposed. - */ - export function registerDocumentRangeSemanticTokensProvider(selector: DocumentSelector, provider: DocumentRangeSemanticTokensProvider, legend: SemanticTokensLegend): Disposable; - } - - //#endregion - //#region editor insets: https://github.com/microsoft/vscode/issues/85682 export interface WebviewEditorInset { @@ -749,10 +582,15 @@ declare module 'vscode' { /** * A [glob pattern](#GlobPattern) that defines files and folders to exclude. The glob pattern - * will be matched against the file paths of resulting matches relative to their workspace. When `undefined` only default excludes will - * apply, when `null` no excludes will apply. + * will be matched against the file paths of resulting matches relative to their workspace. When `undefined`, default excludes will + * apply. */ - exclude?: GlobPattern | null; + exclude?: GlobPattern; + + /** + * Whether to use the default and user-configured excludes. Defaults to true. + */ + useDefaultExcludes?: boolean; /** * The maximum number of results to search for @@ -891,7 +729,7 @@ declare module 'vscode' { //#region LogLevel: https://github.com/microsoft/vscode/issues/85992 /** - * The severity level of a log message + * @deprecated DO NOT USE, will be removed */ export enum LogLevel { Trace = 1, @@ -905,12 +743,12 @@ declare module 'vscode' { export namespace env { /** - * Current logging level. + * @deprecated DO NOT USE, will be removed */ export const logLevel: LogLevel; /** - * An [event](#Event) that fires when the log level has changed. + * @deprecated DO NOT USE, will be removed */ export const onDidChangeLogLevel: Event; } @@ -1039,7 +877,7 @@ declare module 'vscode' { readonly dimensions: TerminalDimensions; } - namespace window { + export namespace window { /** * An event which fires when the [dimensions](#Terminal.dimensions) of the terminal change. */ @@ -1057,6 +895,128 @@ declare module 'vscode' { //#endregion + //#region Terminal link handlers https://github.com/microsoft/vscode/issues/91606 + + export namespace window { + /** + * Register a [TerminalLinkHandler](#TerminalLinkHandler) that can be used to intercept and + * handle links that are activated within terminals. + */ + export function registerTerminalLinkHandler(handler: TerminalLinkHandler): Disposable; + } + + export interface TerminalLinkHandler { + /** + * Handles a link that is activated within the terminal. + * + * @return Whether the link was handled, if the link was handled this link will not be + * considered by any other extension or by the default built-in link handler. + */ + handleLink(terminal: Terminal, link: string): ProviderResult; + } + + //#endregion + + //#region Contribute to terminal environment https://github.com/microsoft/vscode/issues/46696 + + export enum EnvironmentVariableMutatorType { + /** + * Replace the variable's existing value. + */ + Replace = 1, + /** + * Append to the end of the variable's existing value. + */ + Append = 2, + /** + * Prepend to the start of the variable's existing value. + */ + Prepend = 3 + } + + export interface EnvironmentVariableMutator { + /** + * The type of mutation that will occur to the variable. + */ + readonly type: EnvironmentVariableMutatorType; + + /** + * The value to use for the variable. + */ + readonly value: string; + } + + /** + * A collection of mutations that an extension can apply to a process environment. + */ + export interface EnvironmentVariableCollection { + /** + * Replace an environment variable with a value. + * + * Note that an extension can only make a single change to any one variable, so this will + * overwrite any previous calls to replace, append or prepend. + */ + replace(variable: string, value: string): void; + + /** + * Append a value to an environment variable. + * + * Note that an extension can only make a single change to any one variable, so this will + * overwrite any previous calls to replace, append or prepend. + */ + append(variable: string, value: string): void; + + /** + * Prepend a value to an environment variable. + * + * Note that an extension can only make a single change to any one variable, so this will + * overwrite any previous calls to replace, append or prepend. + */ + prepend(variable: string, value: string): void; + + /** + * Gets the mutator that this collection applies to a variable, if any. + */ + get(variable: string): EnvironmentVariableMutator | undefined; + + /** + * Iterate over each mutator in this collection. + */ + forEach(callback: (variable: string, mutator: EnvironmentVariableMutator, collection: EnvironmentVariableCollection) => any, thisArg?: any): void; + + /** + * Deletes this collection's mutator for a variable. + */ + delete(variable: string): void; + + /** + * Clears all mutators from this collection. + */ + clear(): void; + + /** + * Disposes the collection, if the collection was persisted it will no longer be retained + * across reloads. + */ + dispose(): void; + } + + export namespace window { + /** + * Creates or returns the extension's environment variable collection for this workspace, + * enabling changes to be applied to terminal environment variables. + * + * @param persistent Whether the collection should be cached for the workspace and applied + * to the terminal across window reloads. When true the collection will be active + * immediately such when the window reloads. Additionally, this API will return the cached + * version if it exists. The collection will be invalidated when the extension is + * uninstalled or when the collection is disposed. Defaults to false. + */ + export function getEnvironmentVariableCollection(persistent?: boolean): EnvironmentVariableCollection; + } + + //#endregion + //#region Joh -> exclusive document filters export interface DocumentFilter { @@ -1185,75 +1145,160 @@ declare module 'vscode' { //#endregion - //#region Custom editors: https://github.com/microsoft/vscode/issues/77131 - - // TODO: - // - Think about where a rename would live. - // - Think about handling go to line? (add other editor options? reveal?) - // - Should we expose edits? - // - More properties from `TextDocument`? + //#region OnTypeRename: https://github.com/microsoft/vscode/issues/88424 /** - * Defines the capabilities of a custom webview editor. + * The rename provider interface defines the contract between extensions and + * the live-rename feature. */ - interface CustomEditorCapabilities { + export interface OnTypeRenameProvider { /** - * Defines the editing capability of a custom webview document. + * Provide a list of ranges that can be live renamed together. * - * When not provided, the document is considered readonly. + * @param document The document in which the command was invoked. + * @param position The position at which the command was invoked. + * @param token A cancellation token. + * @return A list of ranges that can be live-renamed togehter. The ranges must have + * identical length and contain identical text content. The ranges cannot overlap. */ - readonly editing?: CustomEditorEditingCapability; + provideOnTypeRenameRanges(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; } + namespace languages { + /** + * Register a rename provider that works on type. + * + * Multiple providers can be registered for a language. In that case providers are sorted + * by their [score](#languages.match) and the best-matching provider is used. Failure + * of the selected provider will cause a failure of the whole operation. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider An on type rename provider. + * @param stopPattern Stop on type renaming when input text matches the regular expression. Defaults to `^\s`. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerOnTypeRenameProvider(selector: DocumentSelector, provider: OnTypeRenameProvider, stopPattern?: RegExp): Disposable; + } + + //#endregion + + //#region Custom editor https://github.com/microsoft/vscode/issues/77131 + /** - * Defines the editing capability of a custom webview editor. This allows the webview editor to hook into standard - * editor events such as `undo` or `save`. + * Implements the editing functionality of a custom editor. * - * @param EditType Type of edits. + * This delegate is how custom editors hook into standard VS Code operations such as save and undo. The delegate + * is also how custom editors notify VS Code that an edit has taken place. + * + * @param EditType Type of edits used for the documents this delegate handles. */ - interface CustomEditorEditingCapability { + interface CustomEditorEditingDelegate { /** - * Save the resource. + * Save the resource for a custom editor. * - * @return Thenable signaling that the save has completed. + * This method is invoked by VS Code when the user saves a custom editor. This can happen when the user + * triggers save while the custom editor is active, by commands such as `save all`, or by auto save if enabled. + * + * To implement `save`, the delegate must persist the custom editor. This usually means writing the + * file data for the custom document to disk. After `save` completes, any associated editor instances will + * no longer be marked as dirty. + * + * @param document Document to save. + * @param cancellation Token that signals the save is no longer required (for example, if another save was triggered). + * + * @return Thenable signaling that saving has completed. */ - save(): Thenable; + save(document: CustomDocument, cancellation: CancellationToken): Thenable; /** - * Save the existing resource at a new path. + * Save the resource for a custom editor to a different location. * + * This method is invoked by VS Code when the user triggers `save as` on a custom editor. + * + * To implement `saveAs`, the delegate must persist the custom editor to `targetResource`. The + * existing editor will remain open after `saveAs` completes. + * + * @param document Document to save. * @param targetResource Location to save to. + * @param cancellation Token that signals the save is no longer required. * - * @return Thenable signaling that the save has completed. + * @return Thenable signaling that saving has completed. */ - saveAs(targetResource: Uri): Thenable; + saveAs(document: CustomDocument, targetResource: Uri, cancellation: CancellationToken): Thenable; /** - * Event triggered by extensions to signal to VS Code that an edit has occurred. + * Signal that an edit has occurred inside a custom editor. + * + * This event must be fired by your extension whenever an edit happens in a custom editor. An edit can be + * anything from changing some text, to cropping an image, to reordering a list. Your extension is free to + * define what an edit is and what data is stored on each edit. + * + * VS Code uses edits to determine if a custom editor is dirty or not. VS Code also passes the edit objects back + * to your extension when triggers undo, redo, or revert (using the `undoEdits`, `applyEdits`, and `revert` + * methods of `CustomEditorEditingDelegate`) */ - readonly onDidEdit: Event; + readonly onDidEdit: Event>; /** - * Apply a set of edits. + * Apply a list of edits to a custom editor. * - * Note that is not invoked when `onDidEdit` is called because `onDidEdit` implies also updating the view to reflect the edit. + * This method is invoked by VS Code when the user triggers `redo` in a custom editor. * - * @param edit Array of edits. Sorted from oldest to most recent. + * To implement `applyEdits`, the delegate must make sure all editor instances (webviews) for `document` + * are updated to render the document's new state (that is, every webview must be updated to show the document + * after applying `edits` to it). + * + * Note that `applyEdits` not invoked when `onDidEdit` is fired by your extension because `onDidEdit` implies + * that your extension has also updated its editor instances (webviews) to reflect the edit that just occurred. + * + * @param document Document to apply edits to. + * @param redoneEdits Array of edits that were redone. Sorted from oldest to most recent. Use [`document.appliedEdits`](#CustomDocument.appliedEdits) + * to get the full set of edits applied to the file (when `applyEdits` is called `appliedEdits` will already include + * the newly applied edit at the end). * * @return Thenable signaling that the change has completed. */ - applyEdits(edits: readonly EditType[]): Thenable; + applyEdits(document: CustomDocument, redoneEdits: ReadonlyArray): Thenable; /** - * Undo a set of edits. + * Undo a list of edits to a custom editor. * - * This is triggered when a user undoes an edit or when revert is called on a file. + * This method is invoked by VS Code when the user triggers `undo` in a custom editor. * - * @param edit Array of edits. Sorted from most recent to oldest. + * To implement `undoEdits`, the delegate must make sure all editor instances (webviews) for `document` + * are updated to render the document's new state (that is, every webview must be updated to show the document + * after undoing `edits` from it). + * + * @param document Document to undo edits from. + * @param undoneEdits Array of undone edits. Sorted from most recent to oldest. Use [`document.appliedEdits`](#CustomDocument.appliedEdits) + * to get the full set of edits applied to the file (when `undoEdits` is called, `appliedEdits` will already include + * have the undone edits removed). * * @return Thenable signaling that the change has completed. */ - undoEdits(edits: readonly EditType[]): Thenable; + undoEdits(document: CustomDocument, undoneEdits: ReadonlyArray): Thenable; + + /** + * Revert a custom editor to its last saved state. + * + * This method is invoked by VS Code when the user triggers `File: Revert File` in a custom editor. (Note that + * this is only used using VS Code's `File: Revert File` command and not on a `git revert` of the file). + * + * To implement `revert`, the delegate must make sure all editor instances (webviews) for `document` + * are displaying the document in the same state is saved in. This usually means reloading the file from the + * workspace. + * + * During `revert`, your extension should also clear any backups for the custom editor. Backups are only needed + * when there is a difference between an editor's state in VS Code and its save state on disk. + * + * @param document Document to revert. + * @param revert Object with added or removed edits to get back to the saved state. Use [`document.appliedEdits`](#CustomDocument.appliedEdits) + * to get the full set of edits applied to the file (when `revet` is called, `appliedEdits` will already have + * removed any edits undone by the revert and added any edits applied by the revert). + * + * @return Thenable signaling that the change has completed. + */ + revert(document: CustomDocument, revert: CustomDocumentRevert): Thenable; /** * Back up the resource in its current state. @@ -1268,124 +1313,221 @@ declare module 'vscode' { * made in quick succession, `backup` is only triggered after the last one. `backup` is not invoked when * `auto save` is enabled (since auto save already persists resource ). * + * @param document Document to backup. * @param cancellation Token that signals the current backup since a new backup is coming in. It is up to your * extension to decided how to respond to cancellation. If for example your extension is backing up a large file * in an operation that takes time to complete, your extension may decide to finish the ongoing backup rather * than cancelling it to ensure that VS Code has some valid backup. */ - backup(cancellation: CancellationToken): Thenable; + backup(document: CustomDocument, cancellation: CancellationToken): Thenable; } /** - * Represents a custom document for a custom webview editor. + * Event triggered by extensions to signal to VS Code that an edit has occurred on a `CustomDocument`. * - * Custom documents are only used within a given `CustomEditorProvider`. The lifecycle of a - * `CustomDocument` is managed by VS Code. When more more references remain to a given `CustomDocument` - * then it is disposed of. - * - * @param UserDataType Type of custom object that extensions can store on the document. + * @param EditType Type of edits used for the document. */ - interface CustomDocument { + interface CustomDocumentEditEvent { /** - * The associated viewType for this document. + * Document the edit is for. */ - readonly viewType: string; + readonly document: CustomDocument; + + /** + * Object that describes the edit. + * + * Edit objects are controlled entirely by your extension. Your extension should store whatever information it + * needs to on the edit to understand what type of edit was made, how to render that edit, and how to save that + * edit to disk. + * + * Edit objects are passed back to your extension in `CustomEditorEditingDelegate.undoEdits`, + * `CustomEditorEditingDelegate.applyEdits`, and `CustomEditorEditingDelegate.revert`. They can also be accessed + * using [`CustomDocument.appliedEdits`](#CustomDocument.appliedEdits) and [`CustomDocument.savedEdits`](#CustomDocument.savedEdits). + */ + readonly edit: EditType; + + /** + * Display name describing the edit. + */ + readonly label?: string; + } + + /** + * Delta for edits undone/redone while reverting for a `CustomDocument`. + * + * @param EditType Type of edits used for the document being reverted. + */ + interface CustomDocumentRevert { + /** + * List of edits that were undone to get the document back to its on disk state. + */ + readonly undoneEdits: ReadonlyArray; + + /** + * List of edits that were reapplied to get the document back to its on disk state. + */ + readonly appliedEdits: ReadonlyArray; + } + + /** + * Represents a custom document used by a [`CustomEditorProvider`](#CustomEditorProvider). + * + * Custom documents are only used within a given `CustomEditorProvider`. The lifecycle of a `CustomDocument` is + * managed by VS Code. When no more references remain to a `CustomDocument`, it is disposed of. + * + * @param EditType Type of edits used in this document. + */ + class CustomDocument { + /** + * @param uri The associated resource for this document. + */ + constructor(uri: Uri); /** * The associated uri for this document. */ readonly uri: Uri; + /** + * Is this document representing an untitled file which has never been saved yet. + */ + readonly isUntitled: boolean; + + /** + * The version number of this document (it will strictly increase after each + * change, including undo/redo). + */ + readonly version: number; + + /** + * `true` if there are unpersisted changes. + */ + readonly isDirty: boolean; + + /** + * List of edits from document open to the document's current state. + * + * `appliedEdits` returns a copy of the edit stack at the current point in time. Your extension should always + * use `CustomDocument.appliedEdits` to check the edit stack instead of holding onto a reference to `appliedEdits`. + */ + readonly appliedEdits: ReadonlyArray; + + /** + * List of edits from document open to the document's last saved point. + * + * The save point will be behind `appliedEdits` if the user saves and then continues editing, + * or in front of the last entry in `appliedEdits` if the user saves and then hits undo. + * + * `savedEdits` returns a copy of the edit stack at the current point in time. Your extension should always + * use `CustomDocument.savedEdits` to check the edit stack instead of holding onto a reference to `savedEdits`. + */ + readonly savedEdits: ReadonlyArray; + + /** + * `true` if the document has been closed. A closed document isn't synchronized anymore + * and won't be reused when the same resource is opened again. + */ + readonly isClosed: boolean; + /** * Event fired when there are no more references to the `CustomDocument`. + * + * This happens when all custom editors for the document have been closed. Once a `CustomDocument` is disposed, + * it will not be reused when the same resource is opened again. */ readonly onDidDispose: Event; - - /** - * Custom data that an extension can store on the document. - */ - userData?: UserDataType; - - // TODO: Should we expose edits here? - // This could be helpful for tracking the life cycle of edits } /** - * Provider for webview editors that use a custom data model. + * Provider for custom editors that use a custom document model. * - * Custom webview editors use [`CustomDocument`](#CustomDocument) as their data model. + * Custom editors use [`CustomDocument`](#CustomDocument) as their document model instead of a [`TextDocument`](#TextDocument). * This gives extensions full control over actions such as edit, save, and backup. * - * You should use custom text based editors when dealing with binary files or more complex scenarios. For simple text - * based documents, use [`WebviewTextEditorProvider`](#WebviewTextEditorProvider) instead. + * You should use this type of custom editor when dealing with binary files or more complex scenarios. For simple + * text based documents, use [`CustomTextEditorProvider`](#CustomTextEditorProvider) instead. + * + * @param EditType Type of edits used by the editors of this provider. */ - export interface CustomEditorProvider { - /** - * Resolve the model for a given resource. - * - * @param document Document to resolve. - * - * @return The capabilities of the resolved document. - */ - resolveCustomDocument(document: CustomDocument): Thenable; + export interface CustomEditorProvider { /** - * Resolve a webview editor for a given resource. + * Create a new document for a given resource. * - * To resolve a webview editor, the provider must fill in its initial html content and hook up all - * the event listeners it is interested it. The provider should also take ownership of the passed in `WebviewPanel`. + * `openCustomDocument` is called when the first editor for a given resource is opened, and the resolve document + * is passed to `resolveCustomEditor`. The resolved `CustomDocument` is re-used for subsequent editor opens. + * If all editors for a given resource are closed, the `CustomDocument` is disposed of. Opening an editor at + * this point will trigger another call to `openCustomDocument`. + * + * @param uri Uri of the document to open. + * @param token A cancellation token that indicates the result is no longer needed. + * + * @return The custom document. + */ + openCustomDocument(uri: Uri, token: CancellationToken): Thenable> | CustomDocument; + + /** + * Resolve a custom editor for a given resource. + * + * This is called whenever the user opens a new editor for this `CustomEditorProvider`. + * + * To resolve a custom editor, the provider must fill in its initial html content and hook up all + * the event listeners it is interested it. The provider can also hold onto the `WebviewPanel` to use later, + * for example in a command. See [`WebviewPanel`](#WebviewPanel) for additional details. * * @param document Document for the resource being resolved. - * @param webviewPanel Webview to resolve. The provider should take ownership of this webview. + * @param webviewPanel Webview to resolve. + * @param token A cancellation token that indicates the result is no longer needed. * - * @return Thenable indicating that the webview editor has been resolved. + * @return Optional thenable indicating that the custom editor has been resolved. */ - resolveCustomEditor(document: CustomDocument, webviewPanel: WebviewPanel): Thenable; - } + resolveCustomEditor(document: CustomDocument, webviewPanel: WebviewPanel, token: CancellationToken): Thenable | void; - /** - * Provider for text based webview editors. - * - * Text based webview editors use a [`TextDocument`](#TextDocument) as their data model. This considerably simplifies - * implementing a webview editor as it allows VS Code to handle many common operations such as - * undo and backup. The provider is responsible for synchronizing text changes between the webview and the `TextDocument`. - * - * You should use text based webview editors when dealing with text based file formats, such as `xml` or `json`. - * For binary files or more specialized use cases, see [CustomEditorProvider](#CustomEditorProvider). - */ - export interface CustomTextEditorProvider { /** - * Resolve a webview editor for a given resource. + * Defines the editing capability of the provider. * - * To resolve a webview editor, the provider must fill in its initial html content and hook up all - * the event listeners it is interested it. The provider should also take ownership of the passed in `WebviewPanel`. - * - * @param document Resource being resolved. - * @param webviewPanel Webview to resolve. The provider should take ownership of this webview. - * - * @return Thenable indicating that the webview editor has been resolved. + * When not provided, editors for this provider are considered readonly. */ - resolveCustomTextEditor(document: TextDocument, webviewPanel: WebviewPanel): Thenable; + readonly editingDelegate?: CustomEditorEditingDelegate; } namespace window { /** - * Register a new provider for a custom editor. - * - * @param viewType Type of the webview editor provider. This should match the `viewType` from the - * `package.json` contributions. - * @param provider Provider that resolves editors. - * @param webviewOptions Content settings for the webview panels that the provider is given. - * - * @return Disposable that unregisters the provider. + * Temporary overload for `registerCustomEditorProvider` that takes a `CustomEditorProvider`. */ - export function registerCustomEditorProvider( + export function registerCustomEditorProvider2( viewType: string, - provider: CustomEditorProvider | CustomTextEditorProvider, - webviewOptions?: WebviewPanelOptions, + provider: CustomEditorProvider, + options?: { + readonly webviewOptions?: WebviewPanelOptions; + } ): Disposable; } + // #endregion + + //#region Custom editor move https://github.com/microsoft/vscode/issues/86146 + + // TODO: Also for custom editor + + export interface CustomTextEditorProvider { + + + /** + * Handle when the underlying resource for a custom editor is renamed. + * + * This allows the webview for the editor be preserved throughout the rename. If this method is not implemented, + * VS Code will destory the previous custom editor and create a replacement one. + * + * @param newDocument New text document to use for the custom editor. + * @param existingWebviewPanel Webview panel for the custom editor. + * @param token A cancellation token that indicates the result is no longer needed. + * + * @return Thenable indicating that the webview editor has been moved. + */ + moveCustomTextEditor?(newDocument: TextDocument, existingWebviewPanel: WebviewPanel, token: CancellationToken): Thenable; + } + //#endregion @@ -1412,6 +1554,200 @@ declare module 'vscode' { //#endregion + //#region Peng: Notebook + + export enum CellKind { + Markdown = 1, + Code = 2 + } + + export enum CellOutputKind { + Text = 1, + Error = 2, + Rich = 3 + } + + export interface CellStreamOutput { + outputKind: CellOutputKind.Text; + text: string; + } + + export interface CellErrorOutput { + outputKind: CellOutputKind.Error; + /** + * Exception Name + */ + ename: string; + /** + * Exception Value + */ + evalue: string; + /** + * Exception call stack + */ + traceback: string[]; + } + + export interface CellDisplayOutput { + outputKind: CellOutputKind.Rich; + /** + * { mime_type: value } + * + * Example: + * ```json + * { + * "outputKind": vscode.CellOutputKind.Rich, + * "data": { + * "text/html": [ + * "

Hello

" + * ], + * "text/plain": [ + * "" + * ] + * } + * } + */ + data: { [key: string]: any }; + } + + export type CellOutput = CellStreamOutput | CellErrorOutput | CellDisplayOutput; + + export interface NotebookCellMetadata { + /** + * Controls if the content of a cell is editable or not. + */ + editable?: boolean; + + /** + * Controls if the cell is executable. + * This metadata is ignored for markdown cell. + */ + runnable?: boolean; + + /** + * The order in which this cell was executed. + */ + executionOrder?: number; + } + + export interface NotebookCell { + readonly uri: Uri; + readonly cellKind: CellKind; + readonly source: string; + language: string; + outputs: CellOutput[]; + metadata: NotebookCellMetadata; + } + + export interface NotebookDocumentMetadata { + /** + * Controls if users can add or delete cells + * Defaults to true + */ + editable?: boolean; + + /** + * Default value for [cell editable metadata](#NotebookCellMetadata.editable). + * Defaults to true. + */ + cellEditable?: boolean; + + /** + * Default value for [cell runnable metadata](#NotebookCellMetadata.runnable). + * Defaults to true. + */ + cellRunnable?: boolean; + + /** + * Whether the [execution order](#NotebookCellMetadata.executionOrder) indicator will be displayed. + * Defaults to true. + */ + hasExecutionOrder?: boolean; + } + + export interface NotebookDocument { + readonly uri: Uri; + readonly fileName: string; + readonly isDirty: boolean; + readonly cells: NotebookCell[]; + languages: string[]; + displayOrder?: GlobPattern[]; + metadata: NotebookDocumentMetadata; + } + + export interface NotebookEditorCellEdit { + insert(index: number, content: string | string[], language: string, type: CellKind, outputs: CellOutput[], metadata: NotebookCellMetadata | undefined): void; + delete(index: number): void; + } + + export interface NotebookEditor { + readonly document: NotebookDocument; + viewColumn?: ViewColumn; + /** + * Fired when the output hosting webview posts a message. + */ + readonly onDidReceiveMessage: Event; + /** + * Post a message to the output hosting webview. + * + * Messages are only delivered if the editor is live. + * + * @param message Body of the message. This must be a string or other json serilizable object. + */ + postMessage(message: any): Thenable; + + edit(callback: (editBuilder: NotebookEditorCellEdit) => void): Thenable; + } + + export interface NotebookProvider { + resolveNotebook(editor: NotebookEditor): Promise; + executeCell(document: NotebookDocument, cell: NotebookCell | undefined, token: CancellationToken): Promise; + save(document: NotebookDocument): Promise; + } + + export interface NotebookOutputSelector { + type: string; + subTypes?: string[]; + } + + export interface NotebookOutputRenderer { + /** + * + * @returns HTML fragment. We can probably return `CellOutput` instead of string ? + * + */ + render(document: NotebookDocument, output: CellOutput, mimeType: string): string; + preloads?: Uri[]; + } + + export interface NotebookDocumentChangeEvent { + + /** + * The affected document. + */ + readonly document: NotebookDocument; + + /** + * An array of content changes. + */ + // readonly contentChanges: ReadonlyArray; + } + + export namespace notebook { + export function registerNotebookProvider( + notebookType: string, + provider: NotebookProvider + ): Disposable; + + export function registerNotebookOutputRenderer(type: string, outputSelector: NotebookOutputSelector, renderer: NotebookOutputRenderer): Disposable; + + export let activeNotebookDocument: NotebookDocument | undefined; + + // export const onDidChangeNotebookDocument: Event; + } + + //#endregion + //#region color theme access /** @@ -1466,9 +1802,9 @@ declare module 'vscode' { name: string; /** - * The signature without the return type. Render after `name`. + * The parameters without the return type. Render after `name`. */ - signature?: string; + parameters?: string; /** * The fully qualified name, like package name or file path. Rendered after `signature`. @@ -1567,17 +1903,10 @@ declare module 'vscode' { export interface Timeline { readonly paging?: { /** - * A set of provider-defined cursors specifing the range of timeline items returned. + * A provider-defined cursor specifying the starting point of timeline items which are after the ones returned. + * Use `undefined` to signal that there are no more items to be returned. */ - readonly cursors: { - readonly before: string; - readonly after?: string - }; - - /** - * A flag which indicates whether there are more items that weren't returned. - */ - readonly more?: boolean; + readonly cursor: string | undefined; } /** @@ -1588,19 +1917,15 @@ declare module 'vscode' { export interface TimelineOptions { /** - * A provider-defined cursor specifing the range of timeline items that should be returned. + * A provider-defined cursor specifying the starting point of the timeline items that should be returned. */ cursor?: string; /** - * A flag to specify whether the timeline items being requested should be before or after (default) the provided cursor. + * An optional maximum number timeline items or the all timeline items newer (inclusive) than the timestamp or id that should be returned. + * If `undefined` all timeline items should be returned. */ - before?: boolean; - - /** - * The maximum number or the ending cursor of timeline items that should be returned. - */ - limit?: number | string; + limit?: number | { timestamp: number; id?: string }; } export interface TimelineProvider { @@ -1649,31 +1974,6 @@ declare module 'vscode' { //#endregion - - //#region https://github.com/microsoft/vscode/issues/90208 - - export interface ExtensionContext { - /** - * Get the uri of a resource contained in the extension. - * - * @param relativePath A relative path to a resource contained in the extension. - * @return The uri of the resource. - */ - asExtensionUri(relativePath: string): Uri; - } - - export interface Extension { - /** - * Get the uri of a resource contained in the extension. - * - * @param relativePath A relative path to a resource contained in the extension. - * @return The uri of the resource. - */ - asExtensionUri(relativePath: string): Uri; - } - - //#endregion - //#region https://github.com/microsoft/vscode/issues/86788 export interface CodeActionProviderMetadata { @@ -1728,4 +2028,60 @@ declare module 'vscode' { } //#endregion + + //#region https://github.com/microsoft/vscode/issues/90208 + + export interface ExtensionContext { + /** + * @deprecated THIS API PROPOSAL WILL BE DROPPED + */ + asExtensionUri(relativePath: string): Uri; + + /** + * The uri of the directory containing the extension. + */ + readonly extensionUri: Uri; + } + + export interface Extension { + /** + * @deprecated THIS API PROPOSAL WILL BE DROPPED + */ + asExtensionUri(relativePath: string): Uri; + + /** + * The uri of the directory containing the extension. + */ + readonly extensionUri: Uri; + } + + export namespace Uri { + + /** + * Create a new uri which path is the result of joining + * the path of the base uri with the provided path fragments. + * + * * Note that `joinPath` only affects the path component + * and all other components (scheme, authority, query, and fragment) are + * left as they are. + * * Note that the base uri must have a path; an error is thrown otherwise. + * + * @param base An uri. Must have a path. + * @param pathFragments One more more path fragments + * @returns A new uri which path is joined with the given fragments + */ + export function joinPath(base: Uri, ...pathFragments: string[]): Uri; + } + + //#endregion + + //#region https://github.com/microsoft/vscode/issues/91541 + + export enum CompletionItemKind { + User = 25, + Issue = 26, + } + + //#endregion + } diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index e69aa80159d..3f2de2c7380 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -56,6 +56,7 @@ import './mainThreadWindow'; import './mainThreadWebview'; import './mainThreadWorkspace'; import './mainThreadComments'; +import './mainThreadNotebook'; import './mainThreadTask'; import './mainThreadLabelService'; import './mainThreadTunnelService'; diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 15af9ba8bc6..fc92d7ae6b5 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import * as modes from 'vs/editor/common/modes'; import * as nls from 'vs/nls'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; @@ -12,13 +12,175 @@ import { ExtHostAuthenticationShape, ExtHostContext, IExtHostContext, MainContex import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import Severity from 'vs/base/common/severity'; +import { MenuRegistry, MenuId, IMenuItem } from 'vs/platform/actions/common/actions'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; + +interface AuthDependent { + providerId: string; + label: string; + scopes: string[]; + scopeDescriptions?: string; +} + +const BUILT_IN_AUTH_DEPENDENTS: AuthDependent[] = [ + { + providerId: 'microsoft', + label: 'Settings sync', + scopes: ['https://management.core.windows.net/.default', 'offline_access'], + scopeDescriptions: 'Read user email' + } +]; + +interface AllowedExtension { + id: string; + name: string; +} + +function readAllowedExtensions(storageService: IStorageService, providerId: string, accountName: string): AllowedExtension[] { + let trustedExtensions: AllowedExtension[] = []; + try { + const trustedExtensionSrc = storageService.get(`${providerId}-${accountName}`, StorageScope.GLOBAL); + if (trustedExtensionSrc) { + trustedExtensions = JSON.parse(trustedExtensionSrc); + } + } catch (err) { } + + return trustedExtensions; +} + +export class MainThreadAuthenticationProvider extends Disposable { + private _sessionMenuItems = new Map(); + private _accounts = new Map(); // Map account name to session ids + private _sessions = new Map(); // Map account id to name + private _signInMenuItem: IMenuItem | undefined; -export class MainThreadAuthenticationProvider { constructor( private readonly _proxy: ExtHostAuthenticationShape, public readonly id: string, - public readonly displayName: string - ) { } + public readonly displayName: string, + public readonly dependents: AuthDependent[] + ) { + super(); + + this.registerCommandsAndContextMenuItems(); + } + + private manageTrustedExtensions(quickInputService: IQuickInputService, storageService: IStorageService, accountName: string) { + const quickPick = quickInputService.createQuickPick<{ label: string, extension: AllowedExtension }>(); + quickPick.canSelectMany = true; + const allowedExtensions = readAllowedExtensions(storageService, this.id, accountName); + const items = allowedExtensions.map(extension => { + return { + label: extension.name, + extension + }; + }); + + quickPick.items = items; + quickPick.selectedItems = items; + quickPick.title = nls.localize('manageTrustedExtensions', "Manage Trusted Extensions"); + quickPick.placeholder = nls.localize('manageExensions', "Choose which extensions can access this account"); + + quickPick.onDidAccept(() => { + const updatedAllowedList = quickPick.selectedItems.map(item => item.extension); + storageService.store(`${this.id}-${accountName}`, JSON.stringify(updatedAllowedList), StorageScope.GLOBAL); + + quickPick.dispose(); + }); + + quickPick.onDidHide(() => { + quickPick.dispose(); + }); + + quickPick.show(); + } + + private async registerCommandsAndContextMenuItems(): Promise { + const sessions = await this._proxy.$getSessions(this.id); + + if (this.dependents.length) { + this._register(CommandsRegistry.registerCommand({ + id: `signIn${this.id}`, + handler: (accessor, args) => { + this.login(this.dependents.reduce((previous: string[], current) => previous.concat(current.scopes), [])); + }, + })); + + this._signInMenuItem = { + group: '2_providers', + command: { + id: `signIn${this.id}`, + title: sessions.length + ? nls.localize('addAnotherAccount', "Sign in to another {0} account", this.displayName) + : nls.localize('addAccount', "Sign in to {0}", this.displayName) + }, + order: 3 + }; + + this._register(MenuRegistry.appendMenuItem(MenuId.AccountsContext, this._signInMenuItem)); + } + + sessions.forEach(session => this.registerSession(session)); + } + + private registerSession(session: modes.AuthenticationSession) { + this._sessions.set(session.id, session.accountName); + + const existingSessionsForAccount = this._accounts.get(session.accountName); + if (existingSessionsForAccount) { + this._accounts.set(session.accountName, existingSessionsForAccount.concat(session.id)); + return; + } else { + this._accounts.set(session.accountName, [session.id]); + } + + const menuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, { + group: '1_accounts', + command: { + id: `configureSessions${session.id}`, + title: session.accountName + }, + order: 3 + }); + + const manageCommand = CommandsRegistry.registerCommand({ + id: `configureSessions${session.id}`, + handler: (accessor, args) => { + const quickInputService = accessor.get(IQuickInputService); + const storageService = accessor.get(IStorageService); + + const quickPick = quickInputService.createQuickPick(); + const manage = nls.localize('manageTrustedExtensions', "Manage Trusted Extensions"); + const signOut = nls.localize('signOut', "Sign Out"); + const items = ([{ label: manage }, { label: signOut }]); + + quickPick.items = items; + + quickPick.onDidAccept(e => { + const selected = quickPick.selectedItems[0]; + if (selected.label === signOut) { + const sessionsForAccount = this._accounts.get(session.accountName); + sessionsForAccount?.forEach(sessionId => this.logout(sessionId)); + } + + if (selected.label === manage) { + this.manageTrustedExtensions(quickInputService, storageService, session.accountName); + } + + quickPick.dispose(); + }); + + quickPick.onDidHide(_ => { + quickPick.dispose(); + }); + + quickPick.show(); + }, + }); + + this._sessionMenuItems.set(session.accountName, [menuItem, manageCommand]); + } async getSessions(): Promise> { return (await this._proxy.$getSessions(this.id)).map(session => { @@ -30,6 +192,40 @@ export class MainThreadAuthenticationProvider { }); } + async updateSessionItems(event: modes.AuthenticationSessionsChangeEvent): Promise { + const { added, removed } = event; + const session = await this._proxy.$getSessions(this.id); + const addedSessions = session.filter(session => added.some(id => id === session.id)); + + removed.forEach(sessionId => { + const accountName = this._sessions.get(sessionId); + if (accountName) { + let sessionsForAccount = this._accounts.get(accountName) || []; + const sessionIndex = sessionsForAccount.indexOf(sessionId); + sessionsForAccount.splice(sessionIndex); + + if (!sessionsForAccount.length) { + const disposeables = this._sessionMenuItems.get(accountName); + if (disposeables) { + disposeables.forEach(disposeable => disposeable.dispose()); + this._sessionMenuItems.delete(accountName); + } + this._accounts.delete(accountName); + + if (this._signInMenuItem) { + this._signInMenuItem.command.title = nls.localize('addAccount', "Sign in to {0}", this.displayName); + } + } + } + }); + + addedSessions.forEach(session => this.registerSession(session)); + + if (addedSessions.length && this._signInMenuItem) { + this._signInMenuItem.command.title = nls.localize('addAnotherAccount', "Sign in to another {0} account", this.displayName); + } + } + login(scopes: string[]): Promise { return this._proxy.$login(this.id, scopes).then(session => { return { @@ -40,8 +236,14 @@ export class MainThreadAuthenticationProvider { }); } - logout(accountId: string): Promise { - return this._proxy.$logout(this.id, accountId); + logout(sessionId: string): Promise { + return this._proxy.$logout(this.id, sessionId); + } + + dispose(): void { + super.dispose(); + this._sessionMenuItems.forEach(item => item.forEach(d => d.dispose())); + this._sessionMenuItems.clear(); } } @@ -59,8 +261,10 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostAuthentication); } - $registerAuthenticationProvider(id: string, displayName: string): void { - const provider = new MainThreadAuthenticationProvider(this._proxy, id, displayName); + async $registerAuthenticationProvider(id: string, displayName: string): Promise { + const dependentBuiltIns = BUILT_IN_AUTH_DEPENDENTS.filter(dependency => dependency.providerId === id); + + const provider = new MainThreadAuthenticationProvider(this._proxy, id, displayName, dependentBuiltIns); this.authenticationService.registerAuthenticationProvider(id, provider); } @@ -68,59 +272,45 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu this.authenticationService.unregisterAuthenticationProvider(id); } - $onDidChangeSessions(id: string): void { - this.authenticationService.sessionsUpdate(id); + $onDidChangeSessions(id: string, event: modes.AuthenticationSessionsChangeEvent): void { + this.authenticationService.sessionsUpdate(id, event); } - async $getSessionsPrompt(providerId: string, providerName: string, extensionId: string, extensionName: string): Promise { - const alwaysAllow = this.storageService.get(`${extensionId}-${providerId}`, StorageScope.GLOBAL); - if (alwaysAllow) { - return alwaysAllow === 'true'; + async $getSessionsPrompt(providerId: string, accountName: string, providerName: string, extensionId: string, extensionName: string): Promise { + const allowList = readAllowedExtensions(this.storageService, providerId, accountName); + const extensionData = allowList.find(extension => extension.id === extensionId); + if (extensionData) { + return true; } - const { choice, checkboxChecked } = await this.dialogService.show( + const { choice } = await this.dialogService.show( Severity.Info, - nls.localize('confirmAuthenticationAccess', "The extension '{0}' is trying to access authentication information from {1}.", extensionName, providerName), + nls.localize('confirmAuthenticationAccess', "The extension '{0}' is trying to access authentication information for the {1} account '{2}'.", extensionName, providerName, accountName), [nls.localize('cancel', "Cancel"), nls.localize('allow', "Allow")], { - cancelId: 0, - checkbox: { - label: nls.localize('neverAgain', "Don't Show Again") - } + cancelId: 0 } ); const allow = choice === 1; - if (checkboxChecked) { - this.storageService.store(`${extensionId}-${providerId}`, allow ? 'true' : 'false', StorageScope.GLOBAL); + if (allow) { + allowList.push({ id: extensionId, name: extensionName }); + this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL); } return allow; } - async $loginPrompt(providerId: string, providerName: string, extensionId: string, extensionName: string): Promise { - const alwaysAllow = this.storageService.get(`${extensionId}-${providerId}`, StorageScope.GLOBAL); - if (alwaysAllow) { - return alwaysAllow === 'true'; - } - - const { choice, checkboxChecked } = await this.dialogService.show( + async $loginPrompt(providerName: string, extensionName: string): Promise { + const { choice } = await this.dialogService.show( Severity.Info, nls.localize('confirmLogin', "The extension '{0}' wants to sign in using {1}.", extensionName, providerName), - [nls.localize('cancel', "Cancel"), nls.localize('continue', "Continue")], + [nls.localize('cancel', "Cancel"), nls.localize('allow', "Allow")], { - cancelId: 0, - checkbox: { - label: nls.localize('neverAgain', "Don't Show Again") - } + cancelId: 0 } ); - const allow = choice === 1; - if (checkboxChecked) { - this.storageService.store(`${extensionId}-${providerId}`, allow ? 'true' : 'false', StorageScope.GLOBAL); - } - - return allow; + return choice === 1; } } diff --git a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts index b504c55e3b2..8568dedd56b 100644 --- a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts +++ b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts @@ -89,7 +89,7 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape { const disposables = new DisposableStore(); - const webview = this._webviewService.createWebview('' + handle, { + const webview = this._webviewService.createWebviewElement('' + handle, { enableFindWidget: false, }, { allowScripts: options.enableScripts, diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index bc2a5d3e881..b6bf77177b8 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -452,6 +452,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments id: COMMENTS_VIEW_ID, name: COMMENTS_VIEW_TITLE, ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [COMMENTS_VIEW_ID, COMMENTS_VIEW_TITLE, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + hideIfEmpty: true, order: 10, }, ViewContainerLocation.Panel); @@ -460,6 +461,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments name: COMMENTS_VIEW_TITLE, canToggleVisibility: false, ctorDescriptor: new SyncDescriptor(CommentsPanel), + canMoveView: true, focusCommand: { id: 'workbench.action.focusCommentsPanel' } diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index 4e48c4bf864..9686820a36a 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -75,7 +75,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb } runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise { - return Promise.resolve(this._proxy.$runInTerminal(args)); + return this._proxy.$runInTerminal(args); } // RPC methods (MainThreadDebugServiceShape) diff --git a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts index e1401d465be..78da88e8955 100644 --- a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts @@ -5,7 +5,6 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IDisposable, combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { values } from 'vs/base/common/map'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor, isCodeEditor, isDiffEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; @@ -22,7 +21,7 @@ import { MainThreadTextEditors } from 'vs/workbench/api/browser/mainThreadEditor import { ExtHostContext, ExtHostDocumentsAndEditorsShape, IDocumentsAndEditorsDelta, IExtHostContext, IModelAddedData, ITextEditorAddData, MainContext } from 'vs/workbench/api/common/extHost.protocol'; import { EditorViewColumn, editorGroupToViewColumn } from 'vs/workbench/api/common/shared/editor'; import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; -import { IEditor as IWorkbenchEditor } from 'vs/workbench/common/editor'; +import { IEditorPane } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; @@ -110,8 +109,8 @@ class DocumentAndEditorState { static compute(before: DocumentAndEditorState | undefined, after: DocumentAndEditorState): DocumentAndEditorStateDelta { if (!before) { return new DocumentAndEditorStateDelta( - [], values(after.documents), - [], values(after.textEditors), + [], [...after.documents.values()], + [], [...after.textEditors.values()], undefined, after.activeEditor ); } @@ -291,11 +290,11 @@ class MainThreadDocumentAndEditorStateComputer { } private _getActiveEditorFromEditorPart(): IEditor | undefined { - let result = this._editorService.activeTextEditorWidget; - if (isDiffEditor(result)) { - result = result.getModifiedEditor(); + let activeTextEditorControl = this._editorService.activeTextEditorControl; + if (isDiffEditor(activeTextEditorControl)) { + activeTextEditorControl = activeTextEditorControl.getModifiedEditor(); } - return result; + return activeTextEditorControl; } } @@ -436,17 +435,17 @@ export class MainThreadDocumentsAndEditors { } private _findEditorPosition(editor: MainThreadTextEditor): EditorViewColumn | undefined { - for (const workbenchEditor of this._editorService.visibleControls) { - if (editor.matches(workbenchEditor)) { - return editorGroupToViewColumn(this._editorGroupService, workbenchEditor.group); + for (const editorPane of this._editorService.visibleEditorPanes) { + if (editor.matches(editorPane)) { + return editorGroupToViewColumn(this._editorGroupService, editorPane.group); } } return undefined; } - findTextEditorIdFor(inputEditor: IWorkbenchEditor): string | undefined { + findTextEditorIdFor(editorPane: IEditorPane): string | undefined { for (const [id, editor] of this._textEditors) { - if (editor.matches(inputEditor)) { + if (editor.matches(editorPane)) { return id; } } diff --git a/src/vs/workbench/api/browser/mainThreadEditor.ts b/src/vs/workbench/api/browser/mainThreadEditor.ts index e7d981fe98e..1cb9c614eb4 100644 --- a/src/vs/workbench/api/browser/mainThreadEditor.ts +++ b/src/vs/workbench/api/browser/mainThreadEditor.ts @@ -14,7 +14,7 @@ import { ISingleEditOperation, ITextModel, ITextModelUpdateOptions, IIdentifiedS import { IModelService } from 'vs/editor/common/services/modelService'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; import { IApplyEditsOptions, IEditorPropertiesChangeData, IResolvedTextEditorConfiguration, ITextEditorConfigurationUpdate, IUndoStopOptions, TextEditorRevealType } from 'vs/workbench/api/common/extHost.protocol'; -import { IEditor } from 'vs/workbench/common/editor'; +import { IEditorPane } from 'vs/workbench/common/editor'; import { withNullAsUndefined } from 'vs/base/common/types'; import { equals } from 'vs/base/common/arrays'; @@ -413,7 +413,7 @@ export class MainThreadTextEditor { return false; } - public matches(editor: IEditor): boolean { + public matches(editor: IEditorPane): boolean { if (!editor) { return false; } diff --git a/src/vs/workbench/api/browser/mainThreadEditors.ts b/src/vs/workbench/api/browser/mainThreadEditors.ts index c5e48381abd..a1f130d4c37 100644 --- a/src/vs/workbench/api/browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadEditors.ts @@ -15,7 +15,7 @@ import { ISelection } from 'vs/editor/common/core/selection'; import { IDecorationOptions, IDecorationRenderOptions, ILineChange } from 'vs/editor/common/editorCommon'; import { ISingleEditOperation } from 'vs/editor/common/model'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IEditorOptions, ITextEditorOptions, IResourceInput, EditorActivation } from 'vs/platform/editor/common/editor'; +import { IEditorOptions, ITextEditorOptions, IResourceEditorInput, EditorActivation } from 'vs/platform/editor/common/editor'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { MainThreadDocumentsAndEditors } from 'vs/workbench/api/browser/mainThreadDocumentsAndEditors'; @@ -101,10 +101,10 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { private _getTextEditorPositionData(): ITextEditorPositionData { const result: ITextEditorPositionData = Object.create(null); - for (let workbenchEditor of this._editorService.visibleControls) { - const id = this._documentsAndEditors.findTextEditorIdFor(workbenchEditor); + for (let editorPane of this._editorService.visibleEditorPanes) { + const id = this._documentsAndEditors.findTextEditorIdFor(editorPane); if (id) { - result[id] = editorGroupToViewColumn(this._editorGroupService, workbenchEditor.group); + result[id] = editorGroupToViewColumn(this._editorGroupService, editorPane.group); } } return result; @@ -124,7 +124,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { activation: options.preserveFocus ? EditorActivation.RESTORE : undefined }; - const input: IResourceInput = { + const input: IResourceEditorInput = { resource: uri, options: editorOptions }; @@ -151,10 +151,10 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { async $tryHideEditor(id: string): Promise { const mainThreadEditor = this._documentsAndEditors.getEditor(id); if (mainThreadEditor) { - const editors = this._editorService.visibleControls; - for (let editor of editors) { - if (mainThreadEditor.matches(editor)) { - return editor.group.closeEditor(editor.input); + const editorPanes = this._editorService.visibleEditorPanes; + for (let editorPane of editorPanes) { + if (mainThreadEditor.matches(editorPane)) { + return editorPane.group.closeEditor(editorPane.input); } } } diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 4287e53a8c0..0f52d714290 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable } from 'vs/base/common/lifecycle'; -import { Emitter } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { ITextModel, ISingleEditOperation } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import * as search from 'vs/workbench/contrib/search/common/search'; @@ -21,7 +21,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { mixin } from 'vs/base/common/objects'; -import { decodeSemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokens'; +import { decodeSemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokensDto'; @extHostNamedCustomer(MainContext.MainThreadLanguageFeatures) export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesShape { @@ -261,6 +261,18 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha })); } + // --- on type rename + + $registerOnTypeRenameProvider(handle: number, selector: IDocumentFilterDto[], stopPattern?: IRegExpDto): void { + const revivedStopPattern = stopPattern ? MainThreadLanguageFeatures._reviveRegExp(stopPattern) : undefined; + this._registrations.set(handle, modes.OnTypeRenameProviderRegistry.register(selector, { + stopPattern: revivedStopPattern, + provideOnTypeRenameRanges: (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise => { + return this._proxy.$provideOnTypeRenameRanges(handle, model.uri, position, token); + } + })); + } + // --- references $registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void { @@ -367,8 +379,21 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- semantic tokens - $registerDocumentSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend): void { - this._registrations.set(handle, modes.DocumentSemanticTokensProviderRegistry.register(selector, new MainThreadDocumentSemanticTokensProvider(this._proxy, handle, legend))); + $registerDocumentSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend, eventHandle: number | undefined): void { + let event: Event | undefined = undefined; + if (typeof eventHandle === 'number') { + const emitter = new Emitter(); + this._registrations.set(eventHandle, emitter); + event = emitter.event; + } + this._registrations.set(handle, modes.DocumentSemanticTokensProviderRegistry.register(selector, new MainThreadDocumentSemanticTokensProvider(this._proxy, handle, legend, event))); + } + + $emitDocumentSemanticTokensEvent(eventHandle: number): void { + const obj = this._registrations.get(eventHandle); + if (obj instanceof Emitter) { + obj.fire(undefined); + } } $registerDocumentRangeSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend): void { @@ -661,6 +686,7 @@ export class MainThreadDocumentSemanticTokensProvider implements modes.DocumentS private readonly _proxy: ExtHostLanguageFeaturesShape, private readonly _handle: number, private readonly _legend: modes.SemanticTokensLegend, + public readonly onDidChange: Event | undefined, ) { } diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts new file mode 100644 index 00000000000..62afe241ea3 --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -0,0 +1,291 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; +import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IExtHostContext, ExtHostNotebookShape, ExtHostContext } from '../common/extHost.protocol'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/browser/notebookService'; +import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, CellKind, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CancellationToken } from 'vs/base/common/cancellation'; + +export class MainThreadNotebookDocument extends Disposable { + private _textModel: NotebookTextModel; + + get textModel() { + return this._textModel; + } + + constructor( + private readonly _proxy: ExtHostNotebookShape, + public handle: number, + public viewType: string, + public uri: URI + ) { + super(); + this._textModel = new NotebookTextModel(handle, viewType, uri); + this._register(this._textModel.onDidModelChange(e => { + this._proxy.$acceptModelChanged(this.uri, e); + })); + } + + applyEdit(modelVersionId: number, edits: ICellEditOperation[]): boolean { + return this._textModel.applyEdit(modelVersionId, edits); + } + + updateRenderers(renderers: number[]) { + this._textModel.updateRenderers(renderers); + } + + dispose() { + this._textModel.dispose(); + super.dispose(); + } +} + +@extHostNamedCustomer(MainContext.MainThreadNotebook) +export class MainThreadNotebooks extends Disposable implements MainThreadNotebookShape { + private readonly _notebookProviders = new Map(); + private readonly _proxy: ExtHostNotebookShape; + + constructor( + extHostContext: IExtHostContext, + @INotebookService private _notebookService: INotebookService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IEditorService private readonly editorService: IEditorService, + + ) { + super(); + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook); + this.registerListeners(); + } + + async $tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise { + let controller = this._notebookProviders.get(viewType); + + if (controller) { + return controller.tryApplyEdits(resource, modelVersionId, edits, renderers); + } + + return false; + } + + registerListeners() { + this._register(this._notebookService.onDidChangeActiveEditor(e => { + this._proxy.$updateActiveEditor(e.viewType, e.uri); + })); + + let userOrder = this.configurationService.getValue('notebook.displayOrder'); + this._proxy.$acceptDisplayOrder({ + defaultOrder: NOTEBOOK_DISPLAY_ORDER, + userOrder: userOrder + }); + + this.configurationService.onDidChangeConfiguration(e => { + if (e.affectedKeys.indexOf('notebook.displayOrder') >= 0) { + let userOrder = this.configurationService.getValue('notebook.displayOrder'); + + this._proxy.$acceptDisplayOrder({ + defaultOrder: NOTEBOOK_DISPLAY_ORDER, + userOrder: userOrder + }); + } + }); + } + + async $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, handle: number, preloads: UriComponents[]): Promise { + this._notebookService.registerNotebookRenderer(handle, extension, type, selectors, preloads.map(uri => URI.revive(uri))); + } + + async $unregisterNotebookRenderer(handle: number): Promise { + this._notebookService.unregisterNotebookRenderer(handle); + } + + async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string): Promise { + let controller = new MainThreadNotebookController(this._proxy, this, viewType); + this._notebookProviders.set(viewType, controller); + this._notebookService.registerNotebookController(viewType, extension, controller); + return; + } + + async $unregisterNotebookProvider(viewType: string): Promise { + this._notebookProviders.delete(viewType); + this._notebookService.unregisterNotebookProvider(viewType); + return; + } + + async $createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise { + let controller = this._notebookProviders.get(viewType); + + if (controller) { + controller.createNotebookDocument(handle, viewType, resource); + } + + return; + } + + async $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise { + let controller = this._notebookProviders.get(viewType); + + if (controller) { + controller.updateLanguages(resource, languages); + } + } + + async $updateNotebookMetadata(viewType: string, resource: UriComponents, metadata: NotebookDocumentMetadata): Promise { + let controller = this._notebookProviders.get(viewType); + + if (controller) { + controller.updateNotebookMetadata(resource, metadata); + } + } + + async $updateNotebookCellMetadata(viewType: string, resource: UriComponents, handle: number, metadata: NotebookCellMetadata): Promise { + let controller = this._notebookProviders.get(viewType); + + if (controller) { + controller.updateNotebookCellMetadata(resource, handle, metadata); + } + } + + async resolveNotebook(viewType: string, uri: URI): Promise { + let handle = await this._proxy.$resolveNotebook(viewType, uri); + return handle; + } + + async $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): Promise { + let controller = this._notebookProviders.get(viewType); + controller?.spliceNotebookCellOutputs(resource, cellHandle, splices, renderers); + } + + async executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise { + return this._proxy.$executeNotebook(viewType, uri, undefined, token); + } + + async $postMessage(handle: number, value: any): Promise { + + const activeEditorPane = this.editorService.activeEditorPane as any | undefined; + if (activeEditorPane?.isNotebookEditor) { + const notebookEditor = (activeEditorPane as INotebookEditor); + + if (notebookEditor.viewModel?.handle === handle) { + notebookEditor.postMessage(value); + return true; + } + } + + return false; + } +} + +export class MainThreadNotebookController implements IMainNotebookController { + private _mapping: Map = new Map(); + + constructor( + private readonly _proxy: ExtHostNotebookShape, + private _mainThreadNotebook: MainThreadNotebooks, + private _viewType: string + ) { + } + + async resolveNotebook(viewType: string, uri: URI): Promise { + // TODO: resolve notebook should wait for all notebook document destory operations to finish. + let mainthreadNotebook = this._mapping.get(URI.from(uri).toString()); + + if (mainthreadNotebook) { + return mainthreadNotebook.textModel; + } + + let notebookHandle = await this._mainThreadNotebook.resolveNotebook(viewType, uri); + if (notebookHandle !== undefined) { + mainthreadNotebook = this._mapping.get(URI.from(uri).toString()); + if (mainthreadNotebook && mainthreadNotebook.textModel.cells.length === 0) { + // it's empty, we should create an empty template one + const mainCell = mainthreadNotebook.textModel.createCellTextModel([''], mainthreadNotebook.textModel.languages.length ? mainthreadNotebook.textModel.languages[0] : '', CellKind.Code, [], undefined); + mainthreadNotebook.textModel.insertTemplateCell(mainCell); + } + return mainthreadNotebook?.textModel; + } + + return undefined; + } + + async tryApplyEdits(resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise { + let mainthreadNotebook = this._mapping.get(URI.from(resource).toString()); + + if (mainthreadNotebook) { + mainthreadNotebook.updateRenderers(renderers); + return mainthreadNotebook.applyEdit(modelVersionId, edits); + } + + return false; + } + + spliceNotebookCellOutputs(resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): void { + let mainthreadNotebook = this._mapping.get(URI.from(resource).toString()); + mainthreadNotebook?.textModel.updateRenderers(renderers); + mainthreadNotebook?.textModel.$spliceNotebookCellOutputs(cellHandle, splices); + } + + async executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise { + this._mainThreadNotebook.executeNotebook(viewType, uri, token); + } + + onDidReceiveMessage(uri: UriComponents, message: any): void { + this._proxy.$onDidReceiveMessage(uri, message); + } + + // Methods for ExtHost + async createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise { + let document = new MainThreadNotebookDocument(this._proxy, handle, viewType, URI.revive(resource)); + this._mapping.set(URI.revive(resource).toString(), document); + } + + updateLanguages(resource: UriComponents, languages: string[]) { + let document = this._mapping.get(URI.from(resource).toString()); + document?.textModel.updateLanguages(languages); + } + + updateNotebookMetadata(resource: UriComponents, metadata: NotebookDocumentMetadata) { + let document = this._mapping.get(URI.from(resource).toString()); + document?.textModel.updateNotebookMetadata(metadata); + } + + updateNotebookCellMetadata(resource: UriComponents, handle: number, metadata: NotebookCellMetadata) { + let document = this._mapping.get(URI.from(resource).toString()); + document?.textModel.updateNotebookCellMetadata(handle, metadata); + } + + updateNotebookRenderers(resource: UriComponents, renderers: number[]): void { + let document = this._mapping.get(URI.from(resource).toString()); + document?.textModel.updateRenderers(renderers); + } + + async executeNotebookCell(uri: URI, handle: number, token: CancellationToken): Promise { + return this._proxy.$executeNotebook(this._viewType, uri, handle, token); + } + + async destoryNotebookDocument(notebook: INotebookTextModel): Promise { + let document = this._mapping.get(URI.from(notebook.uri).toString()); + + if (!document) { + return; + } + + let removeFromExtHost = await this._proxy.$destoryNotebookDocument(this._viewType, notebook.uri); + if (removeFromExtHost) { + document.dispose(); + this._mapping.delete(URI.from(notebook.uri).toString()); + } + } + + async save(uri: URI): Promise { + return this._proxy.$saveNotebook(this._viewType, uri); + } +} diff --git a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts index ce7aa7a7b53..ba5c1708f26 100644 --- a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts @@ -9,7 +9,7 @@ import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProgressStep, IProgress } from 'vs/platform/progress/common/progress'; import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { ITextFileSaveParticipant, IResolvedTextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileSaveParticipant, ITextFileService, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { SaveReason } from 'vs/workbench/common/editor'; import { ExtHostContext, ExtHostDocumentSaveParticipantShape, IExtHostContext } from '../common/extHost.protocol'; import { canceled } from 'vs/base/common/errors'; @@ -23,9 +23,9 @@ class ExtHostSaveParticipant implements ITextFileSaveParticipant { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentSaveParticipant); } - async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }, _progress: IProgress, token: CancellationToken): Promise { + async participate(editorModel: ITextFileEditorModel, env: { reason: SaveReason; }, _progress: IProgress, token: CancellationToken): Promise { - if (!shouldSynchronizeModel(editorModel.textEditorModel)) { + if (!editorModel.textEditorModel || !shouldSynchronizeModel(editorModel.textEditorModel)) { // the model never made it to the extension // host meaning we cannot participate in its save return undefined; diff --git a/src/vs/workbench/api/browser/mainThreadStatusBar.ts b/src/vs/workbench/api/browser/mainThreadStatusBar.ts index 91a9ba1419f..fb733a38228 100644 --- a/src/vs/workbench/api/browser/mainThreadStatusBar.ts +++ b/src/vs/workbench/api/browser/mainThreadStatusBar.ts @@ -8,6 +8,7 @@ import { MainThreadStatusBarShape, MainContext, IExtHostContext } from '../commo import { ThemeColor } from 'vs/platform/theme/common/themeService'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { dispose } from 'vs/base/common/lifecycle'; +import { Command } from 'vs/editor/common/modes'; @extHostNamedCustomer(MainContext.MainThreadStatusBar) export class MainThreadStatusBar implements MainThreadStatusBarShape { @@ -24,7 +25,7 @@ export class MainThreadStatusBar implements MainThreadStatusBarShape { this.entries.clear(); } - $setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: string | undefined, color: string | ThemeColor | undefined, alignment: MainThreadStatusBarAlignment, priority: number | undefined): void { + $setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: Command | undefined, color: string | ThemeColor | undefined, alignment: MainThreadStatusBarAlignment, priority: number | undefined): void { const entry: IStatusbarEntry = { text, tooltip, command, color }; if (typeof priority === 'undefined') { diff --git a/src/vs/workbench/api/browser/mainThreadTask.ts b/src/vs/workbench/api/browser/mainThreadTask.ts index ede1ae5c393..30fd88fcb13 100644 --- a/src/vs/workbench/api/browser/mainThreadTask.ts +++ b/src/vs/workbench/api/browser/mainThreadTask.ts @@ -33,6 +33,7 @@ import { RunOptionsDTO } from 'vs/workbench/api/common/shared/tasks'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; namespace TaskExecutionDTO { export function from(value: TaskExecution): TaskExecutionDTO { @@ -604,7 +605,7 @@ export class MainThreadTask implements MainThreadTaskShape { return URI.parse(`${info.scheme}://${info.authority}${path}`); }, context: this._extHostContext, - resolveVariables: (workspaceFolder: IWorkspaceFolder, toResolve: ResolveSet): Promise => { + resolveVariables: (workspaceFolder: IWorkspaceFolder, toResolve: ResolveSet, target: ConfigurationTarget): Promise => { const vars: string[] = []; toResolve.variables.forEach(item => vars.push(item)); return Promise.resolve(this._proxy.$resolveVariables(workspaceFolder.uri, { process: toResolve.process, variables: vars })).then(values => { @@ -613,7 +614,7 @@ export class MainThreadTask implements MainThreadTaskShape { partiallyResolvedVars.push(entry.value); }); return new Promise((resolve, reject) => { - this._configurationResolverService.resolveWithInteraction(workspaceFolder, partiallyResolvedVars, 'tasks').then(resolvedVars => { + this._configurationResolverService.resolveWithInteraction(workspaceFolder, partiallyResolvedVars, 'tasks', undefined, target).then(resolvedVars => { const result: ResolvedVariables = { process: undefined, variables: new Map() diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index 372db8a6af2..54b299991ec 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -3,16 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DisposableStore, Disposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IShellLaunchConfig, ITerminalProcessExtHostProxy, ISpawnExtHostProcessRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY, IAvailableShellsRequest, IDefaultShellAndArgsRequest, IStartExtensionTerminalRequest } from 'vs/workbench/contrib/terminal/common/terminal'; import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, IShellLaunchConfigDto, TerminalLaunchConfig, ITerminalDimensionsDto } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { URI } from 'vs/base/common/uri'; import { StopWatch } from 'vs/base/common/stopwatch'; -import { ITerminalInstanceService, ITerminalService, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalInstanceService, ITerminalService, ITerminalInstance, ITerminalBeforeHandleLinkEvent } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering'; +import { IEnvironmentVariableService, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; +import { deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared'; @extHostNamedCustomer(MainContext.MainThreadTerminalService) export class MainThreadTerminalService implements MainThreadTerminalServiceShape { @@ -23,13 +25,15 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape private readonly _terminalProcesses = new Map>(); private readonly _terminalProcessesReady = new Map void>(); private _dataEventTracker: TerminalDataEventTracker | undefined; + private _linkHandler: IDisposable | undefined; constructor( extHostContext: IExtHostContext, @ITerminalService private readonly _terminalService: ITerminalService, @ITerminalInstanceService readonly terminalInstanceService: ITerminalInstanceService, - @IRemoteAgentService readonly _remoteAgentService: IRemoteAgentService, + @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IEnvironmentVariableService private readonly _environmentVariableService: IEnvironmentVariableService, ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTerminalService); this._remoteAuthority = extHostContext.remoteAuthority; @@ -70,6 +74,13 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape if (activeInstance) { this._proxy.$acceptActiveTerminalChanged(activeInstance.id); } + if (this._environmentVariableService.collections.size > 0) { + const collectionAsArray = [...this._environmentVariableService.collections.entries()]; + const serializedCollections: [string, ISerializableEnvironmentVariableCollection][] = collectionAsArray.map(e => { + return [e[0], serializeEnvironmentVariableCollection(e[1].map)]; + }); + this._proxy.$initEnvironmentVariableCollections(serializedCollections); + } this._terminalService.extHostReady(extHostContext.remoteAuthority); } @@ -146,6 +157,22 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } } + public $startHandlingLinks(): void { + this._linkHandler?.dispose(); + this._linkHandler = this._terminalService.addLinkHandler(this._remoteAuthority || '', e => this._handleLink(e)); + } + + public $stopHandlingLinks(): void { + this._linkHandler?.dispose(); + } + + private async _handleLink(e: ITerminalBeforeHandleLinkEvent): Promise { + if (!e.terminal) { + return false; + } + return this._proxy.$handleLink(e.terminal.id, e.link); + } + private _onActiveTerminalChanged(terminalId: number | null): void { this._proxy.$acceptActiveTerminalChanged(terminalId); } @@ -329,6 +356,18 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } return terminal; } + + $setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: ISerializableEnvironmentVariableCollection | undefined): void { + if (collection) { + const translatedCollection = { + persistent, + map: deserializeEnvironmentVariableCollection(collection) + }; + this._environmentVariableService.set(extensionIdentifier, translatedCollection); + } else { + this._environmentVariableService.delete(extensionIdentifier); + } + } } /** diff --git a/src/vs/workbench/api/browser/mainThreadTheming.ts b/src/vs/workbench/api/browser/mainThreadTheming.ts index 3caaa560e30..4f0fa417240 100644 --- a/src/vs/workbench/api/browser/mainThreadTheming.ts +++ b/src/vs/workbench/api/browser/mainThreadTheming.ts @@ -22,8 +22,8 @@ export class MainThreadTheming implements MainThreadThemingShape { this._themeService = themeService; this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTheming); - this._themeChangeListener = this._themeService.onThemeChange(e => { - this._proxy.$onColorThemeChange(this._themeService.getTheme().type); + this._themeChangeListener = this._themeService.onDidColorThemeChange(e => { + this._proxy.$onColorThemeChange(this._themeService.getColorTheme().type); }); } diff --git a/src/vs/workbench/api/browser/mainThreadTimeline.ts b/src/vs/workbench/api/browser/mainThreadTimeline.ts index cfeb1c6c38c..c8d6d0a9a96 100644 --- a/src/vs/workbench/api/browser/mainThreadTimeline.ts +++ b/src/vs/workbench/api/browser/mainThreadTimeline.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; import { MainContext, MainThreadTimelineShape, IExtHostContext, ExtHostTimelineShape, ExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, ITimelineService } from 'vs/workbench/contrib/timeline/common/timeline'; +import { TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, ITimelineService, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline'; @extHostNamedCustomer(MainContext.MainThreadTimeline) export class MainThreadTimeline implements MainThreadTimelineShape { @@ -39,7 +39,7 @@ export class MainThreadTimeline implements MainThreadTimelineShape { this._timelineService.registerTimelineProvider({ ...provider, onDidChange: onDidChange.event, - provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean }) { + provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: InternalTimelineOptions) { return proxy.$getTimeline(provider.id, uri, options, token, internalOptions); }, dispose() { diff --git a/src/vs/workbench/api/browser/mainThreadTunnelService.ts b/src/vs/workbench/api/browser/mainThreadTunnelService.ts index cc55545aea2..2030fe21736 100644 --- a/src/vs/workbench/api/browser/mainThreadTunnelService.ts +++ b/src/vs/workbench/api/browser/mainThreadTunnelService.ts @@ -6,7 +6,7 @@ import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostContext, ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { IRemoteExplorerService, MakeAddress } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { ITunnelProvider, ITunnelService, TunnelOptions } from 'vs/platform/remote/common/tunnel'; import { Disposable } from 'vs/base/common/lifecycle'; import type { TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver'; @@ -51,6 +51,10 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun this.remoteExplorerService.registerCandidateFinder(() => this._proxy.$findCandidatePorts()); } + async $tunnelServiceReady(): Promise { + return this.remoteExplorerService.restore(); + } + async $setTunnelProvider(): Promise { const tunnelProvider: ITunnelProvider = { forwardPort: (tunnelOptions: TunnelOptions) => { @@ -60,9 +64,12 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun return { tunnelRemotePort: tunnel.remoteAddress.port, tunnelRemoteHost: tunnel.remoteAddress.host, - localAddress: tunnel.localAddress, - dispose: () => { - this._proxy.$closeTunnel({ host: tunnel.remoteAddress.host, port: tunnel.remoteAddress.port }); + localAddress: typeof tunnel.localAddress === 'string' ? tunnel.localAddress : MakeAddress(tunnel.localAddress.host, tunnel.localAddress.port), + tunnelLocalPort: typeof tunnel.localAddress !== 'string' ? tunnel.localAddress.port : undefined, + dispose: (silent: boolean) => { + if (!silent) { + this._proxy.$closeTunnel({ host: tunnel.remoteAddress.host, port: tunnel.remoteAddress.port }); + } } }; }); diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index dda6ad071d9..27d699503e9 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -3,32 +3,44 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createCancelablePromise } from 'vs/base/common/async'; +import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, DisposableStore, dispose, IDisposable, IReference } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; +import { basename } from 'vs/base/common/path'; import { isWeb } from 'vs/base/common/platform'; -import { startsWith } from 'vs/base/common/strings'; +import { isEqual, isEqualOrParent } from 'vs/base/common/resources'; +import { escape } from 'vs/base/common/strings'; import { URI, UriComponents } from 'vs/base/common/uri'; import * as modes from 'vs/editor/common/modes'; import { localize } from 'vs/nls'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILabelService } from 'vs/platform/label/common/label'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IProductService } from 'vs/platform/product/common/productService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IUndoRedoService, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo'; import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol'; import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor'; -import { IEditorInput } from 'vs/workbench/common/editor'; +import { IEditorInput, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; -import { CustomEditorInput, ModelType } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; +import { CustomEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; +import { CustomDocumentBackupData } from 'vs/workbench/contrib/customEditor/browser/customEditorInputFactory'; import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; -import { WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview'; +import { CustomTextEditorModel } from 'vs/workbench/contrib/customEditor/common/customTextEditorModel'; +import { WebviewExtensionDescription, WebviewIcons } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; import { ICreateWebViewShowOptions, IWebviewWorkbenchService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +import { IWorkingCopy, IWorkingCopyBackup, IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { extHostNamedCustomer } from '../common/extHostCustomers'; /** @@ -62,6 +74,10 @@ class WebviewInputStore { public get size(): number { return this._handlesToInputs.size; } + + [Symbol.iterator](): Iterator { + return this._handlesToInputs.values(); + } } class WebviewViewTypeTransformer { @@ -74,12 +90,17 @@ class WebviewViewTypeTransformer { } public toExternal(viewType: string): string | undefined { - return startsWith(viewType, this.prefix) + return viewType.startsWith(this.prefix) ? viewType.substr(this.prefix.length) : undefined; } } +const enum ModelType { + Custom, + Text, +} + const webviewPanelViewType = new WebviewViewTypeTransformer('mainThreadWebview-'); @extHostNamedCustomer(extHostProtocol.MainContext.MainThreadWebviews) @@ -97,11 +118,13 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma private readonly _webviewInputs = new WebviewInputStore(); private readonly _revivers = new Map(); private readonly _editorProviders = new Map(); - private readonly _customEditorModels = new Map(); + private readonly _webviewFromDiffEditorHandles = new Set(); constructor( context: extHostProtocol.IExtHostContext, @IExtensionService extensionService: IExtensionService, + @IWorkingCopyService workingCopyService: IWorkingCopyService, + @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService, @ICustomEditorService private readonly _customEditorService: ICustomEditorService, @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, @IEditorService private readonly _editorService: IEditorService, @@ -109,13 +132,24 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma @IProductService private readonly _productService: IProductService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService, - @IFileService private readonly _fileService: IFileService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { super(); this._proxy = context.getProxy(extHostProtocol.ExtHostContext.ExtHostWebviews); - this._register(_editorService.onDidActiveEditorChange(this.updateWebviewViewStates, this)); - this._register(_editorService.onDidVisibleEditorsChange(this.updateWebviewViewStates, this)); + + this._register(_editorService.onDidActiveEditorChange(() => { + const activeInput = this._editorService.activeEditor; + if (activeInput instanceof DiffEditorInput && activeInput.master instanceof WebviewInput && activeInput.details instanceof WebviewInput) { + this.registerWebviewFromDiffEditorListeners(activeInput); + } + + this.updateWebviewViewStates(activeInput); + })); + + this._register(_editorService.onDidVisibleEditorsChange(() => { + this.updateWebviewViewStates(this._editorService.activeEditor); + })); // This reviver's only job is to activate webview panel extensions // This should trigger the real reviver to be registered from the extension host side. @@ -134,6 +168,20 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma }, resolveWebview: () => { throw new Error('not implemented'); } })); + + workingCopyFileService.registerWorkingCopyProvider((editorResource) => { + const matchedWorkingCopies: IWorkingCopy[] = []; + + for (const workingCopy of workingCopyService.workingCopies) { + if (workingCopy instanceof MainThreadCustomEditorModel) { + if (isEqualOrParent(editorResource, workingCopy.editorResource)) { + matchedWorkingCopies.push(workingCopy); + } + } + } + return matchedWorkingCopies; + + }); } public $createWebviewPanel( @@ -219,7 +267,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma resolveWebview: async (webviewInput): Promise => { const viewType = webviewPanelViewType.toExternal(webviewInput.viewType); if (!viewType) { - webviewInput.webview.html = MainThreadWebviews.getDeserializationFailedContents(webviewInput.viewType); + webviewInput.webview.html = MainThreadWebviews.getWebviewResolvedFailedContent(webviewInput.viewType); return; } @@ -240,7 +288,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma await this._proxy.$deserializeWebviewPanel(handle, viewType, webviewInput.getTitle(), state, editorGroupToViewColumn(this._editorGroupService, webviewInput.group || 0), webviewInput.webview.options); } catch (error) { onUnexpectedError(error); - webviewInput.webview.html = MainThreadWebviews.getDeserializationFailedContents(viewType); + webviewInput.webview.html = MainThreadWebviews.getWebviewResolvedFailedContent(viewType); } } })); @@ -256,64 +304,82 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma this._revivers.delete(viewType); } - public $registerTextEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void { - return this.registerEditorProvider(ModelType.Text, extensionData, viewType, options); + public $registerTextEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, capabilities: extHostProtocol.CustomTextEditorCapabilities): void { + this.registerEditorProvider(ModelType.Text, extensionData, viewType, options, capabilities); } public $registerCustomEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void { - return this.registerEditorProvider(ModelType.Custom, extensionData, viewType, options); + this.registerEditorProvider(ModelType.Custom, extensionData, viewType, options, {}); } - public registerEditorProvider( + private registerEditorProvider( modelType: ModelType, extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, - ): void { + capabilities: extHostProtocol.CustomTextEditorCapabilities, + ): DisposableStore { if (this._editorProviders.has(viewType)) { throw new Error(`Provider for ${viewType} already registered`); } const extension = reviveWebviewExtension(extensionData); - this._editorProviders.set(viewType, this._webviewWorkbenchService.registerResolver({ + const disposables = new DisposableStore(); + disposables.add(this._webviewWorkbenchService.registerResolver({ canResolve: (webviewInput) => { return webviewInput instanceof CustomEditorInput && webviewInput.viewType === viewType; }, - resolveWebview: async (webviewInput: CustomEditorInput) => { + resolveWebview: async (webviewInput: CustomEditorInput, cancellation: CancellationToken) => { const handle = webviewInput.id; - this._webviewInputs.add(handle, webviewInput); - this.hookupWebviewEventDelegate(handle, webviewInput); - - webviewInput.webview.options = options; - webviewInput.webview.extension = extension; - webviewInput.modelType = modelType; - const resource = webviewInput.resource; - if (modelType === ModelType.Custom) { - const model = await this.retainCustomEditorModel(webviewInput, resource, viewType); - webviewInput.onDisposeWebview(() => { - this.releaseCustomEditorModel(model); + this._webviewInputs.add(handle, webviewInput); + this.hookupWebviewEventDelegate(handle, webviewInput); + webviewInput.webview.options = options; + webviewInput.webview.extension = extension; + + let modelRef: IReference; + try { + modelRef = await this.getOrCreateCustomEditorModel(modelType, resource, viewType, cancellation); + } catch (error) { + onUnexpectedError(error); + webviewInput.webview.html = MainThreadWebviews.getWebviewResolvedFailedContent(viewType); + return; + } + + if (cancellation.isCancellationRequested) { + modelRef.dispose(); + return; + } + + webviewInput.webview.onDispose(() => { + modelRef.dispose(); + }); + + if (capabilities.supportsMove) { + webviewInput.onMove(async (newResource: URI) => { + const oldModel = modelRef; + modelRef = await this.getOrCreateCustomEditorModel(modelType, newResource, viewType, CancellationToken.None); + this._proxy.$onMoveCustomEditor(handle, newResource, viewType); + oldModel.dispose(); }); } try { - await this._proxy.$resolveWebviewEditor( - resource, - handle, - viewType, - webviewInput.getTitle(), - editorGroupToViewColumn(this._editorGroupService, webviewInput.group || 0), - webviewInput.webview.options - ); + await this._proxy.$resolveWebviewEditor(resource, handle, viewType, webviewInput.getTitle(), editorGroupToViewColumn(this._editorGroupService, webviewInput.group || 0), webviewInput.webview.options, cancellation); } catch (error) { onUnexpectedError(error); - webviewInput.webview.html = MainThreadWebviews.getDeserializationFailedContents(viewType); + webviewInput.webview.html = MainThreadWebviews.getWebviewResolvedFailedContent(viewType); + modelRef.dispose(); return; } } })); + + this._editorProviders.set(viewType, disposables); + + return disposables; } public $unregisterEditorProvider(viewType: string): void { @@ -328,74 +394,35 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma this._customEditorService.models.disposeAllModelsForView(viewType); } - private async retainCustomEditorModel(webviewInput: WebviewInput, resource: URI, viewType: string) { - const model = await this._customEditorService.models.resolve(webviewInput.resource, webviewInput.viewType); - - const key = viewType + resource.toString(); - const existingEntry = this._customEditorModels.get(key); - if (existingEntry) { - ++existingEntry.referenceCount; - // no need to hook up listeners again - return model; - } - this._customEditorModels.set(key, { referenceCount: 1 }); - const { editable } = await this._proxy.$createWebviewCustomEditorDocument(resource, viewType); - - if (editable) { - model.onUndo(() => { - this._proxy.$undo(resource, viewType); - }); - - model.onRedo(() => { - this._proxy.$redo(resource, viewType); - }); - - model.onWillSave(e => { - e.waitUntil(this._proxy.$onSave(resource.toJSON(), viewType)); - }); + private async getOrCreateCustomEditorModel( + modelType: ModelType, + resource: URI, + viewType: string, + cancellation: CancellationToken, + ): Promise> { + const existingModel = this._customEditorService.models.tryRetain(resource, viewType); + if (existingModel) { + return existingModel; } - // Save as should always be implemented even if the model is readonly - model.onWillSaveAs(e => { - if (editable) { - e.waitUntil(this._proxy.$onSaveAs(e.resource.toJSON(), viewType, e.targetResource.toJSON())); - } else { - // Since the editor is readonly, just copy the file over - e.waitUntil(this._fileService.copy(e.resource, e.targetResource, false /* overwrite */)); - } - }); + const model = modelType === ModelType.Text + ? CustomTextEditorModel.create(this._instantiationService, viewType, resource) + : MainThreadCustomEditorModel.create(this._instantiationService, this._proxy, viewType, resource, () => { + return Array.from(this._webviewInputs) + .filter(editor => editor instanceof CustomEditorInput && isEqual(editor.resource, resource)) as CustomEditorInput[]; + }, cancellation); - model.onBackup(() => { - return createCancelablePromise(token => - this._proxy.$backup(model.resource.toJSON(), viewType, token)); - }); - - return model; + return this._customEditorService.models.add(resource, viewType, model); } - private async releaseCustomEditorModel(model: ICustomEditorModel) { - const key = model.viewType + model.resource; - const entry = this._customEditorModels.get(key); - if (!entry) { - throw new Error('Model not found'); - } - - --entry.referenceCount; - if (entry.referenceCount <= 0) { - this._proxy.$disposeWebviewCustomEditorDocument(model.resource, model.viewType); - this._customEditorService.models.disposeModel(model); - this._customEditorModels.delete(key); - } - } - - - - public $onDidChangeCustomDocumentState(resource: UriComponents, viewType: string, state: { dirty: boolean }) { - const model = this._customEditorService.models.get(URI.revive(resource), viewType); - if (!model) { + public async $onDidEdit(resourceComponents: UriComponents, viewType: string, editId: number, label: string | undefined): Promise { + const resource = URI.revive(resourceComponents); + const model = await this._customEditorService.models.get(resource, viewType); + if (!model || !(model instanceof MainThreadCustomEditorModel)) { throw new Error('Could not find model for webview editor'); } - model.setDirty(state.dirty); + + model.pushEdit(editId, label); } private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewPanelHandle, input: WebviewInput) { @@ -405,22 +432,41 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma disposables.add(input.webview.onMessage((message: any) => { this._proxy.$onMessage(handle, message); })); disposables.add(input.webview.onMissingCsp((extension: ExtensionIdentifier) => this._proxy.$onMissingCsp(handle, extension.value))); - input.onDispose(() => { + disposables.add(input.webview.onDispose(() => { disposables.dispose(); - }); - input.onDisposeWebview(() => { + this._proxy.$onDidDisposeWebviewPanel(handle).finally(() => { this._webviewInputs.delete(handle); }); - }); + })); } - private updateWebviewViewStates() { + private registerWebviewFromDiffEditorListeners(diffEditorInput: DiffEditorInput): void { + const master = diffEditorInput.master as WebviewInput; + const details = diffEditorInput.details as WebviewInput; + + if (this._webviewFromDiffEditorHandles.has(master.id) || this._webviewFromDiffEditorHandles.has(details.id)) { + return; + } + + this._webviewFromDiffEditorHandles.add(master.id); + this._webviewFromDiffEditorHandles.add(details.id); + + const disposables = new DisposableStore(); + disposables.add(master.webview.onDidFocus(() => this.updateWebviewViewStates(master))); + disposables.add(details.webview.onDidFocus(() => this.updateWebviewViewStates(details))); + disposables.add(diffEditorInput.onDispose(() => { + this._webviewFromDiffEditorHandles.delete(master.id); + this._webviewFromDiffEditorHandles.delete(details.id); + dispose(disposables); + })); + } + + private updateWebviewViewStates(activeEditorInput: IEditorInput | undefined) { if (!this._webviewInputs.size) { return; } - const activeInput = this._editorService.activeControl && this._editorService.activeControl.input; const viewStates: extHostProtocol.WebviewPanelViewStateData = {}; const updateViewStatesForInput = (group: IEditorGroup, topLevelInput: IEditorInput, editorInput: IEditorInput) => { @@ -434,7 +480,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma if (handle) { viewStates[handle] = { visible: topLevelInput === group.activeEditor, - active: topLevelInput === activeInput, + active: editorInput === activeEditorInput, position: editorGroupToViewColumn(this._editorGroupService, group.id), }; } @@ -476,7 +522,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma private getWebviewInput(handle: extHostProtocol.WebviewPanelHandle): WebviewInput { const webview = this.tryGetWebviewInput(handle); if (!webview) { - throw new Error('Unknown webview handle:' + handle); + throw new Error(`Unknown webview handle:${handle}`); } return webview; } @@ -485,14 +531,14 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma return this._webviewInputs.getInputForHandle(handle); } - private static getDeserializationFailedContents(viewType: string) { + private static getWebviewResolvedFailedContent(viewType: string) { return ` - ${localize('errorMessage', "An error occurred while restoring view:{0}", viewType)} + ${localize('errorMessage', "An error occurred while loading view: {0}", escape(viewType))} `; } } @@ -511,13 +557,329 @@ function reviveWebviewOptions(options: modes.IWebviewOptions): WebviewInputOptio function reviveWebviewIcon( value: { light: UriComponents, dark: UriComponents; } | undefined -): { light: URI, dark: URI; } | undefined { - if (!value) { - return undefined; +): WebviewIcons | undefined { + return value + ? { light: URI.revive(value.light), dark: URI.revive(value.dark) } + : undefined; +} + +namespace HotExitState { + export const enum Type { + Allowed, + NotAllowed, + Pending, } - return { - light: URI.revive(value.light), - dark: URI.revive(value.dark) - }; + export const Allowed = Object.freeze({ type: Type.Allowed } as const); + export const NotAllowed = Object.freeze({ type: Type.NotAllowed } as const); + + export class Pending { + readonly type = Type.Pending; + + constructor( + public readonly operation: CancelablePromise, + ) { } + } + + export type State = typeof Allowed | typeof NotAllowed | Pending; +} + + +class MainThreadCustomEditorModel extends Disposable implements ICustomEditorModel, IWorkingCopy { + + private _hotExitState: HotExitState.State = HotExitState.Allowed; + private _currentEditIndex: number = -1; + private _savePoint: number = -1; + private readonly _edits: Array = []; + private _fromBackup: boolean = false; + + public static async create( + instantiationService: IInstantiationService, + proxy: extHostProtocol.ExtHostWebviewsShape, + viewType: string, + resource: URI, + getEditors: () => CustomEditorInput[], + cancellation: CancellationToken, + ) { + const { editable } = await proxy.$createWebviewCustomEditorDocument(resource, viewType, cancellation); + const model = instantiationService.createInstance(MainThreadCustomEditorModel, proxy, viewType, resource, editable, getEditors); + await model.init(); + return model; + } + + constructor( + private readonly _proxy: extHostProtocol.ExtHostWebviewsShape, + private readonly _viewType: string, + private readonly _editorResource: URI, + private readonly _editable: boolean, + private readonly _getEditors: () => CustomEditorInput[], + @IWorkingCopyService workingCopyService: IWorkingCopyService, + @ILabelService private readonly _labelService: ILabelService, + @IFileService private readonly _fileService: IFileService, + @IUndoRedoService private readonly _undoService: IUndoRedoService, + @IBackupFileService private readonly _backupFileService: IBackupFileService, + ) { + super(); + + if (_editable) { + this._register(workingCopyService.registerWorkingCopy(this)); + } + } + + get editorResource() { + return this._editorResource; + } + + async init(): Promise { + const backup = await this._backupFileService.resolve(this.resource); + this._fromBackup = !!backup; + } + + dispose() { + if (this._editable) { + this._undoService.removeElements(this._editorResource); + } + this._proxy.$disposeWebviewCustomEditorDocument(this._editorResource, this._viewType); + super.dispose(); + } + + //#region IWorkingCopy + + public get resource() { + // Make sure each custom editor has a unique resource for backup and edits + return URI.from({ + scheme: Schemas.vscodeCustomEditor, + authority: this._viewType, + path: this._editorResource.path, + query: JSON.stringify(this._editorResource.toJSON()), + }); + } + + public get name() { + return basename(this._labelService.getUriLabel(this._editorResource)); + } + + public get capabilities(): WorkingCopyCapabilities { + return 0; + } + + public isDirty(): boolean { + if (this._edits.length > 0) { + return this._savePoint !== this._currentEditIndex; + } + return this._fromBackup; + } + + private readonly _onDidChangeDirty: Emitter = this._register(new Emitter()); + readonly onDidChangeDirty: Event = this._onDidChangeDirty.event; + + private readonly _onDidChangeContent: Emitter = this._register(new Emitter()); + readonly onDidChangeContent: Event = this._onDidChangeContent.event; + + //#endregion + + public isReadonly() { + return this._editable; + } + + public get viewType() { + return this._viewType; + } + + public pushEdit(editId: number, label: string | undefined) { + if (!this._editable) { + throw new Error('Document is not editable'); + } + + this.change(() => { + this.spliceEdits(editId); + this._currentEditIndex = this._edits.length - 1; + }); + + this._undoService.pushElement({ + type: UndoRedoElementType.Resource, + resource: this._editorResource, + label: label ?? localize('defaultEditLabel', "Edit"), + undo: () => this.undo(), + redo: () => this.redo(), + }); + } + + private async undo(): Promise { + if (!this._editable) { + return; + } + + if (this._currentEditIndex < 0) { + // nothing to undo + return; + } + + const undoneEdit = this._edits[this._currentEditIndex]; + this.change(() => { + --this._currentEditIndex; + }); + await this._proxy.$undo(this._editorResource, this.viewType, undoneEdit, this.getEditState()); + } + + private getEditState(): extHostProtocol.CustomDocumentEditState { + return { + allEdits: this._edits, + currentIndex: this._currentEditIndex, + saveIndex: this._savePoint, + }; + } + + private async redo(): Promise { + if (!this._editable) { + return; + } + + if (this._currentEditIndex >= this._edits.length - 1) { + // nothing to redo + return; + } + + const redoneEdit = this._edits[this._currentEditIndex + 1]; + this.change(() => { + ++this._currentEditIndex; + }); + await this._proxy.$redo(this._editorResource, this.viewType, redoneEdit, this.getEditState()); + } + + private spliceEdits(editToInsert?: number) { + const start = this._currentEditIndex + 1; + const toRemove = this._edits.length - this._currentEditIndex; + + const removedEdits = typeof editToInsert === 'number' + ? this._edits.splice(start, toRemove, editToInsert) + : this._edits.splice(start, toRemove); + + if (removedEdits.length) { + this._proxy.$disposeEdits(this._editorResource, this._viewType, removedEdits); + } + } + + private change(makeEdit: () => void): void { + const wasDirty = this.isDirty(); + makeEdit(); + this._onDidChangeContent.fire(); + + if (this.isDirty() !== wasDirty) { + this._onDidChangeDirty.fire(); + } + } + + public async revert(_options?: IRevertOptions) { + if (!this._editable) { + return; + } + + if (this._currentEditIndex === this._savePoint) { + return; + } + + let editsToUndo: number[] = []; + let editsToRedo: number[] = []; + + if (this._currentEditIndex >= this._savePoint) { + editsToUndo = this._edits.slice(this._savePoint, this._currentEditIndex).reverse(); + } else if (this._currentEditIndex < this._savePoint) { + editsToRedo = this._edits.slice(this._currentEditIndex, this._savePoint); + } + + this._proxy.$revert(this._editorResource, this.viewType, { undoneEdits: editsToUndo, redoneEdits: editsToRedo }, this.getEditState()); + this.change(() => { + this._currentEditIndex = this._savePoint; + this.spliceEdits(); + }); + } + + public async save(options?: ISaveOptions): Promise { + return !!await this.saveCustomEditor(options); + } + + public async saveCustomEditor(_options?: ISaveOptions): Promise { + if (!this._editable) { + return undefined; + } + // TODO: handle save untitled case + // TODO: handle cancellation + await createCancelablePromise(token => this._proxy.$onSave(this._editorResource, this.viewType, token)); + this.change(() => { + this._savePoint = this._currentEditIndex; + }); + return this._editorResource; + } + + public async saveCustomEditorAs(resource: URI, targetResource: URI, _options?: ISaveOptions): Promise { + if (this._editable) { + // TODO: handle cancellation + await createCancelablePromise(token => this._proxy.$onSaveAs(this._editorResource, this.viewType, targetResource, token)); + this.change(() => { + this._savePoint = this._currentEditIndex; + }); + return true; + } else { + // Since the editor is readonly, just copy the file over + await this._fileService.copy(resource, targetResource, false /* overwrite */); + return true; + } + } + + public async backup(): Promise { + const editors = this._getEditors(); + if (!editors.length) { + throw new Error('No editors found for resource, cannot back up'); + } + const primaryEditor = editors[0]; + + const backupData: IWorkingCopyBackup = { + meta: { + viewType: this.viewType, + editorResource: this._editorResource, + extension: primaryEditor.extension ? { + id: primaryEditor.extension.id.value, + location: primaryEditor.extension.location, + } : undefined, + webview: { + id: primaryEditor.id, + options: primaryEditor.webview.options, + state: primaryEditor.webview.state, + } + } + }; + + if (!this._editable) { + return backupData; + } + + if (this._hotExitState.type === HotExitState.Type.Pending) { + this._hotExitState.operation.cancel(); + } + + const pendingState = new HotExitState.Pending( + createCancelablePromise(token => + this._proxy.$backup(this._editorResource.toJSON(), this.viewType, token))); + this._hotExitState = pendingState; + + try { + await pendingState.operation; + // Make sure state has not changed in the meantime + if (this._hotExitState === pendingState) { + this._hotExitState = HotExitState.Allowed; + } + } catch (e) { + // Make sure state has not changed in the meantime + if (this._hotExitState === pendingState) { + this._hotExitState = HotExitState.NotAllowed; + } + } + + if (this._hotExitState === HotExitState.Allowed) { + return backupData; + } + + throw new Error('Cannot back up in this state'); + } } diff --git a/src/vs/workbench/api/browser/mainThreadWorkspace.ts b/src/vs/workbench/api/browser/mainThreadWorkspace.ts index 7d67852018a..bf9dc7b1088 100644 --- a/src/vs/workbench/api/browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/browser/mainThreadWorkspace.ts @@ -12,7 +12,7 @@ import { isNative } from 'vs/base/common/platform'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { IFileMatch, IPatternInfo, ISearchProgressItem, ISearchService } from 'vs/workbench/services/search/common/search'; -import { IWorkspaceContextService, WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, WorkbenchState, IWorkspace, toWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -138,7 +138,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { } const query = this._queryBuilder.file( - includeFolder ? [includeFolder] : workspace.folders.map(f => f.uri), + includeFolder ? [toWorkspaceFolder(includeFolder)] : workspace.folders, { maxResults: withNullAsUndefined(maxResults), disregardExcludeSettings: (excludePatternOrDisregardExcludes === false) || undefined, @@ -190,7 +190,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { $checkExists(folders: UriComponents[], includes: string[], token: CancellationToken): Promise { const queryBuilder = this._instantiationService.createInstance(QueryBuilder); - const query = queryBuilder.file(folders.map(folder => URI.revive(folder)), { + const query = queryBuilder.file(folders.map(folder => toWorkspaceFolder(URI.revive(folder))), { _reason: 'checkExists', includePattern: includes.join(', '), expandPatterns: true, diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 67e90081285..354fbf83287 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -9,7 +9,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as resources from 'vs/base/common/resources'; import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor, ViewContainerLocation } from 'vs/workbench/common/views'; -import { CustomTreeViewPane, CustomTreeView } from 'vs/workbench/browser/parts/views/customView'; +import { TreeViewPane, CustomTreeView } from 'vs/workbench/browser/parts/views/treeView'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { coalesce, } from 'vs/base/common/arrays'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -64,6 +64,11 @@ export const viewsContainersContribution: IJSONSchema = { description: localize('views.container.activitybar', "Contribute views containers to Activity Bar"), type: 'array', items: viewsContainerSchema + }, + 'panel': { + description: localize('views.container.panel', "Contribute views containers to Panel"), + type: 'array', + items: viewsContainerSchema } } }; @@ -214,7 +219,8 @@ class ViewsExtensionHandler implements IWorkbenchContribution { private addCustomViewContainers(extensionPoints: readonly IExtensionPointUser[], existingViewContainers: ViewContainer[]): void { const viewContainersRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); - let order = TEST_VIEW_CONTAINER_ORDER + viewContainersRegistry.all.filter(v => !!v.extensionId).length + 1; + let activityBarOrder = TEST_VIEW_CONTAINER_ORDER + viewContainersRegistry.all.filter(v => !!v.extensionId && viewContainersRegistry.getViewContainerLocation(v) === ViewContainerLocation.Sidebar).length + 1; + let panelOrder = 5 + viewContainersRegistry.all.filter(v => !!v.extensionId && viewContainersRegistry.getViewContainerLocation(v) === ViewContainerLocation.Panel).length + 1; for (let { value, collector, description } of extensionPoints) { forEach(value, entry => { if (!this.isValidViewsContainer(entry.value, collector)) { @@ -222,7 +228,10 @@ class ViewsExtensionHandler implements IWorkbenchContribution { } switch (entry.key) { case 'activitybar': - order = this.registerCustomViewContainers(entry.value, description, order, existingViewContainers); + activityBarOrder = this.registerCustomViewContainers(entry.value, description, activityBarOrder, existingViewContainers, ViewContainerLocation.Sidebar); + break; + case 'panel': + panelOrder = this.registerCustomViewContainers(entry.value, description, panelOrder, existingViewContainers, ViewContainerLocation.Panel); break; } }); @@ -248,7 +257,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { const title = localize('test', "Test"); const icon = URI.parse(require.toUrl('./media/test.svg')); - this.registerCustomViewContainer(TEST_VIEW_CONTAINER_ID, title, icon, TEST_VIEW_CONTAINER_ORDER, undefined); + this.registerCustomViewContainer(TEST_VIEW_CONTAINER_ID, title, icon, TEST_VIEW_CONTAINER_ORDER, undefined, ViewContainerLocation.Sidebar); } private isValidViewsContainer(viewsContainersDescriptors: IUserFriendlyViewsContainerDescriptor[], collector: ExtensionMessageCollector): boolean { @@ -279,11 +288,11 @@ class ViewsExtensionHandler implements IWorkbenchContribution { return true; } - private registerCustomViewContainers(containers: IUserFriendlyViewsContainerDescriptor[], extension: IExtensionDescription, order: number, existingViewContainers: ViewContainer[]): number { + private registerCustomViewContainers(containers: IUserFriendlyViewsContainerDescriptor[], extension: IExtensionDescription, order: number, existingViewContainers: ViewContainer[], location: ViewContainerLocation): number { containers.forEach(descriptor => { const icon = resources.joinPath(extension.extensionLocation, descriptor.icon); const id = `workbench.view.extension.${descriptor.id}`; - const viewContainer = this.registerCustomViewContainer(id, descriptor.title, icon, order++, extension.identifier); + const viewContainer = this.registerCustomViewContainer(id, descriptor.title, icon, order++, extension.identifier, location); // Move those views that belongs to this container if (existingViewContainers.length) { @@ -301,7 +310,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { return order; } - private registerCustomViewContainer(id: string, title: string, icon: URI, order: number, extensionId: ExtensionIdentifier | undefined): ViewContainer { + private registerCustomViewContainer(id: string, title: string, icon: URI, order: number, extensionId: ExtensionIdentifier | undefined, location: ViewContainerLocation): ViewContainer { let viewContainer = this.viewContainersRegistry.get(id); if (!viewContainer) { @@ -316,7 +325,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { hideIfEmpty: true, order, icon, - }, ViewContainerLocation.Sidebar); + }, location); // Register Action to Open Viewlet class OpenCustomViewletAction extends ShowViewletAction { @@ -396,7 +405,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { const viewDescriptor = { id: item.id, name: item.name, - ctorDescriptor: new SyncDescriptor(CustomTreeViewPane), + ctorDescriptor: new SyncDescriptor(TreeViewPane), when: ContextKeyExpr.deserialize(item.when), canToggleVisibility: true, canMoveView: true, diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index 9d29edb15f1..d57364a016c 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -43,7 +43,7 @@ const configurationEntrySchema: IJSONSchema = { default: 'window', enumDescriptions: [ nls.localize('scope.application.description', "Configuration that can be configured only in the user settings."), - nls.localize('scope.machine.description', "Configuration that can be configured only in the user settings when the extension is running locally, or only in the remote settings when the extension is running remotely."), + nls.localize('scope.machine.description', "Configuration that can be configured only in the user settings or only in the remote settings."), nls.localize('scope.window.description', "Configuration that can be configured in the user, remote or workspace settings."), nls.localize('scope.resource.description', "Configuration that can be configured in the user, remote, workspace or folder settings."), nls.localize('scope.language-overridable.description', "Resource configuration that can be configured in language specific settings."), diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index cb57cc82270..224e77efd8b 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -67,6 +67,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; import { ExtHostTheming } from 'vs/workbench/api/common/extHostTheming'; import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; @@ -129,7 +130,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostComment = rpcProtocol.set(ExtHostContext.ExtHostComments, new ExtHostComments(rpcProtocol, extHostCommands, extHostDocuments)); const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol)); const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress))); - const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHostLabelService, new ExtHostLabelService(rpcProtocol)); + const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHosLabelService, new ExtHostLabelService(rpcProtocol)); + const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors)); const extHostTheming = rpcProtocol.set(ExtHostContext.ExtHostTheming, new ExtHostTheming(rpcProtocol)); const extHostAuthentication = rpcProtocol.set(ExtHostContext.ExtHostAuthentication, new ExtHostAuthentication(rpcProtocol)); const extHostTimeline = rpcProtocol.set(ExtHostContext.ExtHostTimeline, new ExtHostTimeline(rpcProtocol, extHostCommands)); @@ -143,7 +145,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostClipboard = new ExtHostClipboard(rpcProtocol); const extHostMessageService = new ExtHostMessageService(rpcProtocol, extHostLogService); const extHostDialogs = new ExtHostDialogs(rpcProtocol); - const extHostStatusBar = new ExtHostStatusBar(rpcProtocol); + const extHostStatusBar = new ExtHostStatusBar(rpcProtocol, extHostCommands.converter); const extHostLanguages = new ExtHostLanguages(rpcProtocol, extHostDocuments); // Register API-ish commands @@ -185,12 +187,21 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerAuthenticationProvider(provider: vscode.AuthenticationProvider): vscode.Disposable { return extHostAuthentication.registerAuthenticationProvider(provider); }, - get providers() { - return extHostAuthentication.providers(extension); - }, get onDidChangeAuthenticationProviders(): Event { return extHostAuthentication.onDidChangeAuthenticationProviders; - } + }, + get providerIds(): string[] { + return extHostAuthentication.providerIds; + }, + getSessions(providerId: string, scopes: string[]): Thenable { + return extHostAuthentication.getSessions(extension, providerId, scopes); + }, + login(providerId: string, scopes: string[]): Thenable { + return extHostAuthentication.login(extension, providerId, scopes); + }, + get onDidChangeSessions(): Event<{ [providerId: string]: vscode.AuthenticationSessionsChangeEvent }> { + return extHostAuthentication.onDidChangeSessions; + }, }; // namespace: commands @@ -351,6 +362,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerDocumentHighlightProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable { return extHostLanguageFeatures.registerDocumentHighlightProvider(extension, checkSelector(selector), provider); }, + registerOnTypeRenameProvider(selector: vscode.DocumentSelector, provider: vscode.OnTypeRenameProvider, stopPattern?: RegExp): vscode.Disposable { + checkProposedApiEnabled(extension); + return extHostLanguageFeatures.registerOnTypeRenameProvider(extension, checkSelector(selector), provider, stopPattern); + }, registerReferenceProvider(selector: vscode.DocumentSelector, provider: vscode.ReferenceProvider): vscode.Disposable { return extHostLanguageFeatures.registerReferenceProvider(extension, checkSelector(selector), provider); }, @@ -373,11 +388,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostLanguageFeatures.registerOnTypeFormattingEditProvider(extension, checkSelector(selector), provider, [firstTriggerCharacter].concat(moreTriggerCharacters)); }, registerDocumentSemanticTokensProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentSemanticTokensProvider, legend: vscode.SemanticTokensLegend): vscode.Disposable { - checkProposedApiEnabled(extension); return extHostLanguageFeatures.registerDocumentSemanticTokensProvider(extension, checkSelector(selector), provider, legend); }, registerDocumentRangeSemanticTokensProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentRangeSemanticTokensProvider, legend: vscode.SemanticTokensLegend): vscode.Disposable { - checkProposedApiEnabled(extension); return extHostLanguageFeatures.registerDocumentRangeSemanticTokensProvider(extension, checkSelector(selector), provider, legend); }, registerSignatureHelpProvider(selector: vscode.DocumentSelector, provider: vscode.SignatureHelpProvider, firstItem?: string | vscode.SignatureHelpProviderMetadata, ...remaining: string[]): vscode.Disposable { @@ -472,6 +485,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostTerminalService.onDidWriteTerminalData(listener, thisArg, disposables); }, + getEnvironmentVariableCollection(persistent?: boolean): vscode.EnvironmentVariableCollection { + checkProposedApiEnabled(extension); + return extHostTerminalService.getEnvironmentVariableCollection(extension, persistent); + }, get state() { return extHostWindow.state; }, @@ -552,6 +569,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I } return extHostTerminalService.createTerminal(nameOrOptions, shellPath, shellArgs); }, + registerTerminalLinkHandler(handler: vscode.TerminalLinkHandler): vscode.Disposable { + checkProposedApiEnabled(extension); + return extHostTerminalService.registerLinkHandler(handler); + }, registerTreeDataProvider(viewId: string, treeDataProvider: vscode.TreeDataProvider): vscode.Disposable { return extHostTreeViews.registerTreeDataProvider(viewId, treeDataProvider, extension); }, @@ -561,9 +582,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerWebviewPanelSerializer: (viewType: string, serializer: vscode.WebviewPanelSerializer) => { return extHostWebviews.registerWebviewPanelSerializer(extension, viewType, serializer); }, - registerCustomEditorProvider: (viewType: string, provider: vscode.CustomEditorProvider | vscode.CustomTextEditorProvider, options?: vscode.WebviewPanelOptions) => { + registerCustomEditorProvider: (viewType: string, provider: vscode.CustomTextEditorProvider, options?: { webviewOptions?: vscode.WebviewPanelOptions }) => { + return extHostWebviews.registerCustomEditorProvider(extension, viewType, provider, options?.webviewOptions); + }, + registerCustomEditorProvider2: (viewType: string, provider: vscode.CustomEditorProvider, options?: { webviewOptions?: vscode.WebviewPanelOptions }) => { checkProposedApiEnabled(extension); - return extHostWebviews.registerCustomEditorProvider(extension, viewType, provider, options); + return extHostWebviews.registerCustomEditorProvider(extension, viewType, provider, options?.webviewOptions); }, registerDecorationProvider(provider: vscode.DecorationProvider) { checkProposedApiEnabled(extension); @@ -769,11 +793,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostTunnelService.onDidChangeTunnels(listener, thisArg, disposables); - }, - onDidTunnelsChange: (listener, thisArg?, disposables?) => { - checkProposedApiEnabled(extension); - return extHostTunnelService.onDidChangeTunnels(listener, thisArg, disposables); - }, registerTimelineProvider: (scheme: string | string[], provider: vscode.TimelineProvider) => { checkProposedApiEnabled(extension); @@ -881,6 +900,19 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I } }; + // namespace: notebook + const notebook: typeof vscode.notebook = { + registerNotebookProvider: (viewType: string, provider: vscode.NotebookProvider) => { + return extHostNotebook.registerNotebookProvider(extension, viewType, provider); + }, + registerNotebookOutputRenderer: (type: string, outputFilter: vscode.NotebookOutputSelector, renderer: vscode.NotebookOutputRenderer) => { + return extHostNotebook.registerNotebookOutputRenderer(type, extension, outputFilter, renderer); + }, + get activeNotebookDocument(): vscode.NotebookDocument | undefined { + return extHostNotebook.activeNotebookDocument; + } + }; + return { version: initData.version, // namespaces @@ -894,6 +926,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I comment, comments, tasks, + notebook, window, workspace, // types @@ -929,6 +962,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I DocumentLink: extHostTypes.DocumentLink, DocumentSymbol: extHostTypes.DocumentSymbol, EndOfLine: extHostTypes.EndOfLine, + EnvironmentVariableMutatorType: extHostTypes.EnvironmentVariableMutatorType, EvaluatableExpression: extHostTypes.EvaluatableExpression, EventEmitter: Emitter, ExtensionKind: extHostTypes.ExtensionKind, @@ -1000,10 +1034,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I CallHierarchyItem: extHostTypes.CallHierarchyItem, DebugConsoleMode: extHostTypes.DebugConsoleMode, Decoration: extHostTypes.Decoration, - WebviewContentState: extHostTypes.WebviewContentState, UIKind: UIKind, ColorThemeKind: extHostTypes.ColorThemeKind, - TimelineItem: extHostTypes.TimelineItem + TimelineItem: extHostTypes.TimelineItem, + CellKind: extHostTypes.CellKind, + CellOutputKind: extHostTypes.CellOutputKind, + CustomDocument: extHostTypes.CustomDocument, }; }; } @@ -1015,6 +1051,7 @@ class Extension implements vscode.Extension { private _identifier: ExtensionIdentifier; readonly id: string; + readonly extensionUri: URI; readonly extensionPath: string; readonly packageJSON: IExtensionDescription; readonly extensionKind: vscode.ExtensionKind; @@ -1024,6 +1061,7 @@ class Extension implements vscode.Extension { this._originExtensionId = originExtensionId; this._identifier = description.identifier; this.id = description.identifier.value; + this.extensionUri = description.extensionLocation; this.extensionPath = path.normalize(originalFSPath(description.extensionLocation)); this.packageJSON = description; this.extensionKind = kind; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 10f51d23548..1aa6d171ff2 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -49,10 +49,12 @@ import { SaveReason } from 'vs/workbench/common/editor'; import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { TunnelOptions } from 'vs/platform/remote/common/tunnel'; -import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline'; +import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline'; import { revive } from 'vs/base/common/marshalling'; +import { INotebookMimeTypeSelector, IOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { Dto } from 'vs/base/common/types'; +import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; export interface IEnvironment { isExtensionDevelopmentDebug: boolean; @@ -67,6 +69,7 @@ export interface IEnvironment { userHome: URI; webviewResourceRoot: string; webviewCspSource: string; + useHostProxy?: boolean; } export interface IStaticWorkspaceData { @@ -154,9 +157,9 @@ export interface MainThreadCommentsShape extends IDisposable { export interface MainThreadAuthenticationShape extends IDisposable { $registerAuthenticationProvider(id: string, displayName: string): void; $unregisterAuthenticationProvider(id: string): void; - $onDidChangeSessions(id: string): void; - $getSessionsPrompt(providerId: string, providerName: string, extensionId: string, extensionName: string): Promise; - $loginPrompt(providerId: string, providerName: string, extensionId: string, extensionName: string): Promise; + $onDidChangeSessions(providerId: string, event: modes.AuthenticationSessionsChangeEvent): void; + $getSessionsPrompt(providerId: string, accountName: string, providerName: string, extensionId: string, extensionName: string): Promise; + $loginPrompt(providerName: string, extensionName: string): Promise; } export interface MainThreadConfigurationShape extends IDisposable { @@ -360,6 +363,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerHoverProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerEvaluatableExpressionProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerDocumentHighlightProvider(handle: number, selector: IDocumentFilterDto[]): void; + $registerOnTypeRenameProvider(handle: number, selector: IDocumentFilterDto[], stopPattern: IRegExpDto | undefined): void; $registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void; $registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], metadata: ICodeActionProviderMetadataDto, displayName: string): void; $registerDocumentFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void; @@ -367,7 +371,8 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerOnTypeFormattingSupport(handle: number, selector: IDocumentFilterDto[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void; $registerNavigateTypeSupport(handle: number): void; $registerRenameSupport(handle: number, selector: IDocumentFilterDto[], supportsResolveInitialValues: boolean): void; - $registerDocumentSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend): void; + $registerDocumentSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend, eventHandle: number | undefined): void; + $emitDocumentSemanticTokensEvent(eventHandle: number): void; $registerDocumentRangeSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend): void; $registerSuggestSupport(handle: number, selector: IDocumentFilterDto[], triggerCharacters: string[], supportsResolveDetails: boolean, extensionId: ExtensionIdentifier): void; $registerSignatureHelpProvider(handle: number, selector: IDocumentFilterDto[], metadata: ISignatureHelpProviderMetadataDto): void; @@ -430,6 +435,9 @@ export interface MainThreadTerminalServiceShape extends IDisposable { $show(terminalId: number, preserveFocus: boolean): void; $startSendingDataEvents(): void; $stopSendingDataEvents(): void; + $startHandlingLinks(): void; + $stopHandlingLinks(): void; + $setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: ISerializableEnvironmentVariableCollection | undefined): void; // Process $sendProcessTitle(terminalId: number, title: string): void; @@ -532,7 +540,7 @@ export interface MainThreadQuickOpenShape extends IDisposable { } export interface MainThreadStatusBarShape extends IDisposable { - $setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: string | undefined, color: string | ThemeColor | undefined, alignment: statusbar.StatusbarAlignment, priority: number | undefined): void; + $setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: ICommandDto | undefined, color: string | ThemeColor | undefined, alignment: statusbar.StatusbarAlignment, priority: number | undefined): void; $dispose(id: number): void; } @@ -572,6 +580,20 @@ export interface WebviewExtensionDescription { readonly location: UriComponents; } +export interface NotebookExtensionDescription { + readonly id: ExtensionIdentifier; + readonly location: UriComponents; +} + +export enum WebviewEditorCapabilities { + Editable, + SupportsHotExit, +} + +export interface CustomTextEditorCapabilities { + readonly supportsMove?: boolean; +} + export interface MainThreadWebviewsShape extends IDisposable { $createWebviewPanel(extension: WebviewExtensionDescription, handle: WebviewPanelHandle, viewType: string, title: string, showOptions: WebviewPanelShowOptions, options: modes.IWebviewPanelOptions & modes.IWebviewOptions): void; $disposeWebview(handle: WebviewPanelHandle): void; @@ -587,11 +609,11 @@ export interface MainThreadWebviewsShape extends IDisposable { $registerSerializer(viewType: string): void; $unregisterSerializer(viewType: string): void; - $registerTextEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void; + $registerTextEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, capabilities: CustomTextEditorCapabilities): void; $registerCustomEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void; $unregisterEditorProvider(viewType: string): void; - $onDidChangeCustomDocumentState(resource: UriComponents, viewType: string, state: { dirty: boolean }): void; + $onDidEdit(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void; } export interface WebviewPanelViewStateData { @@ -602,6 +624,12 @@ export interface WebviewPanelViewStateData { }; } +export interface CustomDocumentEditState { + readonly allEdits: readonly number[]; + readonly currentIndex: number; + readonly saveIndex: number; +} + export interface ExtHostWebviewsShape { $onMessage(handle: WebviewPanelHandle, message: any): void; $onMissingCsp(handle: WebviewPanelHandle, extensionId: string): void; @@ -610,17 +638,68 @@ export interface ExtHostWebviewsShape { $deserializeWebviewPanel(newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, state: any, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise; - $resolveWebviewEditor(resource: UriComponents, newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise; - $createWebviewCustomEditorDocument(resource: UriComponents, viewType: string): Promise<{ editable: boolean }>; + $resolveWebviewEditor(resource: UriComponents, newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions, cancellation: CancellationToken): Promise; + $createWebviewCustomEditorDocument(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise<{ editable: boolean }>; $disposeWebviewCustomEditorDocument(resource: UriComponents, viewType: string): Promise; - $undo(resource: UriComponents, viewType: string): void; - $redo(resource: UriComponents, viewType: string): void; - $revert(resource: UriComponents, viewType: string): void; - $onSave(resource: UriComponents, viewType: string): Promise; - $onSaveAs(resource: UriComponents, viewType: string, targetResource: UriComponents): Promise; + $undo(resource: UriComponents, viewType: string, editId: number, state: CustomDocumentEditState): Promise; + $redo(resource: UriComponents, viewType: string, editId: number, state: CustomDocumentEditState): Promise; + $revert(resource: UriComponents, viewType: string, changes: { undoneEdits: number[], redoneEdits: number[] }, state: CustomDocumentEditState): Promise; + $disposeEdits(resourceComponents: UriComponents, viewType: string, editIds: number[]): void; + + $onSave(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise; + $onSaveAs(resource: UriComponents, viewType: string, targetResource: UriComponents, cancellation: CancellationToken): Promise; $backup(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise; + + $onMoveCustomEditor(handle: WebviewPanelHandle, newResource: UriComponents, viewType: string): Promise; +} + +export enum CellKind { + Markdown = 1, + Code = 2 +} + +export enum CellOutputKind { + Text = 1, + Error = 2, + Rich = 3 +} + +export interface ICellDto { + handle: number; + uri: UriComponents, + source: string[]; + language: string; + cellKind: CellKind; + outputs: IOutput[]; + metadata?: NotebookCellMetadata; +} + +export type NotebookCellsSplice = [ + number /* start */, + number /* delete count */, + ICellDto[] +]; + +export type NotebookCellOutputsSplice = [ + number /* start */, + number /* delete count */, + IOutput[] +]; + +export interface MainThreadNotebookShape extends IDisposable { + $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string): Promise; + $unregisterNotebookProvider(viewType: string): Promise; + $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, handle: number, preloads: UriComponents[]): Promise; + $unregisterNotebookRenderer(handle: number): Promise; + $createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise; + $tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise; + $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise; + $updateNotebookMetadata(viewType: string, resource: UriComponents, metadata: NotebookDocumentMetadata): Promise; + $updateNotebookCellMetadata(viewType: string, resource: UriComponents, handle: number, metadata: NotebookCellMetadata | undefined): Promise; + $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): Promise; + $postMessage(handle: number, value: any): Promise; } export interface MainThreadUrlsShape extends IDisposable { @@ -800,6 +879,7 @@ export interface MainThreadTunnelServiceShape extends IDisposable { $registerCandidateFinder(): Promise; $setTunnelProvider(): Promise; $setCandidateFilter(): Promise; + $tunnelServiceReady(): Promise; } export interface MainThreadTimelineShape extends IDisposable { @@ -1219,6 +1299,7 @@ export interface ExtHostLanguageFeaturesShape { $provideHover(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideEvaluatableExpression(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; + $provideOnTypeRenameRanges(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise; $provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise; $releaseCodeActions(handle: number, cacheId: number): void; @@ -1306,6 +1387,8 @@ export interface ExtHostTerminalServiceShape { $acceptWorkspacePermissionsChanged(isAllowed: boolean): void; $getAvailableShells(): Promise; $getDefaultShellAndArgs(useAutomationShell: boolean): Promise; + $handleLink(id: number, link: string): Promise; + $initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void; } export interface ExtHostSCMShape { @@ -1448,6 +1531,17 @@ export interface ExtHostCommentsShape { $toggleReaction(commentControllerHandle: number, threadHandle: number, uri: UriComponents, comment: modes.Comment, reaction: modes.CommentReaction): Promise; } +export interface ExtHostNotebookShape { + $resolveNotebook(viewType: string, uri: UriComponents): Promise; + $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise; + $saveNotebook(viewType: string, uri: UriComponents): Promise; + $updateActiveEditor(viewType: string, uri: UriComponents): Promise; + $destoryNotebookDocument(viewType: string, uri: UriComponents): Promise; + $acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void; + $onDidReceiveMessage(uri: UriComponents, message: any): void; + $acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void; +} + export interface ExtHostStorageShape { $acceptValue(shared: boolean, key: string, value: object | undefined): void; } @@ -1468,7 +1562,7 @@ export interface ExtHostTunnelServiceShape { } export interface ExtHostTimelineShape { - $getTimeline(source: string, uri: UriComponents, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean }): Promise; + $getTimeline(source: string, uri: UriComponents, options: TimelineOptions, token: CancellationToken, internalOptions?: InternalTimelineOptions): Promise; } // --- proxy identifiers @@ -1513,6 +1607,7 @@ export const MainContext = { MainThreadTask: createMainId('MainThreadTask'), MainThreadWindow: createMainId('MainThreadWindow'), MainThreadLabelService: createMainId('MainThreadLabelService'), + MainThreadNotebook: createMainId('MainThreadNotebook'), MainThreadTheming: createMainId('MainThreadTheming'), MainThreadTunnelService: createMainId('MainThreadTunnelService'), MainThreadTimeline: createMainId('MainThreadTimeline') @@ -1549,7 +1644,8 @@ export const ExtHostContext = { ExtHostStorage: createMainId('ExtHostStorage'), ExtHostUrls: createExtId('ExtHostUrls'), ExtHostOutputService: createMainId('ExtHostOutputService'), - ExtHostLabelService: createMainId('ExtHostLabelService'), + ExtHosLabelService: createMainId('ExtHostLabelService'), + ExtHostNotebook: createMainId('ExtHostNotebook'), ExtHostTheming: createMainId('ExtHostTheming'), ExtHostTunnelService: createMainId('ExtHostTunnelService'), ExtHostAuthentication: createMainId('ExtHostAuthentication'), diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index 4a35b91d591..4fcffb43157 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -174,7 +174,7 @@ const newCommands: ApiCommand[] = [ // -- selection range new ApiCommand( 'vscode.executeSelectionRangeProvider', '_executeSelectionRangeProvider', 'Execute selection range provider.', - [ApiCommandArgument.Uri, new ApiCommandArgument('position', 'A positions in a text document', v => Array.isArray(v) && v.every(v => types.Position.isPosition(v)), v => v.map(typeConverters.Position.from))], + [ApiCommandArgument.Uri, new ApiCommandArgument('position', 'A positions in a text document', v => Array.isArray(v) && v.every(v => types.Position.isPosition(v)), v => v.map(typeConverters.Position.from))], new ApiCommandResult('A promise that resolves to an array of ranges.', result => { return result.map(ranges => { let node: types.SelectionRange | undefined; diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index b3b2d4d0f4a..2af521dad00 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -10,61 +10,6 @@ import { IMainContext, MainContext, MainThreadAuthenticationShape, ExtHostAuthen import { Disposable } from 'vs/workbench/api/common/extHostTypes'; import { IExtensionDescription, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -export class AuthenticationProviderWrapper implements vscode.AuthenticationProvider { - readonly onDidChangeSessions: vscode.Event; - - constructor(private _requestingExtension: IExtensionDescription, - private _provider: vscode.AuthenticationProvider, - private _proxy: MainThreadAuthenticationShape) { - - this.onDidChangeSessions = this._provider.onDidChangeSessions; - } - - get id(): string { - return this._provider.id; - } - - get displayName(): string { - return this._provider.displayName; - } - - async getSessions(): Promise> { - return (await this._provider.getSessions()).map(session => { - return { - id: session.id, - accountName: session.accountName, - scopes: session.scopes, - getAccessToken: async () => { - const isAllowed = await this._proxy.$getSessionsPrompt( - this._provider.id, - this.displayName, - ExtensionIdentifier.toKey(this._requestingExtension.identifier), - this._requestingExtension.displayName || this._requestingExtension.name); - - if (!isAllowed) { - throw new Error('User did not consent to token access.'); - } - - return session.getAccessToken(); - } - }; - }); - } - - async login(scopes: string[]): Promise { - const isAllowed = await this._proxy.$loginPrompt(this._provider.id, this.displayName, ExtensionIdentifier.toKey(this._requestingExtension.identifier), this._requestingExtension.displayName || this._requestingExtension.name); - if (!isAllowed) { - throw new Error('User did not consent to login.'); - } - - return this._provider.login(scopes); - } - - logout(sessionId: string): Thenable { - return this._provider.logout(sessionId); - } -} - export class ExtHostAuthentication implements ExtHostAuthenticationShape { private _proxy: MainThreadAuthenticationShape; private _authenticationProviders: Map = new Map(); @@ -72,14 +17,88 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { private _onDidChangeAuthenticationProviders = new Emitter(); readonly onDidChangeAuthenticationProviders: Event = this._onDidChangeAuthenticationProviders.event; + private _onDidChangeSessions = new Emitter<{ [providerId: string]: vscode.AuthenticationSessionsChangeEvent }>(); + readonly onDidChangeSessions: Event<{ [providerId: string]: vscode.AuthenticationSessionsChangeEvent }> = this._onDidChangeSessions.event; + constructor(mainContext: IMainContext) { this._proxy = mainContext.getProxy(MainContext.MainThreadAuthentication); } - providers(requestingExtension: IExtensionDescription): vscode.AuthenticationProvider[] { - let providers: vscode.AuthenticationProvider[] = []; - this._authenticationProviders.forEach(provider => providers.push(new AuthenticationProviderWrapper(requestingExtension, provider, this._proxy))); - return providers; + get providerIds(): string[] { + const ids: string[] = []; + this._authenticationProviders.forEach(provider => { + ids.push(provider.id); + }); + + return ids; + } + + async getSessions(requestingExtension: IExtensionDescription, providerId: string, scopes: string[]): Promise { + const provider = this._authenticationProviders.get(providerId); + if (!provider) { + throw new Error(`No authentication provider with id '${providerId}' is currently registered.`); + } + + const extensionId = ExtensionIdentifier.toKey(requestingExtension.identifier); + const orderedScopes = scopes.sort().join(' '); + + return (await provider.getSessions()) + .filter(session => session.scopes.sort().join(' ') === orderedScopes) + .map(session => { + return { + id: session.id, + accountName: session.accountName, + scopes: session.scopes, + getAccessToken: async () => { + const isAllowed = await this._proxy.$getSessionsPrompt( + provider.id, + session.accountName, + provider.displayName, + extensionId, + requestingExtension.displayName || requestingExtension.name); + + if (!isAllowed) { + throw new Error('User did not consent to token access.'); + } + + return session.getAccessToken(); + } + }; + }); + } + + async login(requestingExtension: IExtensionDescription, providerId: string, scopes: string[]): Promise { + const provider = this._authenticationProviders.get(providerId); + if (!provider) { + throw new Error(`No authentication provider with id '${providerId}' is currently registered.`); + } + + const extensionName = requestingExtension.displayName || requestingExtension.name; + const isAllowed = await this._proxy.$loginPrompt(provider.displayName, extensionName); + if (!isAllowed) { + throw new Error('User did not consent to login.'); + } + + const session = await provider.login(scopes); + return { + id: session.id, + accountName: session.accountName, + scopes: session.scopes, + getAccessToken: async () => { + const isAllowed = await this._proxy.$getSessionsPrompt( + provider.id, + session.accountName, + provider.displayName, + ExtensionIdentifier.toKey(requestingExtension.identifier), + requestingExtension.displayName || requestingExtension.name); + + if (!isAllowed) { + throw new Error('User did not consent to token access.'); + } + + return session.getAccessToken(); + } + }; } registerAuthenticationProvider(provider: vscode.AuthenticationProvider): vscode.Disposable { @@ -89,8 +108,9 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { this._authenticationProviders.set(provider.id, provider); - const listener = provider.onDidChangeSessions(_ => { - this._proxy.$onDidChangeSessions(provider.id); + const listener = provider.onDidChangeSessions(e => { + this._proxy.$onDidChangeSessions(provider.id, e); + this._onDidChangeSessions.fire({ [provider.id]: e }); }); this._proxy.$registerAuthenticationProvider(provider.id, provider.displayName); diff --git a/src/vs/workbench/api/common/extHostDiagnostics.ts b/src/vs/workbench/api/common/extHostDiagnostics.ts index 256cfe3ec70..499bbdbb28b 100644 --- a/src/vs/workbench/api/common/extHostDiagnostics.ts +++ b/src/vs/workbench/api/common/extHostDiagnostics.ts @@ -12,7 +12,6 @@ import { DiagnosticSeverity } from './extHostTypes'; import * as converter from './extHostTypeConverters'; import { mergeSort } from 'vs/base/common/arrays'; import { Event, Emitter } from 'vs/base/common/event'; -import { keys } from 'vs/base/common/map'; import { ILogService } from 'vs/platform/log/common/log'; export class DiagnosticCollection implements vscode.DiagnosticCollection { @@ -36,7 +35,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { dispose(): void { if (!this._isDisposed) { - this._onDidChangeDiagnostics.fire(keys(this._data)); + this._onDidChangeDiagnostics.fire([...this._data.keys()]); if (this._proxy) { this._proxy.$clear(this._owner); } @@ -169,7 +168,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { clear(): void { this._checkDisposed(); - this._onDidChangeDiagnostics.fire(keys(this._data)); + this._onDidChangeDiagnostics.fire([...this._data.keys()]); this._data.clear(); if (this._proxy) { this._proxy.$clear(this._owner); diff --git a/src/vs/workbench/api/common/extHostDocumentData.ts b/src/vs/workbench/api/common/extHostDocumentData.ts index cd871a8b382..a295f646dd3 100644 --- a/src/vs/workbench/api/common/extHostDocumentData.ts +++ b/src/vs/workbench/api/common/extHostDocumentData.ts @@ -238,7 +238,7 @@ export class ExtHostDocumentData extends MirrorTextModel { } } -class ExtHostDocumentLine implements vscode.TextLine { +export class ExtHostDocumentLine implements vscode.TextLine { private readonly _line: number; private readonly _text: string; diff --git a/src/vs/workbench/api/common/extHostExtensionActivator.ts b/src/vs/workbench/api/common/extHostExtensionActivator.ts index 3a076946edd..7e86c340c1d 100644 --- a/src/vs/workbench/api/common/extHostExtensionActivator.ts +++ b/src/vs/workbench/api/common/extHostExtensionActivator.ts @@ -252,7 +252,15 @@ export class ExtensionsActivator { return; } - const currentExtension = this._registry.getExtensionDescription(currentActivation.id)!; + const currentExtension = this._registry.getExtensionDescription(currentActivation.id); + if (!currentExtension) { + // Error condition 0: unknown extension + this._host.onExtensionActivationError(currentActivation.id, new MissingDependencyError(currentActivation.id.value)); + const error = new Error(`Unknown dependency '${currentActivation.id.value}'`); + this._activatedExtensions.set(ExtensionIdentifier.toKey(currentActivation.id), new FailedExtension(error)); + return; + } + const depIds = (typeof currentExtension.extensionDependencies === 'undefined' ? [] : currentExtension.extensionDependencies); let currentExtensionGetsGreenLight = true; diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 197aa88c85c..904c5afd8c8 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -366,6 +366,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio globalState, workspaceState, subscriptions: [], + get extensionUri() { return extensionDescription.extensionLocation; }, get extensionPath() { return extensionDescription.extensionLocation.fsPath; }, get storagePath() { return that._storagePath.workspaceValue(extensionDescription); }, get globalStoragePath() { return that._storagePath.globalValue(extensionDescription); }, diff --git a/src/vs/workbench/api/common/extHostFileSystem.ts b/src/vs/workbench/api/common/extHostFileSystem.ts index a721785d28c..ca30ef0d660 100644 --- a/src/vs/workbench/api/common/extHostFileSystem.ts +++ b/src/vs/workbench/api/common/extHostFileSystem.ts @@ -148,7 +148,16 @@ class ConsumerFileSystem implements vscode.FileSystem { } // file system error - throw new FileSystemError(err.message, err.name as files.FileSystemProviderErrorCode); + switch (err.name) { + case files.FileSystemProviderErrorCode.FileExists: throw FileSystemError.FileExists(err.message); + case files.FileSystemProviderErrorCode.FileNotFound: throw FileSystemError.FileNotFound(err.message); + case files.FileSystemProviderErrorCode.FileNotADirectory: throw FileSystemError.FileNotADirectory(err.message); + case files.FileSystemProviderErrorCode.FileIsADirectory: throw FileSystemError.FileIsADirectory(err.message); + case files.FileSystemProviderErrorCode.NoPermissions: throw FileSystemError.NoPermissions(err.message); + case files.FileSystemProviderErrorCode.Unavailable: throw FileSystemError.Unavailable(err.message); + + default: throw new FileSystemError(err.message, err.name as files.FileSystemProviderErrorCode); + } } } diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 479a5126b3c..cfaa9abc5dd 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -27,7 +27,7 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio import { IURITransformer } from 'vs/base/common/uriIpc'; import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { VSBuffer } from 'vs/base/common/buffer'; -import { encodeSemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokens'; +import { encodeSemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokensDto'; import { IdGenerator } from 'vs/base/common/idGenerator'; import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; import { Cache } from './cache'; @@ -318,6 +318,26 @@ class DocumentHighlightAdapter { } } +class OnTypeRenameAdapter { + constructor( + private readonly _documents: ExtHostDocuments, + private readonly _provider: vscode.OnTypeRenameProvider + ) { } + + provideOnTypeRenameRanges(resource: URI, position: IPosition, token: CancellationToken): Promise { + + const doc = this._documents.getDocument(resource); + const pos = typeConvert.Position.to(position); + + return asPromise(() => this._provider.provideOnTypeRenameRanges(doc, pos, token)).then(value => { + if (Array.isArray(value)) { + return coalesce(value.map(typeConvert.Range.from)); + } + return undefined; + }); + } +} + class ReferenceAdapter { constructor( @@ -1350,7 +1370,8 @@ type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | Hov | RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter | SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter | TypeDefinitionAdapter | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter - | SelectionRangeAdapter | CallHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter | EvaluatableExpressionAdapter; + | SelectionRangeAdapter | CallHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter | EvaluatableExpressionAdapter + | OnTypeRenameAdapter; class AdapterData { constructor( @@ -1594,6 +1615,19 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return this._withAdapter(handle, DocumentHighlightAdapter, adapter => adapter.provideDocumentHighlights(URI.revive(resource), position, token), undefined); } + // --- on type rename + + registerOnTypeRenameProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.OnTypeRenameProvider, stopPattern?: RegExp): vscode.Disposable { + const handle = this._addNewAdapter(new OnTypeRenameAdapter(this._documents, provider), extension); + const serializedStopPattern = stopPattern ? ExtHostLanguageFeatures._serializeRegExp(stopPattern) : undefined; + this._proxy.$registerOnTypeRenameProvider(handle, this._transformDocumentSelector(selector), serializedStopPattern); + return this._createDisposable(handle); + } + + $provideOnTypeRenameRanges(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise { + return this._withAdapter(handle, OnTypeRenameAdapter, adapter => adapter.provideOnTypeRenameRanges(URI.revive(resource), position, token), undefined); + } + // --- references registerReferenceProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.ReferenceProvider): vscode.Disposable { @@ -1702,9 +1736,19 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF //#region semantic coloring registerDocumentSemanticTokensProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentSemanticTokensProvider, legend: vscode.SemanticTokensLegend): vscode.Disposable { - const handle = this._addNewAdapter(new DocumentSemanticTokensAdapter(this._documents, provider), extension); - this._proxy.$registerDocumentSemanticTokensProvider(handle, this._transformDocumentSelector(selector), legend); - return this._createDisposable(handle); + const handle = this._nextHandle(); + const eventHandle = (typeof provider.onDidChangeSemanticTokens === 'function' ? this._nextHandle() : undefined); + + this._adapter.set(handle, new AdapterData(new DocumentSemanticTokensAdapter(this._documents, provider), extension)); + this._proxy.$registerDocumentSemanticTokensProvider(handle, this._transformDocumentSelector(selector), legend, eventHandle); + let result = this._createDisposable(handle); + + if (eventHandle) { + const subscription = provider.onDidChangeSemanticTokens!(_ => this._proxy.$emitDocumentSemanticTokensEvent(eventHandle)); + result = Disposable.from(result, subscription); + } + + return result; } $provideDocumentSemanticTokens(handle: number, resource: UriComponents, previousResultId: number, token: CancellationToken): Promise { diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts new file mode 100644 index 00000000000..f65958cceba --- /dev/null +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -0,0 +1,782 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { readonly } from 'vs/base/common/errors'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { ISplice } from 'vs/base/common/sequence'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { CellKind, CellOutputKind, ExtHostNotebookShape, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { CellEditType, CellUri, diff, ICellEditOperation, ICellInsertEdit, IErrorOutput, INotebookDisplayOrder, INotebookEditData, IOrderedMimeType, IStreamOutput, ITransformedDisplayOutputDto, mimeTypeSupportedByCore, NotebookCellsChangedEvent, NotebookCellsSplice2, sortMimeTypes, ICellDeleteEdit, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { Disposable as VSCodeDisposable } from './extHostTypes'; +import { CancellationToken } from 'vs/base/common/cancellation'; + +interface IObservable { + proxy: T; + onDidChange: Event; +} + +function getObservable(obj: T): IObservable { + const onDidChange = new Emitter(); + const proxy = new Proxy(obj, { + set(target: T, p: PropertyKey, value: any, _receiver: any): boolean { + target[p as keyof T] = value; + onDidChange.fire(); + return true; + } + }); + + return { + proxy, + onDidChange: onDidChange.event + }; +} + +export class ExtHostCell extends Disposable implements vscode.NotebookCell { + + private originalSource: string[]; + private _outputs: any[]; + private _onDidChangeOutputs = new Emitter[]>(); + onDidChangeOutputs: Event[]> = this._onDidChangeOutputs.event; + private _textDocument: vscode.TextDocument | undefined; + private _initalVersion: number = -1; + private _outputMapping = new Set(); + private _metadata: vscode.NotebookCellMetadata; + + private _metadataChangeListener: IDisposable; + + get source() { + if (this._textDocument && this._initalVersion !== this._textDocument?.version) { + return this._textDocument.getText(); + } else { + return this.originalSource.join('\n'); + } + } + + constructor( + private viewType: string, + private documentUri: URI, + readonly handle: number, + readonly uri: URI, + private _content: string, + public readonly cellKind: CellKind, + public language: string, + outputs: any[], + _metadata: vscode.NotebookCellMetadata | undefined, + private _proxy: MainThreadNotebookShape + ) { + super(); + this.originalSource = this._content.split(/\r|\n|\r\n/g); + this._outputs = outputs; + + const observableMetadata = getObservable(_metadata || {}); + this._metadata = observableMetadata.proxy; + this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { + this.updateMetadata(); + })); + } + + get outputs() { + return this._outputs; + } + + set outputs(newOutputs: vscode.CellOutput[]) { + let diffs = diff(this._outputs || [], newOutputs || [], (a) => { + return this._outputMapping.has(a); + }); + + diffs.forEach(diff => { + for (let i = diff.start; i < diff.start + diff.deleteCount; i++) { + this._outputMapping.delete(this._outputs[i]); + } + + diff.toInsert.forEach(output => { + this._outputMapping.add(output); + }); + }); + + this._outputs = newOutputs; + this._onDidChangeOutputs.fire(diffs); + } + + get metadata() { + return this._metadata; + } + + set metadata(newMetadata: vscode.NotebookCellMetadata) { + // Don't apply metadata defaults here, 'undefined' means 'inherit from document metadata' + this._metadataChangeListener.dispose(); + const observableMetadata = getObservable(newMetadata); + this._metadata = observableMetadata.proxy; + this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { + this.updateMetadata(); + })); + + this.updateMetadata(); + } + + private updateMetadata(): Promise { + return this._proxy.$updateNotebookCellMetadata(this.viewType, this.documentUri, this.handle, this._metadata); + } + + getContent(): string { + if (this._textDocument && this._initalVersion !== this._textDocument?.version) { + return this._textDocument.getText(); + } else { + return this.originalSource.join('\n'); + } + } + + attachTextDocument(document: vscode.TextDocument) { + this._textDocument = document; + this._initalVersion = this._textDocument.version; + } + + detachTextDocument() { + if (this._textDocument && this._textDocument.version !== this._initalVersion) { + this.originalSource = this._textDocument.getText().split(/\r|\n|\r\n/g); + } + + this._textDocument = undefined; + this._initalVersion = -1; + } +} + +export class ExtHostNotebookDocument extends Disposable implements vscode.NotebookDocument { + private static _handlePool: number = 0; + readonly handle = ExtHostNotebookDocument._handlePool++; + + private _cells: ExtHostCell[] = []; + + private _cellDisposableMapping = new Map(); + + get cells() { + return this._cells; + } + + private _languages: string[] = []; + + get languages() { + return this._languages = []; + } + + set languages(newLanguages: string[]) { + this._languages = newLanguages; + this._proxy.$updateNotebookLanguages(this.viewType, this.uri, this._languages); + } + + private _metadata: Required = notebookDocumentMetadataDefaults; + private _metadataChangeListener: IDisposable; + + get metadata() { + return this._metadata; + } + + set metadata(newMetadata: Required) { + this._metadataChangeListener.dispose(); + newMetadata = { + ...notebookDocumentMetadataDefaults, + ...newMetadata + }; + const observableMetadata = getObservable(newMetadata); + this._metadata = observableMetadata.proxy; + this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { + this.updateMetadata(); + })); + } + + private _displayOrder: string[] = []; + + get displayOrder() { + return this._displayOrder; + } + + set displayOrder(newOrder: string[]) { + this._displayOrder = newOrder; + } + + private _versionId = 0; + + get versionId() { + return this._versionId; + } + + constructor( + private readonly _proxy: MainThreadNotebookShape, + private _documentsAndEditors: ExtHostDocumentsAndEditors, + public viewType: string, + public uri: URI, + public renderingHandler: ExtHostNotebookOutputRenderingHandler + ) { + super(); + + const observableMetadata = getObservable(notebookDocumentMetadataDefaults); + this._metadata = observableMetadata.proxy; + this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { + this.updateMetadata(); + })); + } + + private updateMetadata() { + this._proxy.$updateNotebookMetadata(this.viewType, this.uri, this._metadata); + } + + dispose() { + super.dispose(); + this._cellDisposableMapping.forEach(cell => cell.dispose()); + } + + get fileName() { return this.uri.fsPath; } + + get isDirty() { return false; } + + accpetModelChanged(event: NotebookCellsChangedEvent) { + this.$spliceNotebookCells(event.changes); + this._versionId = event.versionId; + } + + private $spliceNotebookCells(splices: NotebookCellsSplice2[]): void { + if (!splices.length) { + return; + } + + splices.reverse().forEach(splice => { + let cellDtos = splice[2]; + let newCells = cellDtos.map(cell => { + const extCell = new ExtHostCell(this.viewType, this.uri, cell.handle, URI.revive(cell.uri), cell.source.join('\n'), cell.cellKind, cell.language, cell.outputs, cell.metadata, this._proxy); + const document = this._documentsAndEditors.getDocument(URI.revive(cell.uri)); + + if (document) { + extCell.attachTextDocument(document.document); + } + + if (!this._cellDisposableMapping.has(extCell.handle)) { + this._cellDisposableMapping.set(extCell.handle, new DisposableStore()); + } + + let store = this._cellDisposableMapping.get(extCell.handle)!; + + store.add(extCell.onDidChangeOutputs((diffs) => { + this.eventuallyUpdateCellOutputs(extCell, diffs); + })); + + return extCell; + }); + + for (let j = splice[0]; j < splice[0] + splice[1]; j++) { + this._cellDisposableMapping.get(this.cells[j].handle)?.dispose(); + this._cellDisposableMapping.delete(this.cells[j].handle); + + } + + this.cells.splice(splice[0], splice[1], ...newCells); + }); + } + + eventuallyUpdateCellOutputs(cell: ExtHostCell, diffs: ISplice[]) { + let renderers = new Set(); + let outputDtos: NotebookCellOutputsSplice[] = diffs.map(diff => { + let outputs = diff.toInsert; + + let transformedOutputs = outputs.map(output => { + if (output.outputKind === CellOutputKind.Rich) { + const ret = this.transformMimeTypes(output); + + if (ret.orderedMimeTypes[ret.pickedMimeTypeIndex].isResolved) { + renderers.add(ret.orderedMimeTypes[ret.pickedMimeTypeIndex].rendererId!); + } + return ret; + } else { + return output as IStreamOutput | IErrorOutput; + } + }); + + return [diff.start, diff.deleteCount, transformedOutputs]; + }); + + this._proxy.$spliceNotebookCellOutputs(this.viewType, this.uri, cell.handle, outputDtos, Array.from(renderers)); + } + + transformMimeTypes(output: vscode.CellDisplayOutput): ITransformedDisplayOutputDto { + let mimeTypes = Object.keys(output.data); + + // TODO@rebornix, the document display order might be assigned a bit later. We need to postpone sending the outputs to the core side. + let coreDisplayOrder = this.renderingHandler.outputDisplayOrder; + const sorted = sortMimeTypes(mimeTypes, coreDisplayOrder?.userOrder || [], this._displayOrder, coreDisplayOrder?.defaultOrder || []); + + let orderMimeTypes: IOrderedMimeType[] = []; + + sorted.forEach(mimeType => { + let handlers = this.renderingHandler.findBestMatchedRenderer(mimeType); + + if (handlers.length) { + let renderedOutput = handlers[0].render(this, output, mimeType); + + orderMimeTypes.push({ + mimeType: mimeType, + isResolved: true, + rendererId: handlers[0].handle, + output: renderedOutput + }); + + for (let i = 1; i < handlers.length; i++) { + orderMimeTypes.push({ + mimeType: mimeType, + isResolved: false, + rendererId: handlers[i].handle + }); + } + + if (mimeTypeSupportedByCore(mimeType)) { + orderMimeTypes.push({ + mimeType: mimeType, + isResolved: false, + rendererId: -1 + }); + } + } else { + orderMimeTypes.push({ + mimeType: mimeType, + isResolved: false + }); + } + }); + + return { + outputKind: output.outputKind, + data: output.data, + orderedMimeTypes: orderMimeTypes, + pickedMimeTypeIndex: 0 + }; + } + + getCell(cellHandle: number) { + return this.cells.find(cell => cell.handle === cellHandle); + } + + attachCellTextDocument(textDocument: vscode.TextDocument) { + let cell = this.cells.find(cell => cell.uri.toString() === textDocument.uri.toString()); + if (cell) { + cell.attachTextDocument(textDocument); + } + } + + detachCellTextDocument(textDocument: vscode.TextDocument) { + let cell = this.cells.find(cell => cell.uri.toString() === textDocument.uri.toString()); + if (cell) { + cell.detachTextDocument(); + } + } +} + +export class NotebookEditorCellEdit { + private _finalized: boolean = false; + private readonly _documentVersionId: number; + private _collectedEdits: ICellEditOperation[] = []; + private _renderers = new Set(); + + constructor( + readonly editor: ExtHostNotebookEditor + ) { + this._documentVersionId = editor.document.versionId; + } + + finalize(): INotebookEditData { + this._finalized = true; + return { + documentVersionId: this._documentVersionId, + edits: this._collectedEdits, + renderers: Array.from(this._renderers) + }; + } + + private _throwIfFinalized() { + if (this._finalized) { + throw new Error('Edit is only valid while callback runs'); + } + } + + insert(index: number, content: string | string[], language: string, type: CellKind, outputs: vscode.CellOutput[], metadata: vscode.NotebookCellMetadata | undefined): void { + this._throwIfFinalized(); + + const sourceArr = Array.isArray(content) ? content : content.split(/\r|\n|\r\n/g); + let cell = { + source: sourceArr, + language, + cellKind: type, + outputs: (outputs as any[]), // TODO@rebornix + metadata + }; + + const transformedOutputs = outputs.map(output => { + if (output.outputKind === CellOutputKind.Rich) { + const ret = this.editor.document.transformMimeTypes(output); + + if (ret.orderedMimeTypes[ret.pickedMimeTypeIndex].isResolved) { + this._renderers.add(ret.orderedMimeTypes[ret.pickedMimeTypeIndex].rendererId!); + } + return ret; + } else { + return output as IStreamOutput | IErrorOutput; + } + }); + + cell.outputs = transformedOutputs; + + this._collectedEdits.push({ + editType: CellEditType.Insert, + index, + cells: [cell] + }); + } + + delete(index: number): void { + this._throwIfFinalized(); + + this._collectedEdits.push({ + editType: CellEditType.Delete, + index, + count: 1 + }); + } +} + +export class ExtHostNotebookEditor extends Disposable implements vscode.NotebookEditor { + private _viewColumn: vscode.ViewColumn | undefined; + onDidReceiveMessage: vscode.Event = this._onDidReceiveMessage.event; + + constructor( + private readonly viewType: string, + readonly id: string, + public uri: URI, + private _proxy: MainThreadNotebookShape, + private _onDidReceiveMessage: Emitter, + public document: ExtHostNotebookDocument, + private _documentsAndEditors: ExtHostDocumentsAndEditors + ) { + super(); + this._register(this._documentsAndEditors.onDidAddDocuments(documents => { + for (const { document: textDocument } of documents) { + let data = CellUri.parse(textDocument.uri); + if (data) { + if (this.document.uri.toString() === data.notebook.toString()) { + document.attachCellTextDocument(textDocument); + } + } + } + })); + + this._register(this._documentsAndEditors.onDidRemoveDocuments(documents => { + for (const { document: textDocument } of documents) { + let data = CellUri.parse(textDocument.uri); + if (data) { + if (this.document.uri.toString() === data.notebook.toString()) { + document.detachCellTextDocument(textDocument); + } + } + } + })); + } + + edit(callback: (editBuilder: NotebookEditorCellEdit) => void): Thenable { + const edit = new NotebookEditorCellEdit(this); + callback(edit); + return this._applyEdit(edit); + } + + private _applyEdit(editBuilder: NotebookEditorCellEdit): Promise { + const editData = editBuilder.finalize(); + + // return when there is nothing to do + if (editData.edits.length === 0) { + return Promise.resolve(true); + } + + let compressedEdits: ICellEditOperation[] = []; + let compressedEditsIndex = -1; + + for (let i = 0; i < editData.edits.length; i++) { + if (compressedEditsIndex < 0) { + compressedEdits.push(editData.edits[i]); + compressedEditsIndex++; + continue; + } + + let prevIndex = compressedEditsIndex; + let prev = compressedEdits[prevIndex]; + + if (prev.editType === CellEditType.Insert && editData.edits[i].editType === CellEditType.Insert) { + if (prev.index === editData.edits[i].index) { + prev.cells.push(...(editData.edits[i] as ICellInsertEdit).cells); + continue; + } + } + + if (prev.editType === CellEditType.Delete && editData.edits[i].editType === CellEditType.Delete) { + if (prev.index === editData.edits[i].index) { + prev.count += (editData.edits[i] as ICellDeleteEdit).count; + continue; + } + } + + compressedEdits.push(editData.edits[i]); + compressedEditsIndex++; + } + + return this._proxy.$tryApplyEdits(this.viewType, this.uri, editData.documentVersionId, compressedEdits, editData.renderers); + } + + get viewColumn(): vscode.ViewColumn | undefined { + return this._viewColumn; + } + + set viewColumn(value) { + throw readonly('viewColumn'); + } + + async postMessage(message: any): Promise { + return this._proxy.$postMessage(this.document.handle, message); + } + +} + +export class ExtHostNotebookOutputRenderer { + private static _handlePool: number = 0; + readonly handle = ExtHostNotebookOutputRenderer._handlePool++; + + constructor( + public type: string, + public filter: vscode.NotebookOutputSelector, + public renderer: vscode.NotebookOutputRenderer + ) { + + } + + matches(mimeType: string): boolean { + if (this.filter.subTypes) { + if (this.filter.subTypes.indexOf(mimeType) >= 0) { + return true; + } + } + return false; + } + + render(document: ExtHostNotebookDocument, output: vscode.CellOutput, mimeType: string): string { + let html = this.renderer.render(document, output, mimeType); + + return html; + } +} + +export interface ExtHostNotebookOutputRenderingHandler { + outputDisplayOrder: INotebookDisplayOrder | undefined; + findBestMatchedRenderer(mimeType: string): ExtHostNotebookOutputRenderer[]; +} + +export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostNotebookOutputRenderingHandler { + private static _handlePool: number = 0; + + private readonly _proxy: MainThreadNotebookShape; + private readonly _notebookProviders = new Map(); + private readonly _documents = new Map(); + private readonly _editors = new Map }>(); + private readonly _notebookOutputRenderers = new Map(); + private _outputDisplayOrder: INotebookDisplayOrder | undefined; + + get outputDisplayOrder(): INotebookDisplayOrder | undefined { + return this._outputDisplayOrder; + } + + private _activeNotebookDocument: ExtHostNotebookDocument | undefined; + + get activeNotebookDocument() { + return this._activeNotebookDocument; + } + + constructor(mainContext: IMainContext, commands: ExtHostCommands, private _documentsAndEditors: ExtHostDocumentsAndEditors) { + this._proxy = mainContext.getProxy(MainContext.MainThreadNotebook); + + commands.registerArgumentProcessor({ + processArgument: arg => { + if (arg && arg.$mid === 12) { + const documentHandle = arg.notebookEditor?.notebookHandle; + const cellHandle = arg.cell.handle; + + for (let value of this._editors) { + if (value[1].editor.document.handle === documentHandle) { + const cell = value[1].editor.document.getCell(cellHandle); + if (cell) { + return cell; + } + } + } + } + return arg; + } + }); + } + + registerNotebookOutputRenderer( + type: string, + extension: IExtensionDescription, + filter: vscode.NotebookOutputSelector, + renderer: vscode.NotebookOutputRenderer + ): vscode.Disposable { + let extHostRenderer = new ExtHostNotebookOutputRenderer(type, filter, renderer); + this._notebookOutputRenderers.set(extHostRenderer.handle, extHostRenderer); + this._proxy.$registerNotebookRenderer({ id: extension.identifier, location: extension.extensionLocation }, type, filter, extHostRenderer.handle, renderer.preloads || []); + return new VSCodeDisposable(() => { + this._notebookOutputRenderers.delete(extHostRenderer.handle); + this._proxy.$unregisterNotebookRenderer(extHostRenderer.handle); + }); + } + + findBestMatchedRenderer(mimeType: string): ExtHostNotebookOutputRenderer[] { + let matches: ExtHostNotebookOutputRenderer[] = []; + for (let renderer of this._notebookOutputRenderers) { + if (renderer[1].matches(mimeType)) { + matches.push(renderer[1]); + } + } + + return matches; + } + + registerNotebookProvider( + extension: IExtensionDescription, + viewType: string, + provider: vscode.NotebookProvider, + ): vscode.Disposable { + + if (this._notebookProviders.has(viewType)) { + throw new Error(`Notebook provider for '${viewType}' already registered`); + } + + this._notebookProviders.set(viewType, { extension, provider }); + this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType); + return new VSCodeDisposable(() => { + this._notebookProviders.delete(viewType); + this._proxy.$unregisterNotebookProvider(viewType); + }); + } + + async $resolveNotebook(viewType: string, uri: UriComponents): Promise { + let provider = this._notebookProviders.get(viewType); + + if (provider) { + if (!this._documents.has(URI.revive(uri).toString())) { + let document = new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, viewType, URI.revive(uri), this); + await this._proxy.$createNotebookDocument( + document.handle, + viewType, + uri + ); + + this._documents.set(URI.revive(uri).toString(), document); + } + + const onDidReceiveMessage = new Emitter(); + + let editor = new ExtHostNotebookEditor( + viewType, + `${ExtHostNotebookController._handlePool++}`, + URI.revive(uri), + this._proxy, + onDidReceiveMessage, + this._documents.get(URI.revive(uri).toString())!, + this._documentsAndEditors + ); + + this._editors.set(URI.revive(uri).toString(), { editor, onDidReceiveMessage }); + await provider.provider.resolveNotebook(editor); + // await editor.document.$updateCells(); + return editor.document.handle; + } + + return Promise.resolve(undefined); + } + + async $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise { + let provider = this._notebookProviders.get(viewType); + + if (!provider) { + return; + } + + let document = this._documents.get(URI.revive(uri).toString()); + + if (!document) { + return; + } + + let cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined; + return provider.provider.executeCell(document!, cell, token); + } + + async $saveNotebook(viewType: string, uri: UriComponents): Promise { + let provider = this._notebookProviders.get(viewType); + let document = this._documents.get(URI.revive(uri).toString()); + + if (provider && document) { + return await provider.provider.save(document); + } + + return false; + } + + async $updateActiveEditor(viewType: string, uri: UriComponents): Promise { + this._activeNotebookDocument = this._documents.get(URI.revive(uri).toString()); + } + + async $destoryNotebookDocument(viewType: string, uri: UriComponents): Promise { + let provider = this._notebookProviders.get(viewType); + + if (!provider) { + return false; + } + + let document = this._documents.get(URI.revive(uri).toString()); + + if (document) { + document.dispose(); + this._documents.delete(URI.revive(uri).toString()); + } + + let editor = this._editors.get(URI.revive(uri).toString()); + + if (editor) { + editor.editor.dispose(); + editor.onDidReceiveMessage.dispose(); + this._editors.delete(URI.revive(uri).toString()); + } + + return true; + } + + $acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void { + this._outputDisplayOrder = displayOrder; + } + + $onDidReceiveMessage(uri: UriComponents, message: any): void { + let editor = this._editors.get(URI.revive(uri).toString()); + + if (editor) { + editor.onDidReceiveMessage.fire(message); + } + } + + $acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void { + let editor = this._editors.get(URI.revive(uriComponents).toString()); + + if (editor) { + editor.editor.document.accpetModelChanged(event); + } + + } +} diff --git a/src/vs/workbench/api/common/extHostProgress.ts b/src/vs/workbench/api/common/extHostProgress.ts index 27a4e145c42..5db3edf0862 100644 --- a/src/vs/workbench/api/common/extHostProgress.ts +++ b/src/vs/workbench/api/common/extHostProgress.ts @@ -26,6 +26,7 @@ export class ExtHostProgress implements ExtHostProgressShape { const handle = this._handles++; const { title, location, cancellable } = options; const source = localize('extensionSource', "{0} (Extension)", extension.displayName || extension.name); + this._proxy.$startProgress(handle, { location: ProgressLocation.from(location), title, source, cancellable }, extension); return this._withProgress(handle, task, !!cancellable); } diff --git a/src/vs/workbench/api/common/extHostStatusBar.ts b/src/vs/workbench/api/common/extHostStatusBar.ts index 02c89a6e89e..d26ee3d7c86 100644 --- a/src/vs/workbench/api/common/extHostStatusBar.ts +++ b/src/vs/workbench/api/common/extHostStatusBar.ts @@ -5,11 +5,13 @@ import { StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/workbench/services/statusbar/common/statusbar'; import { StatusBarAlignment as ExtHostStatusBarAlignment, Disposable, ThemeColor } from './extHostTypes'; -import { StatusBarItem, StatusBarAlignment } from 'vscode'; -import { MainContext, MainThreadStatusBarShape, IMainContext } from './extHost.protocol'; +import type * as vscode from 'vscode'; +import { MainContext, MainThreadStatusBarShape, IMainContext, ICommandDto } from './extHost.protocol'; import { localize } from 'vs/nls'; +import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; +import { DisposableStore } from 'vs/base/common/lifecycle'; -export class ExtHostStatusBarEntry implements StatusBarItem { +export class ExtHostStatusBarEntry implements vscode.StatusBarItem { private static ID_GEN = 0; private _id: number; @@ -24,14 +26,20 @@ export class ExtHostStatusBarEntry implements StatusBarItem { private _text: string = ''; private _tooltip?: string; private _color?: string | ThemeColor; - private _command?: string; + private readonly _internalCommandRegistration = new DisposableStore(); + private _command?: { + readonly fromApi: string | vscode.Command, + readonly internal: ICommandDto, + }; private _timeoutHandle: any; private _proxy: MainThreadStatusBarShape; + private _commands: CommandsConverter; - constructor(proxy: MainThreadStatusBarShape, id: string, name: string, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number) { + constructor(proxy: MainThreadStatusBarShape, commands: CommandsConverter, id: string, name: string, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number) { this._id = ExtHostStatusBarEntry.ID_GEN++; this._proxy = proxy; + this._commands = commands; this._statusId = id; this._statusName = name; this._alignment = alignment; @@ -42,7 +50,7 @@ export class ExtHostStatusBarEntry implements StatusBarItem { return this._id; } - public get alignment(): StatusBarAlignment { + public get alignment(): vscode.StatusBarAlignment { return this._alignment; } @@ -62,8 +70,8 @@ export class ExtHostStatusBarEntry implements StatusBarItem { return this._color; } - public get command(): string | undefined { - return this._command; + public get command(): string | vscode.Command | undefined { + return this._command?.fromApi; } public set text(text: string) { @@ -81,8 +89,25 @@ export class ExtHostStatusBarEntry implements StatusBarItem { this.update(); } - public set command(command: string | undefined) { - this._command = command; + public set command(command: string | vscode.Command | undefined) { + if (this._command?.fromApi === command) { + return; + } + + this._internalCommandRegistration.clear(); + if (typeof command === 'string') { + this._command = { + fromApi: command, + internal: this._commands.toInternal({ title: '', command }, this._internalCommandRegistration), + }; + } else if (command) { + this._command = { + fromApi: command, + internal: this._commands.toInternal(command, this._internalCommandRegistration), + }; + } else { + this._command = undefined; + } this.update(); } @@ -109,7 +134,7 @@ export class ExtHostStatusBarEntry implements StatusBarItem { this._timeoutHandle = undefined; // Set to status bar - this._proxy.$setEntry(this.id, this._statusId, this._statusName, this.text, this.tooltip, this.command, this.color, + this._proxy.$setEntry(this.id, this._statusId, this._statusName, this.text, this.tooltip, this._command?.internal, this.color, this._alignment === ExtHostStatusBarAlignment.Left ? MainThreadStatusBarAlignment.LEFT : MainThreadStatusBarAlignment.RIGHT, this._priority); }, 0); @@ -123,7 +148,7 @@ export class ExtHostStatusBarEntry implements StatusBarItem { class StatusBarMessage { - private _item: StatusBarItem; + private _item: vscode.StatusBarItem; private _messages: { message: string }[] = []; constructor(statusBar: ExtHostStatusBar) { @@ -161,16 +186,18 @@ class StatusBarMessage { export class ExtHostStatusBar { - private _proxy: MainThreadStatusBarShape; + private readonly _proxy: MainThreadStatusBarShape; + private readonly _commands: CommandsConverter; private _statusMessage: StatusBarMessage; - constructor(mainContext: IMainContext) { + constructor(mainContext: IMainContext, commands: CommandsConverter) { this._proxy = mainContext.getProxy(MainContext.MainThreadStatusBar); + this._commands = commands; this._statusMessage = new StatusBarMessage(this); } - createStatusBarEntry(id: string, name: string, alignment?: ExtHostStatusBarAlignment, priority?: number): StatusBarItem { - return new ExtHostStatusBarEntry(this._proxy, id, name, alignment, priority); + createStatusBarEntry(id: string, name: string, alignment?: ExtHostStatusBarAlignment, priority?: number): vscode.StatusBarItem { + return new ExtHostStatusBarEntry(this._proxy, this._commands, id, name, alignment, priority); } setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable): Disposable { diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index bccacd0543f..5006cc3c3cf 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -14,6 +14,9 @@ import { timeout } from 'vs/base/common/async'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable as VSCodeDisposable, EnvironmentVariableMutatorType } from './extHostTypes'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; export interface IExtHostTerminalService extends ExtHostTerminalServiceShape { @@ -34,6 +37,8 @@ export interface IExtHostTerminalService extends ExtHostTerminalServiceShape { attachPtyToTerminal(id: number, pty: vscode.Pseudoterminal): void; getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string; getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string; + registerLinkHandler(handler: vscode.TerminalLinkHandler): vscode.Disposable; + getEnvironmentVariableCollection(extension: IExtensionDescription, persistent?: boolean): vscode.EnvironmentVariableCollection; } export const IExtHostTerminalService = createDecorator('IExtHostTerminalService'); @@ -295,6 +300,9 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ protected _extensionTerminalAwaitingStart: { [id: number]: { initialDimensions: ITerminalDimensionsDto | undefined } | undefined } = {}; protected _getTerminalPromises: { [id: number]: Promise } = {}; + private readonly _bufferer: TerminalDataBufferer; + private readonly _linkHandlers: Set = new Set(); + public get activeTerminal(): ExtHostTerminal | undefined { return this._activeTerminal; } public get terminals(): ExtHostTerminal[] { return this._terminals; } @@ -309,8 +317,6 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ protected readonly _onDidWriteTerminalData: Emitter; public get onDidWriteTerminalData(): Event { return this._onDidWriteTerminalData && this._onDidWriteTerminalData.event; } - private readonly _bufferer: TerminalDataBufferer; - constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService ) { @@ -330,6 +336,8 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ public abstract $getAvailableShells(): Promise; public abstract $getDefaultShellAndArgs(useAutomationShell: boolean): Promise; public abstract $acceptWorkspacePermissionsChanged(isAllowed: boolean): void; + public abstract getEnvironmentVariableCollection(extension: IExtensionDescription, persistent?: boolean): vscode.EnvironmentVariableCollection; + public abstract $initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void; public createExtensionTerminal(options: vscode.ExtensionTerminalOptions): vscode.Terminal { const terminal = new ExtHostTerminal(this._proxy, options, options.name); @@ -535,6 +543,38 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ return id; } + public registerLinkHandler(handler: vscode.TerminalLinkHandler): vscode.Disposable { + this._linkHandlers.add(handler); + if (this._linkHandlers.size === 1) { + this._proxy.$startHandlingLinks(); + } + return new VSCodeDisposable(() => { + this._linkHandlers.delete(handler); + if (this._linkHandlers.size === 0) { + this._proxy.$stopHandlingLinks(); + } + }); + } + + public async $handleLink(id: number, link: string): Promise { + const terminal = this._getTerminalById(id); + if (!terminal) { + return false; + } + + // Call each handler synchronously so multiple handlers aren't triggered at once + const it = this._linkHandlers.values(); + let next = it.next(); + while (!next.done) { + const handled = await next.value.handleLink(terminal, link); + if (handled) { + return true; + } + next = it.next(); + } + return false; + } + private _onProcessExit(id: number, exitCode: number | undefined): void { this._bufferer.stopBuffering(id); @@ -602,6 +642,86 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ } } +export class EnvironmentVariableCollection implements vscode.EnvironmentVariableCollection { + readonly map: Map = new Map(); + + private _disposed = false; + + protected readonly _onDidChangeCollection: Emitter = new Emitter(); + get onDidChangeCollection(): Event { return this._onDidChangeCollection && this._onDidChangeCollection.event; } + + constructor( + readonly persistent: boolean, + serialized?: ISerializableEnvironmentVariableCollection + ) { + this.map = new Map(serialized); + } + + get size(): number { + this._checkDisposed(); + return this.map.size; + } + + replace(variable: string, value: string): void { + this._checkDisposed(); + this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Replace }); + } + + append(variable: string, value: string): void { + this._checkDisposed(); + this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Append }); + } + + prepend(variable: string, value: string): void { + this._checkDisposed(); + this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Prepend }); + } + + private _setIfDiffers(variable: string, mutator: vscode.EnvironmentVariableMutator): void { + const current = this.map.get(variable); + if (!current || current.value !== mutator.value || current.type !== mutator.type) { + this.map.set(variable, mutator); + this._onDidChangeCollection.fire(); + } + } + + get(variable: string): vscode.EnvironmentVariableMutator | undefined { + this._checkDisposed(); + return this.map.get(variable); + } + + forEach(callback: (variable: string, mutator: vscode.EnvironmentVariableMutator, collection: vscode.EnvironmentVariableCollection) => any, thisArg?: any): void { + this._checkDisposed(); + this.map.forEach((value, key) => callback(key, value, this)); + } + + delete(variable: string): void { + this._checkDisposed(); + this.map.delete(variable); + this._onDidChangeCollection.fire(); + } + + clear(): void { + this._checkDisposed(); + this.map.clear(); + this._onDidChangeCollection.fire(); + } + + dispose(): void { + if (!this._disposed) { + this._disposed = true; + this.map.clear(); + this._onDidChangeCollection.fire(); + } + } + + protected _checkDisposed() { + if (this._disposed) { + throw new Error('EnvironmentVariableCollection has already been disposed'); + } + } +} + export class WorkerExtHostTerminalService extends BaseExtHostTerminalService { public createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal { throw new Error('Not implemented'); @@ -634,4 +754,13 @@ export class WorkerExtHostTerminalService extends BaseExtHostTerminalService { public $acceptWorkspacePermissionsChanged(isAllowed: boolean): void { // No-op for web worker ext host as workspace permissions aren't used } + + public getEnvironmentVariableCollection(extension: IExtensionDescription, persistent?: boolean): vscode.EnvironmentVariableCollection { + // This is not implemented so worker ext host extensions cannot influence terminal envs + throw new Error('Not implemented'); + } + + public $initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void { + // No-op for web worker ext host as collections aren't used + } } diff --git a/src/vs/workbench/api/common/extHostTimeline.ts b/src/vs/workbench/api/common/extHostTimeline.ts index 9db000d04f7..4df94da98d5 100644 --- a/src/vs/workbench/api/common/extHostTimeline.ts +++ b/src/vs/workbench/api/common/extHostTimeline.ts @@ -7,7 +7,7 @@ import * as vscode from 'vscode'; import { UriComponents, URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ExtHostTimelineShape, MainThreadTimelineShape, IMainContext, MainContext } from 'vs/workbench/api/common/extHost.protocol'; -import { Timeline, TimelineItem, TimelineOptions, TimelineProvider } from 'vs/workbench/contrib/timeline/common/timeline'; +import { Timeline, TimelineItem, TimelineOptions, TimelineProvider, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline'; import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { CancellationToken } from 'vs/base/common/cancellation'; import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; @@ -16,21 +16,19 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; export interface IExtHostTimeline extends ExtHostTimelineShape { readonly _serviceBrand: undefined; - $getTimeline(id: string, uri: UriComponents, options: vscode.TimelineOptions, token: vscode.CancellationToken, internalOptions?: { cacheResults?: boolean }): Promise; + $getTimeline(id: string, uri: UriComponents, options: vscode.TimelineOptions, token: vscode.CancellationToken, internalOptions?: InternalTimelineOptions): Promise; } export const IExtHostTimeline = createDecorator('IExtHostTimeline'); export class ExtHostTimeline implements IExtHostTimeline { - private static handlePool = 0; - _serviceBrand: undefined; private _proxy: MainThreadTimelineShape; private _providers = new Map(); - private _itemsBySourceByUriMap = new Map>>(); + private _itemsBySourceAndUriMap = new Map>>(); constructor( mainContext: IMainContext, @@ -42,7 +40,7 @@ export class ExtHostTimeline implements IExtHostTimeline { processArgument: arg => { if (arg && arg.$mid === 11) { const uri = arg.uri === undefined ? undefined : URI.revive(arg.uri); - return this._itemsBySourceByUriMap.get(getUriKey(uri))?.get(arg.source)?.get(arg.handle); + return this._itemsBySourceAndUriMap.get(arg.source)?.get(getUriKey(uri))?.get(arg.handle); } return arg; @@ -50,7 +48,7 @@ export class ExtHostTimeline implements IExtHostTimeline { }); } - async $getTimeline(id: string, uri: UriComponents, options: vscode.TimelineOptions, token: vscode.CancellationToken, internalOptions?: { cacheResults?: boolean }): Promise { + async $getTimeline(id: string, uri: UriComponents, options: vscode.TimelineOptions, token: vscode.CancellationToken, internalOptions?: InternalTimelineOptions): Promise { const provider = this._providers.get(id); return provider?.provideTimeline(URI.revive(uri), options, token, internalOptions); } @@ -62,26 +60,21 @@ export class ExtHostTimeline implements IExtHostTimeline { let disposable: IDisposable | undefined; if (provider.onDidChange) { - disposable = provider.onDidChange(this.emitTimelineChangeEvent(provider.id), this); + disposable = provider.onDidChange(e => this._proxy.$emitTimelineChangeEvent({ ...e, id: provider.id }), this); } - const itemsBySourceByUriMap = this._itemsBySourceByUriMap; + const itemsBySourceAndUriMap = this._itemsBySourceAndUriMap; return this.registerTimelineProviderCore({ ...provider, scheme: scheme, onDidChange: undefined, - async provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean }) { - // For now, only allow the caching of a single Uri - if (internalOptions?.cacheResults) { - if (options.cursor === undefined) { - timelineDisposables.clear(); - } - - if (!itemsBySourceByUriMap.has(getUriKey(uri))) { - itemsBySourceByUriMap.clear(); - } - } else { + async provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: InternalTimelineOptions) { + if (internalOptions?.resetCache) { timelineDisposables.clear(); + + // For now, only allow the caching of a single Uri + // itemsBySourceAndUriMap.get(provider.id)?.get(getUriKey(uri))?.clear(); + itemsBySourceAndUriMap.get(provider.id)?.clear(); } const result = await provider.provideTimeline(uri, options, token); @@ -91,8 +84,9 @@ export class ExtHostTimeline implements IExtHostTimeline { return undefined; } - // TODO: Determine if we should cache dependent on who calls us (internal vs external) - const convertItem = convertTimelineItem(uri, internalOptions?.cacheResults ?? false); + // TODO: Should we bother converting all the data if we aren't caching? Meaning it is being requested by an extension? + + const convertItem = convertTimelineItem(uri, internalOptions); return { ...result, source: provider.id, @@ -100,6 +94,10 @@ export class ExtHostTimeline implements IExtHostTimeline { }; }, dispose() { + for (const sourceMap of itemsBySourceAndUriMap.values()) { + sourceMap.get(provider.id)?.clear(); + } + disposable?.dispose(); timelineDisposables.dispose(); } @@ -107,29 +105,28 @@ export class ExtHostTimeline implements IExtHostTimeline { } private convertTimelineItem(source: string, commandConverter: CommandsConverter, disposables: DisposableStore) { - return (uri: URI, cacheResults: boolean) => { - let itemsMap: Map | undefined; - if (cacheResults) { - const uriKey = getUriKey(uri); - - let sourceMap = this._itemsBySourceByUriMap.get(uriKey); - if (sourceMap === undefined) { - sourceMap = new Map(); - this._itemsBySourceByUriMap.set(uriKey, sourceMap); + return (uri: URI, options?: InternalTimelineOptions) => { + let items: Map | undefined; + if (options?.cacheResults) { + let itemsByUri = this._itemsBySourceAndUriMap.get(source); + if (itemsByUri === undefined) { + itemsByUri = new Map(); + this._itemsBySourceAndUriMap.set(source, itemsByUri); } - itemsMap = sourceMap.get(source); - if (itemsMap === undefined) { - itemsMap = new Map(); - sourceMap.set(source, itemsMap); + const uriKey = getUriKey(uri); + items = itemsByUri.get(uriKey); + if (items === undefined) { + items = new Map(); + itemsByUri.set(uriKey, items); } } return (item: vscode.TimelineItem): TimelineItem => { const { iconPath, ...props } = item; - const handle = `${source}|${item.id ?? `${item.timestamp}-${ExtHostTimeline.handlePool++}`}`; - itemsMap?.set(handle, item); + const handle = `${source}|${item.id ?? item.timestamp}`; + items?.set(handle, item); let icon; let iconDark; @@ -161,22 +158,6 @@ export class ExtHostTimeline implements IExtHostTimeline { }; } - private emitTimelineChangeEvent(id: string) { - return (e: vscode.TimelineChangeEvent) => { - // Clear caches - if (e?.uri === undefined) { - for (const sourceMap of this._itemsBySourceByUriMap.values()) { - sourceMap.get(id)?.clear(); - } - } - else { - this._itemsBySourceByUriMap.get(getUriKey(e.uri))?.clear(); - } - - this._proxy.$emitTimelineChangeEvent({ ...e, id: id }); - }; - } - private registerTimelineProviderCore(provider: TimelineProvider): IDisposable { // console.log(`ExtHostTimeline#registerTimelineProvider: id=${provider.id}`); @@ -193,7 +174,7 @@ export class ExtHostTimeline implements IExtHostTimeline { this._providers.set(provider.id, provider); return toDisposable(() => { - for (const sourceMap of this._itemsBySourceByUriMap.values()) { + for (const sourceMap of this._itemsBySourceAndUriMap.values()) { sourceMap.get(provider.id)?.clear(); } diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index 98436355eb8..60aec092a05 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -3,16 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostTunnelServiceShape, MainContext, MainThreadTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import * as vscode from 'vscode'; import { RemoteTunnel, TunnelOptions } from 'vs/platform/remote/common/tunnel'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Emitter } from 'vs/base/common/event'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; export interface TunnelDto { remoteAddress: { port: number, host: string }; - localAddress: string; + localAddress: { port: number, host: string } | string; } export namespace TunnelDto { @@ -42,6 +43,13 @@ export const IExtHostTunnelService = createDecorator('IEx export class ExtHostTunnelService implements IExtHostTunnelService { _serviceBrand: undefined; onDidChangeTunnels: vscode.Event = (new Emitter()).event; + private readonly _proxy: MainThreadTunnelServiceShape; + + constructor( + @IExtHostRpcService extHostRpc: IExtHostRpcService, + ) { + this._proxy = extHostRpc.getProxy(MainContext.MainThreadTunnelService); + } async openTunnel(forward: TunnelOptions): Promise { return undefined; @@ -55,7 +63,10 @@ export class ExtHostTunnelService implements IExtHostTunnelService { async $filterCandidates(candidates: { host: string, port: number, detail: string }[]): Promise { return candidates.map(() => true); } - async setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise { return { dispose: () => { } }; } + async setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise { + await this._proxy.$tunnelServiceReady(); + return { dispose: () => { } }; + } $forwardPort(tunnelOptions: TunnelOptions): Promise | undefined { return undefined; } async $closeTunnel(remote: { host: string, port: number }): Promise { } async $onDidTunnelsChange(): Promise { } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 38154ceca2e..6ef467bd8a2 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -835,6 +835,8 @@ export namespace CompletionItemKind { case types.CompletionItemKind.Event: return modes.CompletionItemKind.Event; case types.CompletionItemKind.Operator: return modes.CompletionItemKind.Operator; case types.CompletionItemKind.TypeParameter: return modes.CompletionItemKind.TypeParameter; + case types.CompletionItemKind.Issue: return modes.CompletionItemKind.Issue; + case types.CompletionItemKind.User: return modes.CompletionItemKind.User; } return modes.CompletionItemKind.Property; } @@ -866,6 +868,8 @@ export namespace CompletionItemKind { case modes.CompletionItemKind.Event: return types.CompletionItemKind.Event; case modes.CompletionItemKind.Operator: return types.CompletionItemKind.Operator; case modes.CompletionItemKind.TypeParameter: return types.CompletionItemKind.TypeParameter; + case modes.CompletionItemKind.User: return types.CompletionItemKind.User; + case modes.CompletionItemKind.Issue: return types.CompletionItemKind.Issue; } return types.CompletionItemKind.Property; } @@ -1093,7 +1097,11 @@ export namespace EndOfLine { } export namespace ProgressLocation { - export function from(loc: vscode.ProgressLocation): MainProgressLocation { + export function from(loc: vscode.ProgressLocation | { viewId: string }): MainProgressLocation | string { + if (typeof loc === 'object') { + return loc.viewId; + } + switch (loc) { case types.ProgressLocation.SourceControl: return MainProgressLocation.Scm; case types.ProgressLocation.Window: return MainProgressLocation.Window; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index e08335045e3..f57d386ee87 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4,25 +4,26 @@ *--------------------------------------------------------------------------------------------*/ import { coalesce, equals } from 'vs/base/common/arrays'; +import { escapeCodicons } from 'vs/base/common/codicons'; import { illegalArgument } from 'vs/base/common/errors'; +import { Emitter } from 'vs/base/common/event'; import { IRelativePattern } from 'vs/base/common/glob'; import { isMarkdownString } from 'vs/base/common/htmlContent'; -import { values } from 'vs/base/common/map'; import { startsWith } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; -import type * as vscode from 'vscode'; import { FileSystemProviderErrorCode, markAsFileSystemProviderError } from 'vs/platform/files/common/files'; import { RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { escapeCodicons } from 'vs/base/common/codicons'; +import type * as vscode from 'vscode'; +import { Cache } from './cache'; +import { assertIsDefined, isStringArray } from 'vs/base/common/types'; +import { Schemas } from 'vs/base/common/network'; function es5ClassCompat(target: Function): any { - ///@ts-ignore + ///@ts-expect-error function _() { return Reflect.construct(target, arguments, this.constructor); } Object.defineProperty(_, 'name', Object.getOwnPropertyDescriptor(target, 'name')!); - ///@ts-ignore Object.setPrototypeOf(_, target); - ///@ts-ignore Object.setPrototypeOf(_.prototype, target.prototype); return _; } @@ -44,16 +45,16 @@ export class Disposable { }); } - private _callOnDispose?: () => any; + #callOnDispose?: () => any; constructor(callOnDispose: () => any) { - this._callOnDispose = callOnDispose; + this.#callOnDispose = callOnDispose; } dispose(): any { - if (typeof this._callOnDispose === 'function') { - this._callOnDispose(); - this._callOnDispose = undefined; + if (typeof this.#callOnDispose === 'function') { + this.#callOnDispose(); + this.#callOnDispose = undefined; } } } @@ -481,6 +482,12 @@ export enum EndOfLine { CRLF = 2 } +export enum EnvironmentVariableMutatorType { + Replace = 1, + Append = 2, + Prepend = 3 +} + @es5ClassCompat export class TextEdit { @@ -661,7 +668,7 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { textEdit[1].push(candidate.edit); } } - return values(textEdits); + return [...textEdits.values()]; } allEntries(): ReadonlyArray { @@ -1349,7 +1356,9 @@ export enum CompletionItemKind { Struct = 21, Event = 22, Operator = 23, - TypeParameter = 24 + TypeParameter = 24, + User = 25, + Issue = 26 } export enum CompletionItemTag { @@ -1358,7 +1367,7 @@ export enum CompletionItemTag { export interface CompletionItemLabel { name: string; - signature?: string; + parameters?: string; qualifier?: string; type?: string; } @@ -2334,9 +2343,13 @@ export class FileSystemError extends Error { return new FileSystemError(messageOrUri, FileSystemProviderErrorCode.Unavailable, FileSystemError.Unavailable); } + readonly code: string; + constructor(uriOrMessage?: string | URI, code: FileSystemProviderErrorCode = FileSystemProviderErrorCode.Unknown, terminator?: Function) { super(URI.isUri(uriOrMessage) ? uriOrMessage.toString(true) : uriOrMessage); + this.code = terminator?.name ?? 'Unknown'; + // mark the error as file system provider error so that // we can extract the error code on the receiving side markAsFileSystemProviderError(this, code); @@ -2407,30 +2420,124 @@ export class SemanticTokensLegend { public readonly tokenTypes: string[]; public readonly tokenModifiers: string[]; - constructor(tokenTypes: string[], tokenModifiers: string[]) { + constructor(tokenTypes: string[], tokenModifiers: string[] = []) { this.tokenTypes = tokenTypes; this.tokenModifiers = tokenModifiers; } } +function isStrArrayOrUndefined(arg: any): arg is string[] | undefined { + return ((typeof arg === 'undefined') || isStringArray(arg)); +} + export class SemanticTokensBuilder { private _prevLine: number; private _prevChar: number; + private _dataIsSortedAndDeltaEncoded: boolean; private _data: number[]; private _dataLen: number; + private _tokenTypeStrToInt: Map; + private _tokenModifierStrToInt: Map; + private _hasLegend: boolean; - constructor() { + constructor(legend?: vscode.SemanticTokensLegend) { this._prevLine = 0; this._prevChar = 0; + this._dataIsSortedAndDeltaEncoded = true; this._data = []; this._dataLen = 0; + this._tokenTypeStrToInt = new Map(); + this._tokenModifierStrToInt = new Map(); + this._hasLegend = false; + if (legend) { + this._hasLegend = true; + for (let i = 0, len = legend.tokenTypes.length; i < len; i++) { + this._tokenTypeStrToInt.set(legend.tokenTypes[i], i); + } + for (let i = 0, len = legend.tokenModifiers.length; i < len; i++) { + this._tokenModifierStrToInt.set(legend.tokenModifiers[i], i); + } + } } - public push(line: number, char: number, length: number, tokenType: number, tokenModifiers: number): void { + public push(line: number, char: number, length: number, tokenType: number, tokenModifiers?: number): void; + public push(range: Range, tokenType: string, tokenModifiers?: string[]): void; + public push(arg0: any, arg1: any, arg2: any, arg3?: any, arg4?: any): void { + if (typeof arg0 === 'number' && typeof arg1 === 'number' && typeof arg2 === 'number' && typeof arg3 === 'number' && (typeof arg4 === 'number' || typeof arg4 === 'undefined')) { + if (typeof arg4 === 'undefined') { + arg4 = 0; + } + // 1st overload + return this._pushEncoded(arg0, arg1, arg2, arg3, arg4); + } + if (Range.isRange(arg0) && typeof arg1 === 'string' && isStrArrayOrUndefined(arg2)) { + // 2nd overload + return this._push(arg0, arg1, arg2); + } + throw illegalArgument(); + } + + private _push(range: vscode.Range, tokenType: string, tokenModifiers?: string[]): void { + if (!this._hasLegend) { + throw new Error('Legend must be provided in constructor'); + } + if (range.start.line !== range.end.line) { + throw new Error('`range` cannot span multiple lines'); + } + if (!this._tokenTypeStrToInt.has(tokenType)) { + throw new Error('`tokenType` is not in the provided legend'); + } + const line = range.start.line; + const char = range.start.character; + const length = range.end.character - range.start.character; + const nTokenType = this._tokenTypeStrToInt.get(tokenType)!; + let nTokenModifiers = 0; + if (tokenModifiers) { + for (const tokenModifier of tokenModifiers) { + if (!this._tokenModifierStrToInt.has(tokenModifier)) { + throw new Error('`tokenModifier` is not in the provided legend'); + } + const nTokenModifier = this._tokenModifierStrToInt.get(tokenModifier)!; + nTokenModifiers |= (1 << nTokenModifier) >>> 0; + } + } + this._pushEncoded(line, char, length, nTokenType, nTokenModifiers); + } + + private _pushEncoded(line: number, char: number, length: number, tokenType: number, tokenModifiers: number): void { + if (this._dataIsSortedAndDeltaEncoded && (line < this._prevLine || (line === this._prevLine && char < this._prevChar))) { + // push calls were ordered and are no longer ordered + this._dataIsSortedAndDeltaEncoded = false; + + // Remove delta encoding from data + const tokenCount = (this._data.length / 5) | 0; + let prevLine = 0; + let prevChar = 0; + for (let i = 0; i < tokenCount; i++) { + let line = this._data[5 * i]; + let char = this._data[5 * i + 1]; + + if (line === 0) { + // on the same line as previous token + line = prevLine; + char += prevChar; + } else { + // on a different line than previous token + line += prevLine; + } + + this._data[5 * i] = line; + this._data[5 * i + 1] = char; + + prevLine = line; + prevChar = char; + } + } + let pushLine = line; let pushChar = char; - if (this._dataLen > 0) { + if (this._dataIsSortedAndDeltaEncoded && this._dataLen > 0) { pushLine -= this._prevLine; if (pushLine === 0) { pushChar -= this._prevChar; @@ -2447,8 +2554,55 @@ export class SemanticTokensBuilder { this._prevChar = char; } - public build(): Uint32Array { - return new Uint32Array(this._data); + private static _sortAndDeltaEncode(data: number[]): Uint32Array { + let pos: number[] = []; + const tokenCount = (data.length / 5) | 0; + for (let i = 0; i < tokenCount; i++) { + pos[i] = i; + } + pos.sort((a, b) => { + const aLine = data[5 * a]; + const bLine = data[5 * b]; + if (aLine === bLine) { + const aChar = data[5 * a + 1]; + const bChar = data[5 * b + 1]; + return aChar - bChar; + } + return aLine - bLine; + }); + const result = new Uint32Array(data.length); + let prevLine = 0; + let prevChar = 0; + for (let i = 0; i < tokenCount; i++) { + const srcOffset = 5 * pos[i]; + const line = data[srcOffset + 0]; + const char = data[srcOffset + 1]; + const length = data[srcOffset + 2]; + const tokenType = data[srcOffset + 3]; + const tokenModifiers = data[srcOffset + 4]; + + const pushLine = line - prevLine; + const pushChar = (pushLine === 0 ? char - prevChar : char); + + const dstOffset = 5 * i; + result[dstOffset + 0] = pushLine; + result[dstOffset + 1] = pushChar; + result[dstOffset + 2] = length; + result[dstOffset + 3] = tokenType; + result[dstOffset + 4] = tokenModifiers; + + prevLine = line; + prevChar = char; + } + + return result; + } + + public build(resultId?: string): SemanticTokens { + if (!this._dataIsSortedAndDeltaEncoded) { + return new SemanticTokens(SemanticTokensBuilder._sortAndDeltaEncode(this._data), resultId); + } + return new SemanticTokens(new Uint32Array(this._data), resultId); } } @@ -2533,13 +2687,6 @@ export class Decoration { bubble?: boolean; } -export enum WebviewContentState { - Readonly = 1, - Unchanged = 2, - Dirty = 3, -} - - //#region Theming @es5ClassCompat @@ -2556,6 +2703,21 @@ export enum ColorThemeKind { //#endregion Theming +//#region Notebook + +export enum CellKind { + Markdown = 1, + Code = 2 +} + +export enum CellOutputKind { + Text = 1, + Error = 2, + Rich = 3 +} + +//#endregion + //#region Timeline @es5ClassCompat @@ -2564,3 +2726,94 @@ export class TimelineItem implements vscode.TimelineItem { } //#endregion Timeline + +//#region Custom Editors + +interface EditState { + readonly allEdits: readonly number[]; + readonly currentIndex: number; + readonly saveIndex: number; +} + +export class CustomDocument implements vscode.CustomDocument { + + readonly #edits = new Cache('edits'); + + readonly #uri: vscode.Uri; + + #editState: EditState = { + allEdits: [], + currentIndex: -1, + saveIndex: -1, + }; + #isDisposed = false; + #version = 1; + + constructor(uri: vscode.Uri) { + this.#uri = uri; + } + + //#region Public API + + public get uri(): vscode.Uri { return this.#uri; } + + public get fileName(): string { return this.uri.fsPath; } + + public get isUntitled() { return this.uri.scheme === Schemas.untitled; } + + #onDidDispose = new Emitter(); + public readonly onDidDispose = this.#onDidDispose.event; + + public get isClosed() { return this.#isDisposed; } + + public get version() { return this.#version; } + + public get isDirty() { + return this.#editState.currentIndex !== this.#editState.saveIndex; + } + + public get appliedEdits() { + return this.#editState.allEdits.slice(0, this.#editState.currentIndex + 1) + .map(id => this._getEdit(id)); + } + + public get savedEdits() { + return this.#editState.allEdits.slice(0, this.#editState.saveIndex + 1) + .map(id => this._getEdit(id)); + } + + //#endregion + + /** @internal */ _dispose(): void { + this.#isDisposed = true; + this.#onDidDispose.fire(); + this.#onDidDispose.dispose(); + } + + /** @internal */ _updateEditState(state: EditState) { + ++this.#version; + this.#editState = state; + } + + /** @internal*/ _getEdit(editId: number): EditType { + return assertIsDefined(this.#edits.get(editId, 0)); + } + + /** @internal*/ _disposeEdits(editIds: number[]) { + for (const editId of editIds) { + this.#edits.delete(editId); + } + } + + /** @internal*/ _addEdit(edit: EditType): number { + const id = this.#edits.add([edit]); + this._updateEditState({ + allEdits: [...this.#editState.allEdits.slice(0, this.#editState.currentIndex + 1), id], + currentIndex: this.#editState.currentIndex + 1, + saveIndex: this.#editState.saveIndex, + }); + return id; + } +} + +// #endregion diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index 17b6b4719ec..fdcf7623620 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -5,7 +5,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import * as modes from 'vs/editor/common/modes'; @@ -18,79 +18,97 @@ import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; import type * as vscode from 'vscode'; -import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewExtensionDescription, WebviewPanelHandle, WebviewPanelViewStateData } from './extHost.protocol'; -import { Disposable as VSCodeDisposable } from './extHostTypes'; +import * as extHostProtocol from './extHost.protocol'; +import * as extHostTypes from './extHostTypes'; type IconPath = URI | { light: URI, dark: URI }; export class ExtHostWebview implements vscode.Webview { - private _html: string = ''; - private _isDisposed: boolean = false; - private _hasCalledAsWebviewUri = false; - public readonly _onMessageEmitter = new Emitter(); - public readonly onDidReceiveMessage: Event = this._onMessageEmitter.event; + readonly #handle: extHostProtocol.WebviewPanelHandle; + readonly #proxy: extHostProtocol.MainThreadWebviewsShape; + readonly #deprecationService: IExtHostApiDeprecationService; + + readonly #initData: WebviewInitData; + readonly #workspace: IExtHostWorkspace | undefined; + readonly #extension: IExtensionDescription; + + #html: string = ''; + #options: vscode.WebviewOptions; + #isDisposed: boolean = false; + #hasCalledAsWebviewUri = false; constructor( - private readonly _handle: WebviewPanelHandle, - private readonly _proxy: MainThreadWebviewsShape, - private _options: vscode.WebviewOptions, - private readonly _initData: WebviewInitData, - private readonly _workspace: IExtHostWorkspace | undefined, - private readonly _extension: IExtensionDescription, - private readonly _deprecationService: IExtHostApiDeprecationService, - ) { } + handle: extHostProtocol.WebviewPanelHandle, + proxy: extHostProtocol.MainThreadWebviewsShape, + options: vscode.WebviewOptions, + initData: WebviewInitData, + workspace: IExtHostWorkspace | undefined, + extension: IExtensionDescription, + deprecationService: IExtHostApiDeprecationService, + ) { + this.#handle = handle; + this.#proxy = proxy; + this.#options = options; + this.#initData = initData; + this.#workspace = workspace; + this.#extension = extension; + this.#deprecationService = deprecationService; + } + + /* internal */ readonly _onMessageEmitter = new Emitter(); + public readonly onDidReceiveMessage: Event = this._onMessageEmitter.event; public dispose() { this._onMessageEmitter.dispose(); } public asWebviewUri(resource: vscode.Uri): vscode.Uri { - this._hasCalledAsWebviewUri = true; - return asWebviewUri(this._initData, this._handle, resource); + this.#hasCalledAsWebviewUri = true; + return asWebviewUri(this.#initData, this.#handle, resource); } public get cspSource(): string { - return this._initData.webviewCspSource - .replace('{{uuid}}', this._handle); + return this.#initData.webviewCspSource + .replace('{{uuid}}', this.#handle); } public get html(): string { this.assertNotDisposed(); - return this._html; + return this.#html; } public set html(value: string) { this.assertNotDisposed(); - if (this._html !== value) { - this._html = value; - if (!this._hasCalledAsWebviewUri && /(["'])vscode-resource:([^\s'"]+?)(["'])/i.test(value)) { - this._hasCalledAsWebviewUri = true; - this._deprecationService.report('Webview vscode-resource: uris', this._extension, + if (this.#html !== value) { + this.#html = value; + if (!this.#hasCalledAsWebviewUri && /(["'])vscode-resource:([^\s'"]+?)(["'])/i.test(value)) { + this.#hasCalledAsWebviewUri = true; + this.#deprecationService.report('Webview vscode-resource: uris', this.#extension, `Please migrate to use the 'webview.asWebviewUri' api instead: https://aka.ms/vscode-webview-use-aswebviewuri`); } - this._proxy.$setHtml(this._handle, value); + this.#proxy.$setHtml(this.#handle, value); } } public get options(): vscode.WebviewOptions { this.assertNotDisposed(); - return this._options; + return this.#options; } public set options(newOptions: vscode.WebviewOptions) { this.assertNotDisposed(); - this._proxy.$setOptions(this._handle, convertWebviewOptions(this._extension, this._workspace, newOptions)); - this._options = newOptions; + this.#proxy.$setOptions(this.#handle, convertWebviewOptions(this.#extension, this.#workspace, newOptions)); + this.#options = newOptions; } public postMessage(message: any): Promise { this.assertNotDisposed(); - return this._proxy.$postMessage(this._handle, message); + return this.#proxy.$postMessage(this.#handle, message); } private assertNotDisposed() { - if (this._isDisposed) { + if (this.#isDisposed) { throw new Error('Webview is disposed'); } } @@ -98,19 +116,18 @@ export class ExtHostWebview implements vscode.Webview { export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPanel { - private readonly _handle: WebviewPanelHandle; - private readonly _proxy: MainThreadWebviewsShape; - private readonly _viewType: string; - private _title: string; - private _iconPath?: IconPath; + readonly #handle: extHostProtocol.WebviewPanelHandle; + readonly #proxy: extHostProtocol.MainThreadWebviewsShape; + readonly #viewType: string; - readonly #options: vscode.WebviewPanelOptions; readonly #webview: ExtHostWebview; + readonly #options: vscode.WebviewPanelOptions; + #title: string; + #iconPath?: IconPath; #viewColumn: vscode.ViewColumn | undefined = undefined; #visible: boolean = true; #active: boolean = true; - #isDisposed: boolean = false; readonly #onDidDispose = this._register(new Emitter()); @@ -120,8 +137,8 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa public readonly onDidChangeViewState = this.#onDidChangeViewState.event; constructor( - handle: WebviewPanelHandle, - proxy: MainThreadWebviewsShape, + handle: extHostProtocol.WebviewPanelHandle, + proxy: extHostProtocol.MainThreadWebviewsShape, viewType: string, title: string, viewColumn: vscode.ViewColumn | undefined, @@ -129,12 +146,12 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa webview: ExtHostWebview ) { super(); - this._handle = handle; - this._proxy = proxy; - this._viewType = viewType; + this.#handle = handle; + this.#proxy = proxy; + this.#viewType = viewType; this.#options = editorOptions; this.#viewColumn = viewColumn; - this._title = title; + this.#title = title; this.#webview = webview; } @@ -145,7 +162,7 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa this.#isDisposed = true; this.#onDidDispose.fire(); - this._proxy.$disposeWebview(this._handle); + this.#proxy.$disposeWebview(this.#handle); this.#webview.dispose(); super.dispose(); @@ -158,33 +175,33 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa get viewType(): string { this.assertNotDisposed(); - return this._viewType; + return this.#viewType; } get title(): string { this.assertNotDisposed(); - return this._title; + return this.#title; } set title(value: string) { this.assertNotDisposed(); - if (this._title !== value) { - this._title = value; - this._proxy.$setTitle(this._handle, value); + if (this.#title !== value) { + this.#title = value; + this.#proxy.$setTitle(this.#handle, value); } } get iconPath(): IconPath | undefined { this.assertNotDisposed(); - return this._iconPath; + return this.#iconPath; } set iconPath(value: IconPath | undefined) { this.assertNotDisposed(); - if (this._iconPath !== value) { - this._iconPath = value; + if (this.#iconPath !== value) { + this.#iconPath = value; - this._proxy.$setIconPath(this._handle, URI.isUri(value) ? { light: value, dark: value } : value); + this.#proxy.$setIconPath(this.#handle, URI.isUri(value) ? { light: value, dark: value } : value); } } @@ -227,12 +244,12 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa public postMessage(message: any): Promise { this.assertNotDisposed(); - return this._proxy.$postMessage(this._handle, message); + return this.#proxy.$postMessage(this.#handle, message); } public reveal(viewColumn?: vscode.ViewColumn, preserveFocus?: boolean): void { this.assertNotDisposed(); - this._proxy.$reveal(this._handle, { + this.#proxy.$reveal(this.#handle, { viewColumn: viewColumn ? typeConverters.ViewColumn.from(viewColumn) : undefined, preserveFocus: !!preserveFocus }); @@ -245,171 +262,23 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa } } -type EditType = unknown; - -class CustomDocument extends Disposable implements vscode.CustomDocument { - - public static create(proxy: MainThreadWebviewsShape, viewType: string, uri: vscode.Uri) { - return Object.seal(new CustomDocument(proxy, viewType, uri)); - } - - // Explicitly initialize all properties as we seal the object after creation! - - #currentEditIndex: number = -1; - #savePoint: number = -1; - readonly #edits: Array = []; - - readonly #proxy: MainThreadWebviewsShape; - readonly #viewType: string; - readonly #uri: vscode.Uri; - - #capabilities: vscode.CustomEditorCapabilities | undefined = undefined; - - private constructor(proxy: MainThreadWebviewsShape, viewType: string, uri: vscode.Uri) { - super(); - this.#proxy = proxy; - this.#viewType = viewType; - this.#uri = uri; - } - - dispose() { - this.#onDidDispose.fire(); - super.dispose(); - } - - //#region Public API - - public get viewType(): string { return this.#viewType; } - - public get uri(): vscode.Uri { return this.#uri; } - - #onDidDispose = this._register(new Emitter()); - public readonly onDidDispose = this.#onDidDispose.event; - - public userData: unknown = undefined; - - //#endregion - - //#region Internal - - /** @internal*/ _setCapabilities(capabilities: vscode.CustomEditorCapabilities) { - if (this.#capabilities) { - throw new Error('Capabilities already provided'); - } - - this.#capabilities = capabilities; - capabilities.editing?.onDidEdit(edit => { - this.pushEdit(edit); - }); - } - - /** @internal*/ _revert() { - const editing = this.getEditingCapability(); - if (this.#currentEditIndex === this.#savePoint) { - return true; - } - - if (this.#currentEditIndex >= this.#savePoint) { - const editsToUndo = this.#edits.slice(this.#savePoint, this.#currentEditIndex); - editing.undoEdits(editsToUndo.reverse()); - } else if (this.#currentEditIndex < this.#savePoint) { - const editsToRedo = this.#edits.slice(this.#currentEditIndex, this.#savePoint); - editing.applyEdits(editsToRedo); - } - - this.#currentEditIndex = this.#savePoint; - this.spliceEdits(); - - this.updateState(); - return true; - } - - /** @internal*/ _undo() { - const editing = this.getEditingCapability(); - if (this.#currentEditIndex < 0) { - // nothing to undo - return; - } - - const undoneEdit = this.#edits[this.#currentEditIndex]; - --this.#currentEditIndex; - editing.undoEdits([undoneEdit]); - this.updateState(); - } - - /** @internal*/ _redo() { - const editing = this.getEditingCapability(); - if (this.#currentEditIndex >= this.#edits.length - 1) { - // nothing to redo - return; - } - - ++this.#currentEditIndex; - const redoneEdit = this.#edits[this.#currentEditIndex]; - editing.applyEdits([redoneEdit]); - this.updateState(); - } - - /** @internal*/ _save() { - return this.getEditingCapability().save(); - } - - /** @internal*/ _saveAs(target: vscode.Uri) { - return this.getEditingCapability().saveAs(target); - } - - /** @internal*/ _backup(cancellation: CancellationToken) { - return this.getEditingCapability().backup(cancellation); - } - - //#endregion - - private pushEdit(edit: EditType) { - this.spliceEdits(edit); - - this.#currentEditIndex = this.#edits.length - 1; - this.updateState(); - } - - private updateState() { - const dirty = this.#edits.length > 0 && this.#savePoint !== this.#currentEditIndex; - this.#proxy.$onDidChangeCustomDocumentState(this.uri, this.viewType, { dirty }); - } - - private spliceEdits(editToInsert?: EditType) { - const start = this.#currentEditIndex + 1; - const toRemove = this.#edits.length - this.#currentEditIndex; - - editToInsert - ? this.#edits.splice(start, toRemove, editToInsert) - : this.#edits.splice(start, toRemove); - } - - private getEditingCapability(): vscode.CustomEditorEditingCapability { - if (!this.#capabilities?.editing) { - throw new Error('Document is not editable'); - } - return this.#capabilities.editing; - } -} - class WebviewDocumentStore { - private readonly _documents = new Map(); + private readonly _documents = new Map(); - public get(viewType: string, resource: vscode.Uri): CustomDocument | undefined { + public get(viewType: string, resource: vscode.Uri): extHostTypes.CustomDocument | undefined { return this._documents.get(this.key(viewType, resource)); } - public add(document: CustomDocument) { - const key = this.key(document.viewType, document.uri); + public add(viewType: string, document: extHostTypes.CustomDocument) { + const key = this.key(viewType, document.uri); if (this._documents.has(key)) { - throw new Error(`Document already exists for viewType:${document.viewType} resource:${document.uri}`); + throw new Error(`Document already exists for viewType:${viewType} resource:${document.uri}`); } this._documents.set(key, document); } - public delete(document: CustomDocument) { - const key = this.key(document.viewType, document.uri); + public delete(viewType: string, document: extHostTypes.CustomDocument) { + const key = this.key(viewType, document.uri); this._documents.delete(key); } @@ -453,18 +322,18 @@ class EditorProviderStore { throw new Error(`Provider for viewType:${viewType} already registered`); } this._providers.set(viewType, { type, extension, provider } as ProviderEntry); - return new VSCodeDisposable(() => this._providers.delete(viewType)); + return new extHostTypes.Disposable(() => this._providers.delete(viewType)); } } -export class ExtHostWebviews implements ExtHostWebviewsShape { +export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape { - private static newHandle(): WebviewPanelHandle { + private static newHandle(): extHostProtocol.WebviewPanelHandle { return generateUuid(); } - private readonly _proxy: MainThreadWebviewsShape; - private readonly _webviewPanels = new Map(); + private readonly _proxy: extHostProtocol.MainThreadWebviewsShape; + private readonly _webviewPanels = new Map(); private readonly _serializers = new Map { + return new extHostTypes.Disposable(() => { this._serializers.delete(viewType); this._proxy.$unregisterSerializer(viewType); }); @@ -532,24 +401,33 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { provider: vscode.CustomEditorProvider | vscode.CustomTextEditorProvider, options: vscode.WebviewPanelOptions | undefined = {} ): vscode.Disposable { - let disposable: vscode.Disposable; + const disposables = new DisposableStore(); if ('resolveCustomTextEditor' in provider) { - disposable = this._editorProviders.addTextProvider(viewType, extension, provider); - this._proxy.$registerTextEditorProvider(toExtensionData(extension), viewType, options); + disposables.add(this._editorProviders.addTextProvider(viewType, extension, provider)); + this._proxy.$registerTextEditorProvider(toExtensionData(extension), viewType, options, { + supportsMove: !!provider.moveCustomTextEditor, + }); } else { - disposable = this._editorProviders.addCustomProvider(viewType, extension, provider); + disposables.add(this._editorProviders.addCustomProvider(viewType, extension, provider)); this._proxy.$registerCustomEditorProvider(toExtensionData(extension), viewType, options); + if (provider.editingDelegate) { + disposables.add(provider.editingDelegate.onDidEdit(e => { + const document = e.document; + const editId = (document as extHostTypes.CustomDocument)._addEdit(e.edit); + this._proxy.$onDidEdit(document.uri, viewType, editId, e.label); + })); + } } - return VSCodeDisposable.from( - disposable, - new VSCodeDisposable(() => { + return extHostTypes.Disposable.from( + disposables, + new extHostTypes.Disposable(() => { this._proxy.$unregisterEditorProvider(viewType); })); } public $onMessage( - handle: WebviewPanelHandle, + handle: extHostProtocol.WebviewPanelHandle, message: any ): void { const panel = this.getWebviewPanel(handle); @@ -559,13 +437,13 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } public $onMissingCsp( - _handle: WebviewPanelHandle, + _handle: extHostProtocol.WebviewPanelHandle, extensionId: string ): void { this._logService.warn(`${extensionId} created a webview without a content security policy: https://aka.ms/vscode-webview-missing-csp`); } - public $onDidChangeWebviewPanelViewStates(newStates: WebviewPanelViewStateData): void { + public $onDidChangeWebviewPanelViewStates(newStates: extHostProtocol.WebviewPanelViewStateData): void { const handles = Object.keys(newStates); // Notify webviews of state changes in the following order: // - Non-visible @@ -598,7 +476,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } } - async $onDidDisposeWebviewPanel(handle: WebviewPanelHandle): Promise { + async $onDidDisposeWebviewPanel(handle: extHostProtocol.WebviewPanelHandle): Promise { const panel = this.getWebviewPanel(handle); if (panel) { panel.dispose(); @@ -607,7 +485,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } async $deserializeWebviewPanel( - webviewHandle: WebviewPanelHandle, + webviewHandle: extHostProtocol.WebviewPanelHandle, viewType: string, title: string, state: any, @@ -626,7 +504,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { await serializer.deserializeWebviewPanel(revivedPanel, state); } - async $createWebviewCustomEditorDocument(resource: UriComponents, viewType: string) { + async $createWebviewCustomEditorDocument(resource: UriComponents, viewType: string, cancellation: CancellationToken) { const entry = this._editorProviders.get(viewType); if (!entry) { throw new Error(`No provider found for '${viewType}'`); @@ -637,12 +515,10 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } const revivedResource = URI.revive(resource); - const document = CustomDocument.create(this._proxy, viewType, revivedResource); - const capabilities = await entry.provider.resolveCustomDocument(document); - document._setCapabilities(capabilities); - this._documents.add(document); + const document = await entry.provider.openCustomDocument(revivedResource, cancellation); + this._documents.add(viewType, document as extHostTypes.CustomDocument); return { - editable: !!capabilities.editing + editable: !!entry.provider.editingDelegate, }; } @@ -657,18 +533,19 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } const revivedResource = URI.revive(resource); - const document = this.getDocument(viewType, revivedResource); - this._documents.delete(document); - document.dispose(); + const document = this.getCustomDocument(viewType, revivedResource); + this._documents.delete(viewType, document); + document._dispose(); } async $resolveWebviewEditor( resource: UriComponents, - handle: WebviewPanelHandle, + handle: extHostProtocol.WebviewPanelHandle, viewType: string, title: string, position: EditorViewColumn, - options: modes.IWebviewOptions & modes.IWebviewPanelOptions + options: modes.IWebviewOptions & modes.IWebviewPanelOptions, + cancellation: CancellationToken, ): Promise { const entry = this._editorProviders.get(viewType); if (!entry) { @@ -684,14 +561,13 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { switch (entry.type) { case WebviewEditorType.Custom: { - const document = this.getDocument(viewType, revivedResource); - return entry.provider.resolveCustomEditor(document, revivedPanel); + const document = this.getCustomDocument(viewType, revivedResource); + return entry.provider.resolveCustomEditor(document, revivedPanel, cancellation); } case WebviewEditorType.Text: { - await this._extHostDocuments.ensureDocumentData(revivedResource); const document = this._extHostDocuments.getDocument(revivedResource); - return entry.provider.resolveCustomTextEditor(document, revivedPanel); + return entry.provider.resolveCustomTextEditor(document, revivedPanel, cancellation); } default: { @@ -700,50 +576,98 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } } - async $undo(resourceComponents: UriComponents, viewType: string): Promise { - const document = this.getDocument(viewType, resourceComponents); - document._undo(); + $disposeEdits(resourceComponents: UriComponents, viewType: string, editIds: number[]): void { + const document = this.getCustomDocument(viewType, resourceComponents); + document._disposeEdits(editIds); } - async $redo(resourceComponents: UriComponents, viewType: string): Promise { - const document = this.getDocument(viewType, resourceComponents); - document._redo(); + async $onMoveCustomEditor(handle: string, newResourceComponents: UriComponents, viewType: string): Promise { + const entry = this._editorProviders.get(viewType); + if (!entry) { + throw new Error(`No provider found for '${viewType}'`); + } + + if (!(entry.provider as vscode.CustomTextEditorProvider).moveCustomTextEditor) { + throw new Error(`Provider does not implement move '${viewType}'`); + } + + const webview = this.getWebviewPanel(handle); + if (!webview) { + throw new Error(`No webview found`); + } + + const resource = URI.revive(newResourceComponents); + const document = this._extHostDocuments.getDocument(resource); + await (entry.provider as vscode.CustomTextEditorProvider).moveCustomTextEditor!(document, webview, CancellationToken.None); } - async $revert(resourceComponents: UriComponents, viewType: string): Promise { - const document = this.getDocument(viewType, resourceComponents); - document._revert(); + async $undo(resourceComponents: UriComponents, viewType: string, editId: number, state: extHostProtocol.CustomDocumentEditState): Promise { + const delegate = this.getEditingDelegate(viewType); + const document = this.getCustomDocument(viewType, resourceComponents); + document._updateEditState(state); + return delegate.undoEdits(document, [document._getEdit(editId)]); } - async $onSave(resourceComponents: UriComponents, viewType: string): Promise { - const document = this.getDocument(viewType, resourceComponents); - document._save(); + async $redo(resourceComponents: UriComponents, viewType: string, editId: number, state: extHostProtocol.CustomDocumentEditState): Promise { + const delegate = this.getEditingDelegate(viewType); + const document = this.getCustomDocument(viewType, resourceComponents); + document._updateEditState(state); + return delegate.applyEdits(document, [document._getEdit(editId)]); } - async $onSaveAs(resourceComponents: UriComponents, viewType: string, targetResource: UriComponents): Promise { - const document = this.getDocument(viewType, resourceComponents); - return document._saveAs(URI.revive(targetResource)); + async $revert(resourceComponents: UriComponents, viewType: string, changes: { undoneEdits: number[], redoneEdits: number[] }, state: extHostProtocol.CustomDocumentEditState): Promise { + const delegate = this.getEditingDelegate(viewType); + const document = this.getCustomDocument(viewType, resourceComponents); + const undoneEdits = changes.undoneEdits.map(id => document._getEdit(id)); + const appliedEdits = changes.redoneEdits.map(id => document._getEdit(id)); + document._updateEditState(state); + return delegate.revert(document, { undoneEdits, appliedEdits }); + } + + async $onSave(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise { + const delegate = this.getEditingDelegate(viewType); + const document = this.getCustomDocument(viewType, resourceComponents); + return delegate.save(document, cancellation); + } + + async $onSaveAs(resourceComponents: UriComponents, viewType: string, targetResource: UriComponents, cancellation: CancellationToken): Promise { + const delegate = this.getEditingDelegate(viewType); + const document = this.getCustomDocument(viewType, resourceComponents); + return delegate.saveAs(document, URI.revive(targetResource), cancellation); } async $backup(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise { - const document = this.getDocument(viewType, resourceComponents); - return document._backup(cancellation); + const delegate = this.getEditingDelegate(viewType); + const document = this.getCustomDocument(viewType, resourceComponents); + return delegate.backup(document, cancellation); } - private getWebviewPanel(handle: WebviewPanelHandle): ExtHostWebviewEditor | undefined { + private getWebviewPanel(handle: extHostProtocol.WebviewPanelHandle): ExtHostWebviewEditor | undefined { return this._webviewPanels.get(handle); } - private getDocument(viewType: string, resource: UriComponents): CustomDocument { + private getCustomDocument(viewType: string, resource: UriComponents): extHostTypes.CustomDocument { const document = this._documents.get(viewType, URI.revive(resource)); if (!document) { throw new Error('No webview editor custom document found'); } return document; } + + private getEditingDelegate(viewType: string): vscode.CustomEditorEditingDelegate { + const entry = this._editorProviders.get(viewType); + if (!entry) { + throw new Error(`No provider found for '${viewType}'`); + } + const delegate = (entry.provider as vscode.CustomEditorProvider).editingDelegate; + if (!delegate) { + throw new Error(`Provider for ${viewType}' does not support editing`); + } + return delegate; + } } -function toExtensionData(extension: IExtensionDescription): WebviewExtensionDescription { +function toExtensionData(extension: IExtensionDescription): extHostProtocol.WebviewExtensionDescription { return { id: extension.identifier, location: extension.extensionLocation }; } diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index 4b5c0c1301f..60336a02067 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -473,7 +473,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac ignoreSymlinks: typeof options.followSymlinks === 'boolean' ? !options.followSymlinks : undefined, disregardIgnoreFiles: typeof options.useIgnoreFiles === 'boolean' ? !options.useIgnoreFiles : undefined, disregardGlobalIgnoreFiles: typeof options.useGlobalIgnoreFiles === 'boolean' ? !options.useGlobalIgnoreFiles : undefined, - disregardExcludeSettings: options.exclude === null, + disregardExcludeSettings: typeof options.useDefaultExcludes === 'boolean' ? !options.useDefaultExcludes : true, fileEncoding: options.encoding, maxResults: options.maxResults, previewOptions, diff --git a/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts b/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts index c3f09c4cdb1..dd2ad38b467 100644 --- a/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts +++ b/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts @@ -7,9 +7,10 @@ import * as nls from 'vs/nls'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import * as strings from 'vs/base/common/strings'; import * as resources from 'vs/base/common/resources'; +import { isString } from 'vs/base/common/types'; interface IJSONValidationExtensionPoint { - fileMatch: string; + fileMatch: string | string[]; url: string; } @@ -25,8 +26,11 @@ const configurationExtPoint = ExtensionsRegistry.registerExtensionPoint { - if (typeof extension.fileMatch !== 'string') { - collector.error(nls.localize('invalid.fileMatch', "'configuration.jsonValidation.fileMatch' must be defined")); + if (!isString(extension.fileMatch) && !(Array.isArray(extension.fileMatch) && extension.fileMatch.every(isString))) { + collector.error(nls.localize('invalid.fileMatch', "'configuration.jsonValidation.fileMatch' must be defined as a string or an array of strings.")); return; } let uri = extension.url; - if (typeof extension.url !== 'string') { + if (!isString(uri)) { collector.error(nls.localize('invalid.url', "'configuration.jsonValidation.url' must be a URL or relative path")); return; } diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index ab474c9825f..a18b4d6e1d3 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -38,12 +38,13 @@ namespace schema { case 'debug/toolbar': return MenuId.DebugToolBar; case 'debug/toolBar': return MenuId.DebugToolBar; case 'menuBar/file': return MenuId.MenubarFileMenu; + case 'menuBar/webNavigation': return MenuId.MenubarWebNavigationMenu; case 'scm/title': return MenuId.SCMTitle; case 'scm/sourceControl': return MenuId.SCMSourceControl; - case 'scm/resourceState/context': return MenuId.SCMResourceContext; + case 'scm/resourceState/context': return MenuId.SCMResourceContext;// case 'scm/resourceFolder/context': return MenuId.SCMResourceFolderContext; case 'scm/resourceGroup/context': return MenuId.SCMResourceGroupContext; - case 'scm/change/title': return MenuId.SCMChangeContext; + case 'scm/change/title': return MenuId.SCMChangeContext;// case 'statusBar/windowIndicator': return MenuId.StatusBarWindowIndicatorMenu; case 'view/title': return MenuId.ViewTitle; case 'view/item/context': return MenuId.ViewItemContext; @@ -51,6 +52,7 @@ namespace schema { case 'comments/commentThread/context': return MenuId.CommentThreadActions; case 'comments/comment/title': return MenuId.CommentTitle; case 'comments/comment/context': return MenuId.CommentActions; + case 'notebook/cell/title': return MenuId.NotebookCellTitle; case 'extension/context': return MenuId.ExtensionContext; case 'timeline/title': return MenuId.TimelineTitle; case 'timeline/item/context': return MenuId.TimelineItemContext; @@ -63,6 +65,7 @@ namespace schema { switch (menuId) { case MenuId.StatusBarWindowIndicatorMenu: case MenuId.MenubarFileMenu: + case MenuId.MenubarWebNavigationMenu: return true; } return false; @@ -162,6 +165,11 @@ namespace schema { type: 'array', items: menuItem }, + 'menuBar/webNavigation': { + description: localize('menus.webNavigation', "The top level navigational menu (web only)"), + type: 'array', + items: menuItem + }, 'scm/title': { description: localize('menus.scmTitle', "The Source Control title menu"), type: 'array', @@ -182,6 +190,16 @@ namespace schema { type: 'array', items: menuItem }, + 'scm/resourceFolder/context': { + description: localize('menus.resourceFolderContext', "The Source Control resource folder context menu"), + type: 'array', + items: menuItem + }, + 'scm/change/title': { + description: localize('menus.changeTitle', "The Source Control inline change menu"), + type: 'array', + items: menuItem + }, 'view/title': { description: localize('view.viewTitle', "The contributed view title menu"), type: 'array', @@ -212,6 +230,11 @@ namespace schema { type: 'array', items: menuItem }, + 'notebook/cell/title': { + description: localize('notebook.cell.title', "The contributed notebook cell title menu"), + type: 'array', + items: menuItem + }, 'extension/context': { description: localize('menus.extensionContext', "The extension context menu"), type: 'array', diff --git a/src/vs/workbench/api/common/shared/semanticTokens.ts b/src/vs/workbench/api/common/shared/semanticTokens.ts deleted file mode 100644 index adce8e3bf58..00000000000 --- a/src/vs/workbench/api/common/shared/semanticTokens.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 { VSBuffer } from 'vs/base/common/buffer'; - -export interface IFullSemanticTokensDto { - id: number; - type: 'full'; - data: Uint32Array; -} - -export interface IDeltaSemanticTokensDto { - id: number; - type: 'delta'; - deltas: { start: number; deleteCount: number; data?: Uint32Array; }[]; -} - -export type ISemanticTokensDto = IFullSemanticTokensDto | IDeltaSemanticTokensDto; - -const enum EncodedSemanticTokensType { - Full = 1, - Delta = 2 -} - -export function encodeSemanticTokensDto(semanticTokens: ISemanticTokensDto): VSBuffer { - const buff = VSBuffer.alloc(encodedSize2(semanticTokens)); - let offset = 0; - buff.writeUInt32BE(semanticTokens.id, offset); offset += 4; - if (semanticTokens.type === 'full') { - buff.writeUInt8(EncodedSemanticTokensType.Full, offset); offset += 1; - buff.writeUInt32BE(semanticTokens.data.length, offset); offset += 4; - for (const uint of semanticTokens.data) { - buff.writeUInt32BE(uint, offset); offset += 4; - } - } else { - buff.writeUInt8(EncodedSemanticTokensType.Delta, offset); offset += 1; - buff.writeUInt32BE(semanticTokens.deltas.length, offset); offset += 4; - for (const delta of semanticTokens.deltas) { - buff.writeUInt32BE(delta.start, offset); offset += 4; - buff.writeUInt32BE(delta.deleteCount, offset); offset += 4; - if (delta.data) { - buff.writeUInt32BE(delta.data.length, offset); offset += 4; - for (const uint of delta.data) { - buff.writeUInt32BE(uint, offset); offset += 4; - } - } else { - buff.writeUInt32BE(0, offset); offset += 4; - } - } - } - return buff; -} - -function encodedSize2(semanticTokens: ISemanticTokensDto): number { - let result = 0; - result += 4; // id - result += 1; // type - if (semanticTokens.type === 'full') { - result += 4; // data length - result += semanticTokens.data.byteLength; - } else { - result += 4; // delta count - for (const delta of semanticTokens.deltas) { - result += 4; // start - result += 4; // deleteCount - result += 4; // data length - if (delta.data) { - result += delta.data.byteLength; - } - } - } - return result; -} - -export function decodeSemanticTokensDto(buff: VSBuffer): ISemanticTokensDto { - let offset = 0; - const id = buff.readUInt32BE(offset); offset += 4; - const type: EncodedSemanticTokensType = buff.readUInt8(offset); offset += 1; - if (type === EncodedSemanticTokensType.Full) { - const length = buff.readUInt32BE(offset); offset += 4; - const data = new Uint32Array(length); - for (let j = 0; j < length; j++) { - data[j] = buff.readUInt32BE(offset); offset += 4; - } - return { - id: id, - type: 'full', - data: data - }; - } - const deltaCount = buff.readUInt32BE(offset); offset += 4; - let deltas: { start: number; deleteCount: number; data?: Uint32Array; }[] = []; - for (let i = 0; i < deltaCount; i++) { - const start = buff.readUInt32BE(offset); offset += 4; - const deleteCount = buff.readUInt32BE(offset); offset += 4; - const length = buff.readUInt32BE(offset); offset += 4; - let data: Uint32Array | undefined; - if (length > 0) { - data = new Uint32Array(length); - for (let j = 0; j < length; j++) { - data[j] = buff.readUInt32BE(offset); offset += 4; - } - } - deltas[i] = { start, deleteCount, data }; - } - return { - id: id, - type: 'delta', - deltas: deltas - }; -} diff --git a/src/vs/workbench/api/common/shared/semanticTokensDto.ts b/src/vs/workbench/api/common/shared/semanticTokensDto.ts new file mode 100644 index 00000000000..a5f3738272d --- /dev/null +++ b/src/vs/workbench/api/common/shared/semanticTokensDto.ts @@ -0,0 +1,152 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { VSBuffer } from 'vs/base/common/buffer'; +import * as platform from 'vs/base/common/platform'; + +export interface IFullSemanticTokensDto { + id: number; + type: 'full'; + data: Uint32Array; +} + +export interface IDeltaSemanticTokensDto { + id: number; + type: 'delta'; + deltas: { start: number; deleteCount: number; data?: Uint32Array; }[]; +} + +export type ISemanticTokensDto = IFullSemanticTokensDto | IDeltaSemanticTokensDto; + +const enum EncodedSemanticTokensType { + Full = 1, + Delta = 2 +} + +function reverseEndianness(arr: Uint8Array): void { + for (let i = 0, len = arr.length; i < len; i += 4) { + // flip bytes 0<->3 and 1<->2 + const b0 = arr[i + 0]; + const b1 = arr[i + 1]; + const b2 = arr[i + 2]; + const b3 = arr[i + 3]; + arr[i + 0] = b3; + arr[i + 1] = b2; + arr[i + 2] = b1; + arr[i + 3] = b0; + } +} + +function toLittleEndianBuffer(arr: Uint32Array): VSBuffer { + const uint8Arr = new Uint8Array(arr.buffer, arr.byteOffset, arr.length * 4); + if (!platform.isLittleEndian()) { + // the byte order must be changed + reverseEndianness(uint8Arr); + } + return VSBuffer.wrap(uint8Arr); +} + +function fromLittleEndianBuffer(buff: VSBuffer): Uint32Array { + const uint8Arr = buff.buffer; + if (!platform.isLittleEndian()) { + // the byte order must be changed + reverseEndianness(uint8Arr); + } + if (uint8Arr.byteOffset % 4 === 0) { + return new Uint32Array(uint8Arr.buffer, uint8Arr.byteOffset); + } else { + // unaligned memory access doesn't work on all platforms + const data = new Uint8Array(uint8Arr.byteLength); + data.set(uint8Arr); + return new Uint32Array(data.buffer, data.byteOffset); + } +} + +export function encodeSemanticTokensDto(semanticTokens: ISemanticTokensDto): VSBuffer { + const dest = new Uint32Array(encodeSemanticTokensDtoSize(semanticTokens)); + let offset = 0; + dest[offset++] = semanticTokens.id; + if (semanticTokens.type === 'full') { + dest[offset++] = EncodedSemanticTokensType.Full; + dest[offset++] = semanticTokens.data.length; + dest.set(semanticTokens.data, offset); offset += semanticTokens.data.length; + } else { + dest[offset++] = EncodedSemanticTokensType.Delta; + dest[offset++] = semanticTokens.deltas.length; + for (const delta of semanticTokens.deltas) { + dest[offset++] = delta.start; + dest[offset++] = delta.deleteCount; + if (delta.data) { + dest[offset++] = delta.data.length; + dest.set(delta.data, offset); offset += delta.data.length; + } else { + dest[offset++] = 0; + } + } + } + return toLittleEndianBuffer(dest); +} + +function encodeSemanticTokensDtoSize(semanticTokens: ISemanticTokensDto): number { + let result = 0; + result += ( + + 1 // id + + 1 // type + ); + if (semanticTokens.type === 'full') { + result += ( + + 1 // data length + + semanticTokens.data.length + ); + } else { + result += ( + + 1 // delta count + ); + result += ( + + 1 // start + + 1 // deleteCount + + 1 // data length + ) * semanticTokens.deltas.length; + for (const delta of semanticTokens.deltas) { + if (delta.data) { + result += delta.data.length; + } + } + } + return result; +} + +export function decodeSemanticTokensDto(_buff: VSBuffer): ISemanticTokensDto { + const src = fromLittleEndianBuffer(_buff); + let offset = 0; + const id = src[offset++]; + const type: EncodedSemanticTokensType = src[offset++]; + if (type === EncodedSemanticTokensType.Full) { + const length = src[offset++]; + const data = src.subarray(offset, offset + length); offset += length; + return { + id: id, + type: 'full', + data: data + }; + } + const deltaCount = src[offset++]; + let deltas: { start: number; deleteCount: number; data?: Uint32Array; }[] = []; + for (let i = 0; i < deltaCount; i++) { + const start = src[offset++]; + const deleteCount = src[offset++]; + const length = src[offset++]; + let data: Uint32Array | undefined; + if (length > 0) { + data = src.subarray(offset, offset + length); offset += length; + } + deltas[i] = { start, deleteCount, data }; + } + return { + id: id, + type: 'delta', + deltas: deltas + }; +} diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index dab72ef96b4..23ec3e88954 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -113,7 +113,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { } else if (args.kind === 'external') { - runInExternalTerminal(args, await this._configurationService.getConfigProvider()); + return runInExternalTerminal(args, await this._configurationService.getConfigProvider()); } return super.$runInTerminal(args); } diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index 79189ba670b..3a02c5ce0b7 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -61,7 +61,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { // Do this when extension service exists, but extensions are not being activated yet. const configProvider = await this._extHostConfiguration.getConfigProvider(); - await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._logService, this._mainThreadTelemetryProxy); + await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._logService, this._mainThreadTelemetryProxy, this._initData); // Use IPC messages to forward console-calls, note that the console is // already patched to use`process.send()` diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index d7809338f81..65c473553dc 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -20,14 +20,21 @@ import { ExtHostVariableResolverService } from 'vs/workbench/api/common/extHostD import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { getSystemShell, detectAvailableShells } from 'vs/workbench/contrib/terminal/node/terminal'; import { getMainProcessParentEnv } from 'vs/workbench/contrib/terminal/node/terminalEnvironment'; -import { BaseExtHostTerminalService, ExtHostTerminal } from 'vs/workbench/api/common/extHostTerminalService'; +import { BaseExtHostTerminalService, ExtHostTerminal, EnvironmentVariableCollection } from 'vs/workbench/api/common/extHostTerminalService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { dispose } from 'vs/base/common/lifecycle'; +import { serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared'; +import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; +import { MergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableCollection'; export class ExtHostTerminalService extends BaseExtHostTerminalService { private _variableResolver: ExtHostVariableResolverService | undefined; private _lastActiveWorkspace: IWorkspaceFolder | undefined; + private _environmentVariableCollections: Map = new Map(); + // TODO: Pull this from main side private _isWorkspaceShellAllowed: boolean = false; @@ -191,6 +198,10 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { baseEnv ); + // Apply extension environment variable collections to the environment + const mergedCollection = new MergedEnvironmentVariableCollection(this._environmentVariableCollections); + mergedCollection.applyToProcessEnvironment(env); + this._proxy.$sendResolvedLaunchConfig(id, shellLaunchConfig); // Fork the process and listen for messages this._logService.debug(`Terminal process launching on ext host`, shellLaunchConfig, initialCwd, cols, rows, env); @@ -215,4 +226,50 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { public $acceptWorkspacePermissionsChanged(isAllowed: boolean): void { this._isWorkspaceShellAllowed = isAllowed; } + + public getEnvironmentVariableCollection(extension: IExtensionDescription, persistent: boolean = false): vscode.EnvironmentVariableCollection { + let collection: EnvironmentVariableCollection | undefined; + if (persistent) { + // If persistent is specified, return the current collection if it exists + collection = this._environmentVariableCollections.get(extension.identifier.value); + + // If persistence changed then create a new collection + if (collection && !collection.persistent) { + collection = undefined; + } + } + + if (!collection) { + // If not persistent, clear out the current collection and create a new one + dispose(this._environmentVariableCollections.get(extension.identifier.value)); + collection = new EnvironmentVariableCollection(persistent); + this._setEnvironmentVariableCollection(extension.identifier.value, collection); + } + + return collection; + } + + private _syncEnvironmentVariableCollection(extensionIdentifier: string, collection: EnvironmentVariableCollection): void { + const serialized = serializeEnvironmentVariableCollection(collection.map); + this._proxy.$setEnvironmentVariableCollection(extensionIdentifier, collection.persistent, serialized.length === 0 ? undefined : serialized); + } + + public $initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void { + collections.forEach(entry => { + const extensionIdentifier = entry[0]; + const collection = new EnvironmentVariableCollection(true, entry[1]); + this._setEnvironmentVariableCollection(extensionIdentifier, collection); + }); + } + + private _setEnvironmentVariableCollection(extensionIdentifier: string, collection: EnvironmentVariableCollection): void { + this._environmentVariableCollections.set(extensionIdentifier, collection); + collection.onDidChangeCollection(() => { + // When any collection value changes send this immediately, this is done to ensure + // following calls to createTerminal will be created with the new environment. It will + // result in more noise by sending multiple updates when called but collections are + // expected to be small. + this._syncEnvironmentVariableCollection(extensionIdentifier, collection!); + }); + } } diff --git a/src/vs/workbench/api/node/extHostTunnelService.ts b/src/vs/workbench/api/node/extHostTunnelService.ts index 6d9dc1cb12a..eaa5aa0743b 100644 --- a/src/vs/workbench/api/node/extHostTunnelService.ts +++ b/src/vs/workbench/api/node/extHostTunnelService.ts @@ -23,8 +23,8 @@ class ExtensionTunnel implements vscode.Tunnel { onDidDispose: Event = this._onDispose.event; constructor( - public readonly remoteAddress: { port: number; host: string; }, - public readonly localAddress: string, + public readonly remoteAddress: { port: number, host: string }, + public readonly localAddress: { port: number, host: string } | string, private readonly _dispose: () => void) { } dispose(): void { @@ -52,6 +52,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe this.registerCandidateFinder(); } } + async openTunnel(forward: TunnelOptions): Promise { const tunnel = await this._proxy.$openTunnel(forward); if (tunnel) { @@ -91,6 +92,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe } else { this._forwardPortProvider = undefined; } + await this._proxy.$tunnelServiceReady(); return toDisposable(() => { this._forwardPortProvider = undefined; }); diff --git a/src/vs/workbench/browser/actions.ts b/src/vs/workbench/browser/actions.ts deleted file mode 100644 index eaa70be455d..00000000000 --- a/src/vs/workbench/browser/actions.ts +++ /dev/null @@ -1,196 +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 { Registry } from 'vs/platform/registry/common/platform'; -import { IAction } from 'vs/base/common/actions'; -import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { ITree, IActionProvider } from 'vs/base/parts/tree/browser/tree'; -import { IInstantiationService, IConstructorSignature0, ServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation'; - -/** - * The action bar contributor allows to add actions to an actionbar in a given context. - */ -export class ActionBarContributor { - - /** - * Returns true if this contributor has actions for the given context. - */ - hasActions(context: unknown): boolean { - return false; - } - - /** - * Returns an array of primary actions in the given context. - */ - getActions(context: unknown): ReadonlyArray { - return []; - } -} - -/** - * Some predefined scopes to contribute actions to - */ -export const Scope = { - - /** - * Actions inside tree widgets. - */ - VIEWER: 'viewer' -}; - -/** - * The ContributableActionProvider leverages the actionbar contribution model to find actions. - */ -export class ContributableActionProvider implements IActionProvider { - private readonly registry: IActionBarRegistry = Registry.as(Extensions.Actionbar); - - private toContext(tree: ITree, element: unknown): unknown { - return { - viewer: tree, - element: element - }; - } - - hasActions(tree: ITree, element: unknown): boolean { - const context = this.toContext(tree, element); - - const contributors = this.registry.getActionBarContributors(Scope.VIEWER); - return contributors.some(contributor => contributor.hasActions(context)); - } - - getActions(tree: ITree, element: unknown): ReadonlyArray { - const actions: IAction[] = []; - const context = this.toContext(tree, element); - - // Collect Actions - const contributors = this.registry.getActionBarContributors(Scope.VIEWER); - for (const contributor of contributors) { - if (contributor.hasActions(context)) { - actions.push(...contributor.getActions(context)); - } - } - - return prepareActions(actions); - } -} - -// Helper function used in parts to massage actions before showing in action areas -export function prepareActions(actions: IAction[]): IAction[] { - if (!actions.length) { - return actions; - } - - // Clean up leading separators - let firstIndexOfAction = -1; - for (let i = 0; i < actions.length; i++) { - if (actions[i].id === Separator.ID) { - continue; - } - - firstIndexOfAction = i; - break; - } - - if (firstIndexOfAction === -1) { - return []; - } - - actions = actions.slice(firstIndexOfAction); - - // Clean up trailing separators - for (let h = actions.length - 1; h >= 0; h--) { - const isSeparator = actions[h].id === Separator.ID; - if (isSeparator) { - actions.splice(h, 1); - } else { - break; - } - } - - // Clean up separator duplicates - let foundAction = false; - for (let k = actions.length - 1; k >= 0; k--) { - const isSeparator = actions[k].id === Separator.ID; - if (isSeparator && !foundAction) { - actions.splice(k, 1); - } else if (!isSeparator) { - foundAction = true; - } else if (isSeparator) { - foundAction = false; - } - } - - return actions; -} - -export const Extensions = { - Actionbar: 'workbench.contributions.actionbar' -}; - -export interface IActionBarRegistry { - /** - * Registers an Actionbar contributor. It will be called to contribute actions to all the action bars - * that are used in the Workbench in the given scope. - */ - registerActionBarContributor(scope: string, ctor: { new(...services: Services): ActionBarContributor }): void; - - /** - * Returns an array of registered action bar contributors known to the workbench for the given scope. - */ - getActionBarContributors(scope: string): ActionBarContributor[]; - - /** - * Starts the registry by providing the required services. - */ - start(accessor: ServicesAccessor): void; -} - -class ActionBarRegistry implements IActionBarRegistry { - private readonly actionBarContributorConstructors: { scope: string; ctor: IConstructorSignature0; }[] = []; - private readonly actionBarContributorInstances: Map = new Map(); - private instantiationService: IInstantiationService | undefined; - - start(accessor: ServicesAccessor): void { - this.instantiationService = accessor.get(IInstantiationService); - - while (this.actionBarContributorConstructors.length > 0) { - const entry = this.actionBarContributorConstructors.shift()!; - this.createActionBarContributor(entry.scope, entry.ctor); - } - } - - private createActionBarContributor(scope: string, ctor: IConstructorSignature0): void { - if (this.instantiationService) { - const instance = this.instantiationService.createInstance(ctor); - let target = this.actionBarContributorInstances.get(scope); - if (!target) { - target = []; - this.actionBarContributorInstances.set(scope, target); - } - target.push(instance); - } - } - - private getContributors(scope: string): ActionBarContributor[] { - return this.actionBarContributorInstances.get(scope) || []; - } - - registerActionBarContributor(scope: string, ctor: IConstructorSignature0): void { - if (!this.instantiationService) { - this.actionBarContributorConstructors.push({ - scope: scope, - ctor: ctor - }); - } else { - this.createActionBarContributor(scope, ctor); - } - } - - getActionBarContributors(scope: string): ActionBarContributor[] { - return this.getContributors(scope).slice(0); - } -} - -Registry.add(Extensions.Actionbar, new ActionBarRegistry()); diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index d4784a72e85..23c29d4c878 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/screencast'; +import 'vs/css!./media/actions'; import { Action } from 'vs/base/common/actions'; import * as nls from 'vs/nls'; @@ -17,7 +17,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Context } from 'vs/platform/contextkey/browser/contextKeyService'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { timeout } from 'vs/base/common/async'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { Registry } from 'vs/platform/registry/common/platform'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; @@ -41,7 +41,7 @@ class InspectContextKeysAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { const disposables = new DisposableStore(); const stylesheet = createStyleSheet(); @@ -85,8 +85,6 @@ class InspectContextKeysAction extends Action { dispose(disposables); }, null, disposables); - - return Promise.resolve(); } } @@ -101,7 +99,7 @@ class ToggleScreencastModeAction extends Action { id: string, label: string, @IKeybindingService private readonly keybindingService: IKeybindingService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @ILayoutService private readonly layoutService: ILayoutService, @IConfigurationService private readonly configurationService: IConfigurationService ) { super(id, label); @@ -116,7 +114,7 @@ class ToggleScreencastModeAction extends Action { const disposables = new DisposableStore(); - const container = this.layoutService.getWorkbenchElement(); + const container = this.layoutService.container; const mouseMarker = append(container, $('.screencast-mouse')); disposables.add(toDisposable(() => mouseMarker.remove())); diff --git a/src/vs/workbench/browser/actions/helpActions.ts b/src/vs/workbench/browser/actions/helpActions.ts index 2ecc5026fc8..283d00258ea 100644 --- a/src/vs/workbench/browser/actions/helpActions.ts +++ b/src/vs/workbench/browser/actions/helpActions.ts @@ -31,13 +31,11 @@ class KeybindingsReferenceAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { const url = isLinux ? this.productService.keyboardShortcutsUrlLinux : isMacintosh ? this.productService.keyboardShortcutsUrlMac : this.productService.keyboardShortcutsUrlWin; if (url) { this.openerService.open(URI.parse(url)); } - - return Promise.resolve(); } } @@ -56,12 +54,10 @@ class OpenDocumentationUrlAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { if (this.productService.documentationUrl) { this.openerService.open(URI.parse(this.productService.documentationUrl)); } - - return Promise.resolve(); } } @@ -80,12 +76,10 @@ class OpenIntroductoryVideosUrlAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { if (this.productService.introductoryVideosUrl) { this.openerService.open(URI.parse(this.productService.introductoryVideosUrl)); } - - return Promise.resolve(); } } @@ -104,12 +98,10 @@ class OpenTipsAndTricksUrlAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { if (this.productService.tipsAndTricksUrl) { this.openerService.open(URI.parse(this.productService.tipsAndTricksUrl)); } - - return Promise.resolve(); } } @@ -151,12 +143,10 @@ class OpenTwitterUrlAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { if (this.productService.twitterUrl) { this.openerService.open(URI.parse(this.productService.twitterUrl)); } - - return Promise.resolve(); } } @@ -175,12 +165,10 @@ class OpenRequestFeatureUrlAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { if (this.productService.requestFeatureUrl) { this.openerService.open(URI.parse(this.productService.requestFeatureUrl)); } - - return Promise.resolve(); } } @@ -199,7 +187,7 @@ class OpenLicenseUrlAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { if (this.productService.licenseUrl) { if (language) { const queryArgChar = this.productService.licenseUrl.indexOf('?') > 0 ? '&' : '?'; @@ -208,8 +196,6 @@ class OpenLicenseUrlAction extends Action { this.openerService.open(URI.parse(this.productService.licenseUrl)); } } - - return Promise.resolve(); } } @@ -228,7 +214,7 @@ class OpenPrivacyStatementUrlAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { if (this.productService.privacyStatementUrl) { if (language) { const queryArgChar = this.productService.privacyStatementUrl.indexOf('?') > 0 ? '&' : '?'; @@ -237,8 +223,6 @@ class OpenPrivacyStatementUrlAction extends Action { this.openerService.open(URI.parse(this.productService.privacyStatementUrl)); } } - - return Promise.resolve(); } } diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index d8e36e8297f..5ad55ee2b3a 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/actions'; - import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { Action } from 'vs/base/common/actions'; @@ -18,17 +16,17 @@ import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { getMenuBarVisibility } from 'vs/platform/windows/common/windows'; import { isWindows, isLinux, isWeb } from 'vs/base/common/platform'; -import { IsMacNativeContext } from 'vs/workbench/browser/contextkeys'; +import { IsMacNativeContext } from 'vs/platform/contextkey/common/contextkeys'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { InEditorZenModeContext, IsCenteredLayoutContext, EditorAreaVisibleContext } from 'vs/workbench/common/editor'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { SideBarVisibleContext } from 'vs/workbench/common/viewlet'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IViewDescriptorService, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewsService, FocusedViewContext, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewDescriptorService, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewsService, FocusedViewContext, ViewContainerLocation, IViewDescriptor } from 'vs/workbench/common/views'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; +import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; const registry = Registry.as(WorkbenchExtensions.WorkbenchActions); const viewCategory = nls.localize('view', "View"); @@ -55,7 +53,7 @@ export class CloseSidebarAction extends Action { } } -registry.registerWorkbenchAction(SyncActionDescriptor.create(CloseSidebarAction, CloseSidebarAction.ID, CloseSidebarAction.LABEL), 'View: Close Side Bar ', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(CloseSidebarAction, CloseSidebarAction.ID, CloseSidebarAction.LABEL), 'View: Close Side Bar', viewCategory); // --- Toggle Activity Bar @@ -237,7 +235,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { export class ToggleEditorVisibilityAction extends Action { static readonly ID = 'workbench.action.toggleEditorVisibility'; - static readonly LABEL = nls.localize('toggleEditor', "Toggle Editor Area"); + static readonly LABEL = nls.localize('toggleEditor', "Toggle Editor Area Visibility"); constructor( id: string, @@ -506,6 +504,37 @@ export class ResetViewLocationsAction extends Action { registry.registerWorkbenchAction(SyncActionDescriptor.create(ResetViewLocationsAction, ResetViewLocationsAction.ID, ResetViewLocationsAction.LABEL), 'View: Reset View Locations', viewCategory); +// --- Toggle View with Command +export abstract class ToggleViewAction extends Action { + + constructor( + id: string, + label: string, + private readonly viewId: string, + protected viewsService: IViewsService, + protected viewDescriptorService: IViewDescriptorService, + protected contextKeyService: IContextKeyService, + private layoutService: IWorkbenchLayoutService, + cssClass?: string + ) { + super(id, label, cssClass); + } + + async run(): Promise { + const focusedViewId = FocusedViewContext.getValue(this.contextKeyService); + + if (focusedViewId === this.viewId) { + if (this.viewDescriptorService.getViewLocation(this.viewId) === ViewContainerLocation.Sidebar) { + this.layoutService.setSideBarHidden(true); + } else { + this.layoutService.setPanelHidden(true); + } + } else { + this.viewsService.openView(this.viewId, true); + } + } +} + // --- Move View with Command export class MoveFocusedViewAction extends Action { static readonly ID = 'workbench.action.moveFocusedView'; @@ -520,7 +549,7 @@ export class MoveFocusedViewAction extends Action { @IContextKeyService private contextKeyService: IContextKeyService, @INotificationService private notificationService: INotificationService, @IActivityBarService private activityBarService: IActivityBarService, - @IViewletService private viewletService: IViewletService + @IPanelService private panelService: IPanelService ) { super(id, label); } @@ -543,38 +572,70 @@ export class MoveFocusedViewAction extends Action { const quickPick = this.quickInputService.createQuickPick(); quickPick.placeholder = nls.localize('moveFocusedView.selectDestination', "Select a Destination for the View"); + quickPick.title = nls.localize('moveFocusedView.title', "View: Move {0}", viewDescriptor.name); + + const items: Array = []; + + items.push({ + type: 'separator', + label: nls.localize('sidebar', "Side Bar") + }); + + const currentContainer = this.viewDescriptorService.getViewContainer(focusedViewId)!; + const currentLocation = this.viewDescriptorService.getViewLocation(focusedViewId)!; + const isViewSolo = this.viewDescriptorService.getViewDescriptors(currentContainer).allViewDescriptors.length === 1; + + if (!(isViewSolo && currentLocation === ViewContainerLocation.Sidebar)) { + items.push({ + id: '_.sidebar.newcontainer', + label: nls.localize('moveFocusedView.newContainerInSidebar', "New Container in Side Bar") + }); + } const pinnedViewlets = this.activityBarService.getPinnedViewletIds(); - const items: Array = this.viewletService.getViewlets() - .filter(viewlet => { - if (viewlet.id === this.viewDescriptorService.getViewContainer(focusedViewId)!.id) { + items.push(...pinnedViewlets + .filter(viewletId => { + if (viewletId === this.viewDescriptorService.getViewContainer(focusedViewId)!.id) { return false; } - return !viewContainerRegistry.get(viewlet.id)!.rejectAddedViews && pinnedViewlets.indexOf(viewlet.id) !== -1; + return !viewContainerRegistry.get(viewletId)!.rejectAddedViews; }) - .map(viewlet => { + .map(viewletId => { return { - id: viewlet.id, - label: viewlet.name, + id: viewletId, + label: viewContainerRegistry.get(viewletId)!.name }; - }); + })); - if (this.viewDescriptorService.getViewLocation(focusedViewId) !== ViewContainerLocation.Panel) { - items.unshift({ - type: 'separator', - label: nls.localize('sidebar', "Side Bar") - }); - items.push({ - type: 'separator', - label: nls.localize('panel', "Panel") - }); + items.push({ + type: 'separator', + label: nls.localize('panel', "Panel") + }); + + if (!(isViewSolo && currentLocation === ViewContainerLocation.Panel)) { items.push({ id: '_.panel.newcontainer', label: nls.localize('moveFocusedView.newContainerInPanel', "New Container in Panel"), }); } + const pinnedPanels = this.panelService.getPinnedPanels(); + items.push(...pinnedPanels + .filter(panel => { + if (panel.id === this.viewDescriptorService.getViewContainer(focusedViewId)!.id) { + return false; + } + + return !viewContainerRegistry.get(panel.id)!.rejectAddedViews; + }) + .map(panel => { + return { + id: panel.id, + label: viewContainerRegistry.get(panel.id)!.name + }; + })); + quickPick.items = items; quickPick.onDidAccept(() => { @@ -583,6 +644,9 @@ export class MoveFocusedViewAction extends Action { if (destination.id === '_.panel.newcontainer') { this.viewDescriptorService.moveViewToLocation(viewDescriptor!, ViewContainerLocation.Panel); this.viewsService.openView(focusedViewId, true); + } else if (destination.id === '_.sidebar.newcontainer') { + this.viewDescriptorService.moveViewToLocation(viewDescriptor!, ViewContainerLocation.Sidebar); + this.viewsService.openView(focusedViewId, true); } else if (destination.id) { this.viewDescriptorService.moveViewsToContainer([viewDescriptor], viewContainerRegistry.get(destination.id)!); this.viewsService.openView(focusedViewId, true); @@ -598,6 +662,49 @@ export class MoveFocusedViewAction extends Action { registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveFocusedViewAction, MoveFocusedViewAction.ID, MoveFocusedViewAction.LABEL), 'View: Move Focused View', viewCategory, FocusedViewContext.notEqualsTo('')); +// --- Reset View Location with Command +export class ResetFocusedViewLocationAction extends Action { + static readonly ID = 'workbench.action.resetFocusedViewLocation'; + static readonly LABEL = nls.localize('resetFocusedViewLocation', "Reset Focused View Location"); + + constructor( + id: string, + label: string, + @IViewDescriptorService private viewDescriptorService: IViewDescriptorService, + @IContextKeyService private contextKeyService: IContextKeyService, + @INotificationService private notificationService: INotificationService, + @IViewsService private viewsService: IViewsService + ) { + super(id, label); + } + + async run(): Promise { + const focusedViewId = FocusedViewContext.getValue(this.contextKeyService); + + let viewDescriptor: IViewDescriptor | null = null; + if (focusedViewId !== undefined && focusedViewId.trim() !== '') { + viewDescriptor = this.viewDescriptorService.getViewDescriptor(focusedViewId); + } + + if (!viewDescriptor) { + this.notificationService.error(nls.localize('resetFocusedView.error.noFocusedView', "There is no view currently focused.")); + return; + } + + const defaultContainer = this.viewDescriptorService.getDefaultContainer(viewDescriptor.id); + if (!defaultContainer || defaultContainer === this.viewDescriptorService.getViewContainer(viewDescriptor.id)) { + return; + } + + this.viewDescriptorService.moveViewsToContainer([viewDescriptor], defaultContainer); + this.viewsService.openView(viewDescriptor.id, true); + + } +} + +registry.registerWorkbenchAction(SyncActionDescriptor.create(ResetFocusedViewLocationAction, ResetFocusedViewLocationAction.ID, ResetFocusedViewLocationAction.LABEL), 'View: Reset Focused View Location', viewCategory, FocusedViewContext.notEqualsTo('')); + + // --- Resize View export abstract class BaseResizeViewAction extends Action { @@ -645,9 +752,8 @@ export class IncreaseViewSizeAction extends BaseResizeViewAction { super(id, label, layoutService); } - run(): Promise { + async run(): Promise { this.resizePart(BaseResizeViewAction.RESIZE_INCREMENT); - return Promise.resolve(true); } } @@ -665,9 +771,8 @@ export class DecreaseViewSizeAction extends BaseResizeViewAction { super(id, label, layoutService); } - run(): Promise { + async run(): Promise { this.resizePart(-BaseResizeViewAction.RESIZE_INCREMENT); - return Promise.resolve(true); } } diff --git a/src/vs/workbench/browser/actions/listCommands.ts b/src/vs/workbench/browser/actions/listCommands.ts index a2754c7be3e..1726376be57 100644 --- a/src/vs/workbench/browser/actions/listCommands.ts +++ b/src/vs/workbench/browser/actions/listCommands.ts @@ -11,17 +11,11 @@ import { WorkbenchListFocusContextKey, IListService, WorkbenchListSupportsMultiS import { PagedList } from 'vs/base/browser/ui/list/listPaging'; import { range } from 'vs/base/common/arrays'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { ITree } from 'vs/base/parts/tree/browser/tree'; import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; import { DataTree } from 'vs/base/browser/ui/tree/dataTree'; import { ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; - -function isLegacyTree(widget: ListWidget): widget is ITree { - return widget instanceof Tree; -} function ensureDOMFocus(widget: ListWidget | undefined): void { // it can happen that one of the commands is executed while @@ -51,7 +45,7 @@ function focusDown(accessor: ServicesAccessor, arg2?: number, loop: boolean = fa } } - // ObjectTree + // Tree else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const tree = focused; @@ -63,14 +57,6 @@ function focusDown(accessor: ServicesAccessor, arg2?: number, loop: boolean = fa tree.reveal(listFocus[0]); } } - - // Tree - else if (focused) { - const tree = focused; - - tree.focusNext(count, { origin: 'keyboard' }); - tree.reveal(tree.getFocus()); - } } KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -85,7 +71,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: (accessor, arg2) => focusDown(accessor, arg2) }); -function expandMultiSelection(focused: List | PagedList | ITree | ObjectTree | DataTree | AsyncDataTree, previousFocus: unknown): void { +function expandMultiSelection(focused: List | PagedList | ObjectTree | DataTree | AsyncDataTree, previousFocus: unknown): void { // List if (focused instanceof List || focused instanceof PagedList) { @@ -102,7 +88,7 @@ function expandMultiSelection(focused: List | PagedList | ITre } } - // ObjectTree + // Tree else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const list = focused; @@ -121,19 +107,6 @@ function expandMultiSelection(focused: List | PagedList | ITre list.setSelection(selection.concat(focus), fakeKeyboardEvent); } } - - // Tree - else if (focused) { - const tree = focused; - - const focus = tree.getFocus(); - const selection = tree.getSelection(); - if (selection && selection.indexOf(focus) >= 0) { - tree.setSelection(selection.filter(s => s !== previousFocus)); - } else { - tree.setSelection(selection.concat(focus)); - } - } } KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -144,7 +117,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: (accessor, arg2) => { const focused = accessor.get(IListService).lastFocusedList; - // List + // List / Tree if (focused instanceof List || focused instanceof PagedList || focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const list = focused; @@ -155,18 +128,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ // Then adjust selection expandMultiSelection(focused, previousFocus); } - - // Tree - else if (focused) { - const tree = focused; - - // Focus down first - const previousFocus = tree.getFocus(); - focusDown(accessor, arg2); - - // Then adjust selection - expandMultiSelection(focused, previousFocus); - } } }); @@ -188,7 +149,7 @@ function focusUp(accessor: ServicesAccessor, arg2?: number, loop: boolean = fals } } - // ObjectTree + // Tree else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const tree = focused; @@ -200,14 +161,6 @@ function focusUp(accessor: ServicesAccessor, arg2?: number, loop: boolean = fals tree.reveal(listFocus[0]); } } - - // Tree - else if (focused) { - const tree = focused; - - tree.focusPrevious(count, { origin: 'keyboard' }); - tree.reveal(tree.getFocus()); - } } KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -230,7 +183,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: (accessor, arg2) => { const focused = accessor.get(IListService).lastFocusedList; - // List + // List / Tree if (focused instanceof List || focused instanceof PagedList || focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const list = focused; @@ -241,18 +194,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ // Then adjust selection expandMultiSelection(focused, previousFocus); } - - // Tree - else if (focused) { - const tree = focused; - - // Focus up first - const previousFocus = tree.getFocus(); - focusUp(accessor, arg2); - - // Then adjust selection - expandMultiSelection(focused, previousFocus); - } } }); @@ -289,19 +230,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ tree.reveal(parent); } } - } else { - const tree = focused; - const focus = tree.getFocus(); - - tree.collapse(focus).then(didCollapse => { - if (focus && !didCollapse) { - tree.focusParent({ origin: 'keyboard' }); - - return tree.reveal(tree.getFocus()); - } - - return undefined; - }); } } } @@ -350,9 +278,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ tree.setFocus([parent], fakeKeyboardEvent); tree.reveal(parent); } - } else { - const tree = focused; - tree.focusParent({ origin: 'keyboard' }); } } }); @@ -416,19 +341,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } } }); - } else { - const tree = focused; - const focus = tree.getFocus(); - - tree.expand(focus).then(didExpand => { - if (focus && !didExpand) { - tree.focusFirstChild({ origin: 'keyboard' }); - - return tree.reveal(tree.getFocus()); - } - - return undefined; - }); } } } @@ -453,7 +365,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ list.reveal(list.getFocus()[0]); } - // ObjectTree + // Tree else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const list = focused; @@ -461,14 +373,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ list.focusPreviousPage(fakeKeyboardEvent); list.reveal(list.getFocus()[0]); } - - // Tree - else if (focused) { - const tree = focused; - - tree.focusPreviousPage({ origin: 'keyboard' }); - tree.reveal(tree.getFocus()); - } } }); @@ -491,7 +395,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ list.reveal(list.getFocus()[0]); } - // ObjectTree + // Tree else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const list = focused; @@ -499,14 +403,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ list.focusNextPage(fakeKeyboardEvent); list.reveal(list.getFocus()[0]); } - - // Tree - else if (focused) { - const tree = focused; - - tree.focusNextPage({ origin: 'keyboard' }); - tree.reveal(tree.getFocus()); - } } }); @@ -540,7 +436,7 @@ function listFocusFirst(accessor: ServicesAccessor, options?: { fromFocused: boo list.reveal(0); } - // ObjectTree + // Tree else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const tree = focused; const fakeKeyboardEvent = new KeyboardEvent('keydown'); @@ -552,14 +448,6 @@ function listFocusFirst(accessor: ServicesAccessor, options?: { fromFocused: boo tree.reveal(focus[0]); } } - - // Tree - else if (focused) { - const tree = focused; - - tree.focusFirst({ origin: 'keyboard' }, options?.fromFocused ? tree.getFocus() : undefined); - tree.reveal(tree.getFocus()); - } } KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -592,7 +480,7 @@ function listFocusLast(accessor: ServicesAccessor, options?: { fromFocused: bool list.reveal(list.length - 1); } - // ObjectTree + // Tree else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const tree = focused; const fakeKeyboardEvent = new KeyboardEvent('keydown'); @@ -604,14 +492,6 @@ function listFocusLast(accessor: ServicesAccessor, options?: { fromFocused: bool tree.reveal(focus[0]); } } - - // Tree - else if (focused) { - const tree = focused; - - tree.focusLast({ origin: 'keyboard' }, options?.fromFocused ? tree.getFocus() : undefined); - tree.reveal(tree.getFocus()); - } } KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -625,18 +505,18 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ }, handler: (accessor) => { const focused = accessor.get(IListService).lastFocusedList; + const fakeKeyboardEvent = getSelectionKeyboardEvent('keydown', false); // List if (focused instanceof List || focused instanceof PagedList) { const list = focused; - list.setSelection(list.getFocus()); - list.open(list.getFocus()); + list.setSelection(list.getFocus(), fakeKeyboardEvent); + list.open(list.getFocus(), fakeKeyboardEvent); } - // ObjectTree + // Tree else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const list = focused; - const fakeKeyboardEvent = getSelectionKeyboardEvent('keydown', false); const focus = list.getFocus(); if (focus.length > 0) { @@ -656,16 +536,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ list.setSelection(focus, fakeKeyboardEvent); list.open(focus, fakeKeyboardEvent); } - - // Tree - else if (focused) { - const tree = focused; - const focus = tree.getFocus(); - - if (focus) { - tree.setSelection([focus], { origin: 'keyboard' }); - } - } } }); @@ -744,7 +614,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: (accessor) => { const widget = accessor.get(IListService).lastFocusedList; - if (!widget || isLegacyTree(widget)) { + if (!widget) { return; } @@ -784,13 +654,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } tree.toggleCollapsed(focus[0]); - } else { - const tree = focused; - const focus = tree.getFocus(); - - if (focus) { - tree.toggleExpansion(focus); - } } } } @@ -812,7 +675,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ list.setFocus([]); } - // ObjectTree + // Tree else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const list = focused; const fakeKeyboardEvent = new KeyboardEvent('keydown'); @@ -820,14 +683,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ list.setSelection([], fakeKeyboardEvent); list.setFocus([], fakeKeyboardEvent); } - - // Tree - else if (focused) { - const tree = focused; - - tree.clearSelection({ origin: 'keyboard' }); - tree.clearFocus({ origin: 'keyboard' }); - } } }); @@ -842,7 +697,7 @@ CommandsRegistry.registerCommand({ list.toggleKeyboardNavigation(); } - // ObjectTree + // Tree else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const tree = focused; tree.toggleKeyboardNavigation(); @@ -860,7 +715,7 @@ CommandsRegistry.registerCommand({ // TODO@joao } - // ObjectTree + // Tree else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const tree = focused; tree.updateOptions({ filterOnType: !tree.filterOnType }); @@ -876,7 +731,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: accessor => { const focused = accessor.get(IListService).lastFocusedList; - if (!focused || isLegacyTree(focused)) { + if (!focused) { return; } @@ -892,7 +747,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: accessor => { const focused = accessor.get(IListService).lastFocusedList; - if (!focused || isLegacyTree(focused)) { + if (!focused) { return; } @@ -901,13 +756,13 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ }); KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: '84256', + id: 'list.scrollLeft', weight: KeybindingWeight.WorkbenchContrib, when: WorkbenchListFocusContextKey, handler: accessor => { const focused = accessor.get(IListService).lastFocusedList; - if (!focused || isLegacyTree(focused)) { + if (!focused) { return; } @@ -922,7 +777,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: accessor => { const focused = accessor.get(IListService).lastFocusedList; - if (!focused || isLegacyTree(focused)) { + if (!focused) { return; } diff --git a/src/vs/workbench/browser/actions/media/actions.css b/src/vs/workbench/browser/actions/media/actions.css index a4a092d8349..cd7a0367ea6 100644 --- a/src/vs/workbench/browser/actions/media/actions.css +++ b/src/vs/workbench/browser/actions/media/actions.css @@ -2,3 +2,53 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + +.quick-input-list .quick-input-list-entry.has-actions:hover .quick-input-list-entry-action-bar .action-label.dirty-workspace::before { + content: "\ea76"; /* Close icon flips between black dot and "X" for dirty workspaces */ +} + +.monaco-workbench .screencast-mouse { + position: absolute; + border: 2px solid red; + border-radius: 20px; + width: 20px; + height: 20px; + top: 0; + left: 0; + z-index: 100000; + content: ' '; + pointer-events: none; + display: none; +} + +.monaco-workbench .screencast-keyboard { + position: absolute; + background-color: rgba(0, 0, 0 ,0.5); + width: 100%; + height: 100px; + bottom: 20%; + left: 0; + z-index: 100000; + pointer-events: none; + color: #eee; + line-height: 100px; + text-align: center; + font-size: 56px; + transition: opacity 0.3s ease-out; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.monaco-workbench .screencast-keyboard:empty { + opacity: 0; +} + +.monaco-workbench .screencast-keyboard > .key { + padding: 0 8px; + box-shadow: inset 0 -3px 0 hsla(0,0%,73%,.4); + margin-right: 6px; + border: 1px solid hsla(0,0%,80%,.4); + border-radius: 5px; + background-color: rgba(255, 255, 255, 0.05); +} diff --git a/src/vs/workbench/browser/actions/media/screencast.css b/src/vs/workbench/browser/actions/media/screencast.css deleted file mode 100644 index b4b71b36726..00000000000 --- a/src/vs/workbench/browser/actions/media/screencast.css +++ /dev/null @@ -1,50 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-workbench .screencast-mouse { - position: absolute; - border: 2px solid red; - border-radius: 20px; - width: 20px; - height: 20px; - top: 0; - left: 0; - z-index: 100000; - content: ' '; - pointer-events: none; - display: none; -} - -.monaco-workbench .screencast-keyboard { - position: absolute; - background-color: rgba(0, 0, 0 ,0.5); - width: 100%; - height: 100px; - bottom: 20%; - left: 0; - z-index: 100000; - pointer-events: none; - color: #eee; - line-height: 100px; - text-align: center; - font-size: 56px; - transition: opacity 0.3s ease-out; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.monaco-workbench .screencast-keyboard:empty { - opacity: 0; -} - -.monaco-workbench .screencast-keyboard > .key { - padding: 0 8px; - box-shadow: inset 0 -3px 0 hsla(0,0%,73%,.4); - margin-right: 6px; - border: 1px solid hsla(0,0%,80%,.4); - border-radius: 5px; - background-color: rgba(255, 255, 255, 0.05); -} diff --git a/src/vs/workbench/browser/actions/quickAccessActions.ts b/src/vs/workbench/browser/actions/quickAccessActions.ts new file mode 100644 index 00000000000..f8a2c41cbae --- /dev/null +++ b/src/vs/workbench/browser/actions/quickAccessActions.ts @@ -0,0 +1,251 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; +import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; +import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { IQuickInputService, ItemActivation } from 'vs/platform/quickinput/common/quickInput'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { Action } from 'vs/base/common/actions'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { inQuickPickContext, defaultQuickAccessContext, getQuickNavigateHandler } from 'vs/workbench/browser/quickaccess'; + +//#region Quick access management commands and keys + +const globalQuickAccessKeybinding = { + primary: KeyMod.CtrlCmd | KeyCode.KEY_P, + secondary: [KeyMod.CtrlCmd | KeyCode.KEY_E], + mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_P, secondary: undefined } +}; + +const QUICKACCESS_ACTION_ID = 'workbench.action.quickOpen'; + +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { id: QUICKACCESS_ACTION_ID, title: { value: localize('quickOpen', "Go to File..."), original: 'Go to File...' } } +}); + +KeybindingsRegistry.registerKeybindingRule({ + id: QUICKACCESS_ACTION_ID, + weight: KeybindingWeight.WorkbenchContrib, + when: undefined, + primary: globalQuickAccessKeybinding.primary, + secondary: globalQuickAccessKeybinding.secondary, + mac: globalQuickAccessKeybinding.mac +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'workbench.action.closeQuickOpen', + weight: KeybindingWeight.WorkbenchContrib, + when: inQuickPickContext, + primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], + handler: accessor => { + const quickInputService = accessor.get(IQuickInputService); + return quickInputService.cancel(); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'workbench.action.acceptSelectedQuickOpenItem', + weight: KeybindingWeight.WorkbenchContrib, + when: inQuickPickContext, + primary: 0, + handler: accessor => { + const quickInputService = accessor.get(IQuickInputService); + return quickInputService.accept(); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'workbench.action.alternativeAcceptSelectedQuickOpenItem', + weight: KeybindingWeight.WorkbenchContrib, + when: inQuickPickContext, + primary: 0, + handler: accessor => { + const quickInputService = accessor.get(IQuickInputService); + return quickInputService.accept({ ctrlCmd: true, alt: false }); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'workbench.action.focusQuickOpen', + weight: KeybindingWeight.WorkbenchContrib, + when: inQuickPickContext, + primary: 0, + handler: accessor => { + const quickInputService = accessor.get(IQuickInputService); + quickInputService.focus(); + } +}); + +const quickAccessNavigateNextInFilePickerId = 'workbench.action.quickOpenNavigateNextInFilePicker'; +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: quickAccessNavigateNextInFilePickerId, + weight: KeybindingWeight.WorkbenchContrib + 50, + handler: getQuickNavigateHandler(quickAccessNavigateNextInFilePickerId, true), + when: defaultQuickAccessContext, + primary: globalQuickAccessKeybinding.primary, + secondary: globalQuickAccessKeybinding.secondary, + mac: globalQuickAccessKeybinding.mac +}); + +const quickAccessNavigatePreviousInFilePickerId = 'workbench.action.quickOpenNavigatePreviousInFilePicker'; +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: quickAccessNavigatePreviousInFilePickerId, + weight: KeybindingWeight.WorkbenchContrib + 50, + handler: getQuickNavigateHandler(quickAccessNavigatePreviousInFilePickerId, false), + when: defaultQuickAccessContext, + primary: globalQuickAccessKeybinding.primary | KeyMod.Shift, + secondary: [globalQuickAccessKeybinding.secondary[0] | KeyMod.Shift], + mac: { + primary: globalQuickAccessKeybinding.mac.primary | KeyMod.Shift, + secondary: undefined + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'workbench.action.quickPickManyToggle', + weight: KeybindingWeight.WorkbenchContrib, + when: inQuickPickContext, + primary: 0, + handler: accessor => { + const quickInputService = accessor.get(IQuickInputService); + quickInputService.toggle(); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'workbench.action.quickInputBack', + weight: KeybindingWeight.WorkbenchContrib + 50, + when: inQuickPickContext, + primary: 0, + win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, + mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS }, + linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS }, + handler: accessor => { + const quickInputService = accessor.get(IQuickInputService); + quickInputService.back(); + } +}); + +CommandsRegistry.registerCommand({ + id: QUICKACCESS_ACTION_ID, + handler: async function (accessor: ServicesAccessor, prefix: unknown) { + const quickInputService = accessor.get(IQuickInputService); + + quickInputService.quickAccess.show(typeof prefix === 'string' ? prefix : undefined); + }, + description: { + description: `Quick access`, + args: [{ + name: 'prefix', + schema: { + 'type': 'string' + } + }] + } +}); + +CommandsRegistry.registerCommand('workbench.action.quickOpenPreviousEditor', async function (accessor: ServicesAccessor, prefix: string | null = null) { + const quickInputService = accessor.get(IQuickInputService); + + quickInputService.quickAccess.show('', { itemActivation: ItemActivation.SECOND }); +}); + +//#endregion + +//#region Workbench actions + +export class BaseQuickAccessNavigateAction extends Action { + + constructor( + id: string, + label: string, + private next: boolean, + private quickNavigate: boolean, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IKeybindingService private readonly keybindingService: IKeybindingService + ) { + super(id, label); + } + + async run(): Promise { + const keys = this.keybindingService.lookupKeybindings(this.id); + const quickNavigate = this.quickNavigate ? { keybindings: keys } : undefined; + + this.quickInputService.navigate(this.next, quickNavigate); + } +} + +export class QuickAccessNavigateNextAction extends BaseQuickAccessNavigateAction { + + static readonly ID = 'workbench.action.quickOpenNavigateNext'; + static readonly LABEL = localize('quickNavigateNext', "Navigate Next in Quick Open"); + + constructor( + id: string, + label: string, + @IQuickInputService quickInputService: IQuickInputService, + @IKeybindingService keybindingService: IKeybindingService + ) { + super(id, label, true, true, quickInputService, keybindingService); + } +} + +class QuickAccessNavigatePreviousAction extends BaseQuickAccessNavigateAction { + + static readonly ID = 'workbench.action.quickOpenNavigatePrevious'; + static readonly LABEL = localize('quickNavigatePrevious', "Navigate Previous in Quick Open"); + + constructor( + id: string, + label: string, + @IQuickInputService quickInputService: IQuickInputService, + @IKeybindingService keybindingService: IKeybindingService + ) { + super(id, label, false, true, quickInputService, keybindingService); + } +} + +class QuickAccessSelectNextAction extends BaseQuickAccessNavigateAction { + + static readonly ID = 'workbench.action.quickOpenSelectNext'; + static readonly LABEL = localize('quickSelectNext', "Select Next in Quick Open"); + + constructor( + id: string, + label: string, + @IQuickInputService quickInputService: IQuickInputService, + @IKeybindingService keybindingService: IKeybindingService + ) { + super(id, label, true, false, quickInputService, keybindingService); + } +} + +class QuickAccessSelectPreviousAction extends BaseQuickAccessNavigateAction { + + static readonly ID = 'workbench.action.quickOpenSelectPrevious'; + static readonly LABEL = localize('quickSelectPrevious', "Select Previous in Quick Open"); + + constructor( + id: string, + label: string, + @IQuickInputService quickInputService: IQuickInputService, + @IKeybindingService keybindingService: IKeybindingService + ) { + super(id, label, false, false, quickInputService, keybindingService); + } +} + +const registry = Registry.as(ActionExtensions.WorkbenchActions); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickAccessSelectNextAction, QuickAccessSelectNextAction.ID, QuickAccessSelectNextAction.LABEL, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_N } }, inQuickPickContext, KeybindingWeight.WorkbenchContrib + 50), 'Select Next in Quick Open'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickAccessSelectPreviousAction, QuickAccessSelectPreviousAction.ID, QuickAccessSelectPreviousAction.LABEL, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_P } }, inQuickPickContext, KeybindingWeight.WorkbenchContrib + 50), 'Select Previous in Quick Open'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickAccessNavigateNextAction, QuickAccessNavigateNextAction.ID, QuickAccessNavigateNextAction.LABEL), 'Navigate Next in Quick Open'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickAccessNavigatePreviousAction, QuickAccessNavigatePreviousAction.ID, QuickAccessNavigatePreviousAction.LABEL), 'Navigate Previous in Quick Open'); + +//#endregion diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index 707ba426404..c15ebec6a93 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/actions'; - import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { IWindowOpenable } from 'vs/platform/windows/common/windows'; @@ -12,35 +10,47 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IsFullscreenContext, IsDevelopmentContext, IsMacNativeContext } from 'vs/workbench/browser/contextkeys'; +import { IsFullscreenContext } from 'vs/workbench/browser/contextkeys'; +import { IsMacNativeContext, IsDevelopmentContext } from 'vs/platform/contextkey/common/contextkeys'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IQuickInputButton, IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputButton, IQuickInputService, IQuickPickSeparator, IKeyMods, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ILabelService } from 'vs/platform/label/common/label'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { IRecentWorkspace, IRecentFolder, IRecentFile, IRecent, isRecentFolder, isRecentWorkspace, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; +import { IRecent, isRecentFolder, isRecentWorkspace, IWorkspacesService, IWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { URI } from 'vs/base/common/uri'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { FileKind } from 'vs/platform/files/common/files'; import { splitName } from 'vs/base/common/labels'; -import { IKeyMods } from 'vs/base/parts/quickopen/common/quickOpen'; import { isMacintosh } from 'vs/base/common/platform'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { inQuickOpenContext, getQuickNavigateHandler } from 'vs/workbench/browser/parts/quickopen/quickopen'; +import { inQuickPickContext, getQuickNavigateHandler } from 'vs/workbench/browser/quickaccess'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { ResourceMap } from 'vs/base/common/map'; export const inRecentFilesPickerContextKey = 'inRecentFilesPicker'; +interface IRecentlyOpenedPick extends IQuickPickItem { + resource: URI, + openable: IWindowOpenable; +} + abstract class BaseOpenRecentAction extends Action { - private removeFromRecentlyOpened: IQuickInputButton = { + private readonly removeFromRecentlyOpened: IQuickInputButton = { iconClass: 'codicon-close', tooltip: nls.localize('remove', "Remove from Recently Opened") }; + private readonly dirtyRecentlyOpened: IQuickInputButton = { + iconClass: 'dirty-workspace codicon-circle-filled', + tooltip: nls.localize('dirtyRecentlyOpened', "Workspace With Dirty Files"), + alwaysVisible: true + }; + constructor( id: string, label: string, @@ -51,7 +61,8 @@ abstract class BaseOpenRecentAction extends Action { private keybindingService: IKeybindingService, private modelService: IModelService, private modeService: IModeService, - private hostService: IHostService + private hostService: IHostService, + private dialogService: IDialogService ) { super(id, label); } @@ -59,61 +70,53 @@ abstract class BaseOpenRecentAction extends Action { protected abstract isQuickNavigate(): boolean; async run(): Promise { - const { workspaces, files } = await this.workspacesService.getRecentlyOpened(); + const recentlyOpened = await this.workspacesService.getRecentlyOpened(); + const dirtyWorkspacesAndFolders = await this.workspacesService.getDirtyWorkspaces(); - this.openRecent(workspaces, files); - } + // Identify all folders and workspaces with dirty files + const dirtyFolders = new ResourceMap(); + const dirtyWorkspaces = new ResourceMap(); + for (const dirtyWorkspace of dirtyWorkspacesAndFolders) { + if (URI.isUri(dirtyWorkspace)) { + dirtyFolders.set(dirtyWorkspace, true); + } else { + dirtyWorkspaces.set(dirtyWorkspace.configPath, dirtyWorkspace); + } + } - private async openRecent(recentWorkspaces: Array, recentFiles: IRecentFile[]): Promise { - - const toPick = (recent: IRecent, labelService: ILabelService, buttons: IQuickInputButton[] | undefined) => { - let openable: IWindowOpenable | undefined; - let iconClasses: string[]; - let fullLabel: string | undefined; - let resource: URI | undefined; - - // Folder + // Identify all recently opened folders and workspaces + const recentFolders = new ResourceMap(); + const recentWorkspaces = new ResourceMap(); + for (const recent of recentlyOpened.workspaces) { if (isRecentFolder(recent)) { - resource = recent.folderUri; - iconClasses = getIconClasses(this.modelService, this.modeService, resource, FileKind.FOLDER); - openable = { folderUri: resource }; - fullLabel = recent.label || labelService.getWorkspaceLabel(resource, { verbose: true }); + recentFolders.set(recent.folderUri, true); + } else { + recentWorkspaces.set(recent.workspace.configPath, recent.workspace); } + } - // Workspace - else if (isRecentWorkspace(recent)) { - resource = recent.workspace.configPath; - iconClasses = getIconClasses(this.modelService, this.modeService, resource, FileKind.ROOT_FOLDER); - openable = { workspaceUri: resource }; - fullLabel = recent.label || labelService.getWorkspaceLabel(recent.workspace, { verbose: true }); + // Fill in all known recently opened workspaces + const workspacePicks: IRecentlyOpenedPick[] = []; + for (const recent of recentlyOpened.workspaces) { + const isDirty = isRecentFolder(recent) ? dirtyFolders.has(recent.folderUri) : dirtyWorkspaces.has(recent.workspace.configPath); + + workspacePicks.push(this.toQuickPick(recent, isDirty)); + } + + // Fill any backup workspace that is not yet shown at the end + for (const dirtyWorkspaceOrFolder of dirtyWorkspacesAndFolders) { + if (URI.isUri(dirtyWorkspaceOrFolder) && !recentFolders.has(dirtyWorkspaceOrFolder)) { + workspacePicks.push(this.toQuickPick({ folderUri: dirtyWorkspaceOrFolder }, true)); + } else if (isWorkspaceIdentifier(dirtyWorkspaceOrFolder) && !recentWorkspaces.has(dirtyWorkspaceOrFolder.configPath)) { + workspacePicks.push(this.toQuickPick({ workspace: dirtyWorkspaceOrFolder }, true)); } + } - // File - else { - resource = recent.fileUri; - iconClasses = getIconClasses(this.modelService, this.modeService, resource, FileKind.FILE); - openable = { fileUri: resource }; - fullLabel = recent.label || labelService.getUriLabel(resource); - } - - const { name, parentPath } = splitName(fullLabel); - - return { - iconClasses, - label: name, - description: parentPath, - buttons, - openable, - resource - }; - }; - - const workspacePicks = recentWorkspaces.map(workspace => toPick(workspace, this.labelService, !this.isQuickNavigate() ? [this.removeFromRecentlyOpened] : undefined)); - const filePicks = recentFiles.map(p => toPick(p, this.labelService, !this.isQuickNavigate() ? [this.removeFromRecentlyOpened] : undefined)); + const filePicks = recentlyOpened.files.map(p => this.toQuickPick(p, false)); // focus second entry if the first recent workspace is the current workspace - const firstEntry = recentWorkspaces[0]; - let autoFocusSecondEntry: boolean = firstEntry && this.contextService.isCurrentWorkspace(isRecentWorkspace(firstEntry) ? firstEntry.workspace : firstEntry.folderUri); + const firstEntry = recentlyOpened.workspaces[0]; + const autoFocusSecondEntry: boolean = firstEntry && this.contextService.isCurrentWorkspace(isRecentWorkspace(firstEntry) ? firstEntry.workspace : firstEntry.folderUri); let keyMods: IKeyMods | undefined; @@ -124,20 +127,82 @@ abstract class BaseOpenRecentAction extends Action { const pick = await this.quickInputService.pick(picks, { contextKey: inRecentFilesPickerContextKey, activeItem: [...workspacePicks, ...filePicks][autoFocusSecondEntry ? 1 : 0], - placeHolder: isMacintosh ? nls.localize('openRecentPlaceHolderMac', "Select to open (hold Cmd-key to open in new window)") : nls.localize('openRecentPlaceHolder', "Select to open (hold Ctrl-key to open in new window)"), + placeHolder: isMacintosh ? nls.localize('openRecentPlaceholderMac', "Select to open (hold Cmd-key to force new window or Alt-key for same window)") : nls.localize('openRecentPlaceholder', "Select to open (hold Ctrl-key to force new window or Alt-key for same window)"), matchOnDescription: true, onKeyMods: mods => keyMods = mods, quickNavigate: this.isQuickNavigate() ? { keybindings: this.keybindingService.lookupKeybindings(this.id) } : undefined, onDidTriggerItemButton: async context => { - await this.workspacesService.removeRecentlyOpened([context.item.resource]); - context.removeItem(); + + // Remove + if (context.button === this.removeFromRecentlyOpened) { + await this.workspacesService.removeRecentlyOpened([context.item.resource]); + context.removeItem(); + } + + // Dirty Workspace + else if (context.button === this.dirtyRecentlyOpened) { + const result = await this.dialogService.confirm({ + type: 'question', + title: nls.localize('dirtyWorkspace', "Workspace with Dirty Files"), + message: nls.localize('dirtyWorkspaceConfirm', "Do you want to open the workspace to review the dirty files?"), + detail: nls.localize('dirtyWorkspaceConfirmDetail', "Workspaces with dirty files cannot be removed until all dirty files have been saved or reverted.") + }); + + if (result.confirmed) { + this.hostService.openWindow([context.item.openable]); + this.quickInputService.cancel(); + } + } } }); if (pick) { - return this.hostService.openWindow([pick.openable], { forceNewWindow: keyMods?.ctrlCmd }); + return this.hostService.openWindow([pick.openable], { forceNewWindow: keyMods?.ctrlCmd, forceReuseWindow: keyMods?.alt }); } } + + private toQuickPick(recent: IRecent, isDirty: boolean): IRecentlyOpenedPick { + let openable: IWindowOpenable | undefined; + let iconClasses: string[]; + let fullLabel: string | undefined; + let resource: URI | undefined; + + // Folder + if (isRecentFolder(recent)) { + resource = recent.folderUri; + iconClasses = getIconClasses(this.modelService, this.modeService, resource, FileKind.FOLDER); + openable = { folderUri: resource }; + fullLabel = recent.label || this.labelService.getWorkspaceLabel(resource, { verbose: true }); + } + + // Workspace + else if (isRecentWorkspace(recent)) { + resource = recent.workspace.configPath; + iconClasses = getIconClasses(this.modelService, this.modeService, resource, FileKind.ROOT_FOLDER); + openable = { workspaceUri: resource }; + fullLabel = recent.label || this.labelService.getWorkspaceLabel(recent.workspace, { verbose: true }); + } + + // File + else { + resource = recent.fileUri; + iconClasses = getIconClasses(this.modelService, this.modeService, resource, FileKind.FILE); + openable = { fileUri: resource }; + fullLabel = recent.label || this.labelService.getUriLabel(resource); + } + + const { name, parentPath } = splitName(fullLabel); + + return { + iconClasses, + label: name, + ariaLabel: isDirty ? nls.localize('recentDirtyAriaLabel', "{0}, dirty workspace", name) : name, + description: parentPath, + buttons: isDirty ? [this.dirtyRecentlyOpened] : [this.removeFromRecentlyOpened], + openable, + resource + }; + } } export class OpenRecentAction extends BaseOpenRecentAction { @@ -155,9 +220,10 @@ export class OpenRecentAction extends BaseOpenRecentAction { @IModelService modelService: IModelService, @IModeService modeService: IModeService, @ILabelService labelService: ILabelService, - @IHostService hostService: IHostService + @IHostService hostService: IHostService, + @IDialogService dialogService: IDialogService ) { - super(id, label, workspacesService, quickInputService, contextService, labelService, keybindingService, modelService, modeService, hostService); + super(id, label, workspacesService, quickInputService, contextService, labelService, keybindingService, modelService, modeService, hostService, dialogService); } protected isQuickNavigate(): boolean { @@ -165,7 +231,7 @@ export class OpenRecentAction extends BaseOpenRecentAction { } } -class QuickOpenRecentAction extends BaseOpenRecentAction { +class QuickPickRecentAction extends BaseOpenRecentAction { static readonly ID = 'workbench.action.quickOpenRecent'; static readonly LABEL = nls.localize('quickOpenRecent', "Quick Open Recent..."); @@ -180,9 +246,10 @@ class QuickOpenRecentAction extends BaseOpenRecentAction { @IModelService modelService: IModelService, @IModeService modeService: IModeService, @ILabelService labelService: ILabelService, - @IHostService hostService: IHostService + @IHostService hostService: IHostService, + @IDialogService dialogService: IDialogService ) { - super(id, label, workspacesService, quickInputService, contextService, labelService, keybindingService, modelService, modeService, hostService); + super(id, label, workspacesService, quickInputService, contextService, labelService, keybindingService, modelService, modeService, hostService, dialogService); } protected isQuickNavigate(): boolean { @@ -270,7 +337,7 @@ const registry = Registry.as(Extensions.WorkbenchActio const fileCategory = nls.localize('file', "File"); registry.registerWorkbenchAction(SyncActionDescriptor.create(NewWindowAction, NewWindowAction.ID, NewWindowAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_N }), 'New Window'); -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenRecentAction, QuickOpenRecentAction.ID, QuickOpenRecentAction.LABEL), 'File: Quick Open Recent...', fileCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickPickRecentAction, QuickPickRecentAction.ID, QuickPickRecentAction.LABEL), 'File: Quick Open Recent...', fileCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenRecentAction, OpenRecentAction.ID, OpenRecentAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_R, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_R } }), 'File: Open Recent...', fileCategory); const viewCategory = nls.localize('view', "View"); @@ -284,23 +351,23 @@ registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowAboutDialogActi // --- Commands/Keybindings Registration -const recentFilesPickerContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyExpr.has(inRecentFilesPickerContextKey)); +const recentFilesPickerContext = ContextKeyExpr.and(inQuickPickContext, ContextKeyExpr.has(inRecentFilesPickerContextKey)); -const quickOpenNavigateNextInRecentFilesPickerId = 'workbench.action.quickOpenNavigateNextInRecentFilesPicker'; +const quickPickNavigateNextInRecentFilesPickerId = 'workbench.action.quickOpenNavigateNextInRecentFilesPicker'; KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: quickOpenNavigateNextInRecentFilesPickerId, + id: quickPickNavigateNextInRecentFilesPickerId, weight: KeybindingWeight.WorkbenchContrib + 50, - handler: getQuickNavigateHandler(quickOpenNavigateNextInRecentFilesPickerId, true), + handler: getQuickNavigateHandler(quickPickNavigateNextInRecentFilesPickerId, true), when: recentFilesPickerContext, primary: KeyMod.CtrlCmd | KeyCode.KEY_R, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_R } }); -const quickOpenNavigatePreviousInRecentFilesPicker = 'workbench.action.quickOpenNavigatePreviousInRecentFilesPicker'; +const quickPickNavigatePreviousInRecentFilesPicker = 'workbench.action.quickOpenNavigatePreviousInRecentFilesPicker'; KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: quickOpenNavigatePreviousInRecentFilesPicker, + id: quickPickNavigatePreviousInRecentFilesPicker, weight: KeybindingWeight.WorkbenchContrib + 50, - handler: getQuickNavigateHandler(quickOpenNavigatePreviousInRecentFilesPicker, false), + handler: getQuickNavigateHandler(quickPickNavigatePreviousInRecentFilesPicker, false), when: recentFilesPickerContext, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_R, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KEY_R } diff --git a/src/vs/workbench/browser/actions/workspaceActions.ts b/src/vs/workbench/browser/actions/workspaceActions.ts index 6607abc7716..1b2d9636354 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -112,11 +112,10 @@ export class CloseWorkspaceAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { this.notificationService.info(nls.localize('noWorkspaceOpened', "There is currently no workspace opened in this instance to close.")); - - return Promise.resolve(undefined); + return; } return this.hostService.openWindow({ forceReuseWindow: true, remoteAuthority: this.environmentService.configuration.remoteAuthority }); diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index 2f138cb0c65..fa197206bb4 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -6,8 +6,7 @@ import { Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys'; -import { IWindowsConfiguration } from 'vs/platform/windows/common/windows'; +import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext } from 'vs/platform/contextkey/common/contextkeys'; import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorIsReadonlyContext, EditorAreaVisibleContext, DirtyWorkingCopiesContext } from 'vs/workbench/common/editor'; import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -18,27 +17,15 @@ import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/ import { SideBarVisibleContext } from 'vs/workbench/common/viewlet'; import { IWorkbenchLayoutService, Parts, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { isMacintosh, isLinux, isWindows, isWeb } from 'vs/base/common/platform'; import { PanelPositionContext } from 'vs/workbench/common/panel'; import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -export const IsMacContext = new RawContextKey('isMac', isMacintosh); -export const IsLinuxContext = new RawContextKey('isLinux', isLinux); -export const IsWindowsContext = new RawContextKey('isWindows', isWindows); - -export const IsWebContext = new RawContextKey('isWeb', isWeb); -export const IsMacNativeContext = new RawContextKey('isMacNative', isMacintosh && !isWeb); - export const Deprecated_RemoteAuthorityContext = new RawContextKey('remoteAuthority', ''); export const RemoteNameContext = new RawContextKey('remoteName', ''); export const RemoteConnectionState = new RawContextKey<'' | 'initializing' | 'disconnected' | 'connected'>('remoteConnectionState', ''); -export const HasMacNativeTabsContext = new RawContextKey('hasMacNativeTabs', false); - -export const IsDevelopmentContext = new RawContextKey('isDevelopment', false); - export const WorkbenchStateContext = new RawContextKey('workbenchState', undefined); export const WorkspaceFolderCountContext = new RawContextKey('workspaceFolderCount', 0); @@ -98,10 +85,6 @@ export class WorkbenchContextKeysHandler extends Disposable { RemoteNameContext.bindTo(this.contextKeyService).set(getRemoteName(this.environmentService.configuration.remoteAuthority) || ''); - // macOS Native Tabs - const windowConfig = this.configurationService.getValue(); - HasMacNativeTabsContext.bindTo(this.contextKeyService).set(windowConfig?.window?.nativeTabs); - // Development IsDevelopmentContext.bindTo(this.contextKeyService).set(!this.environmentService.isBuilt || this.environmentService.isExtensionDevelopment); @@ -193,13 +176,13 @@ export class WorkbenchContextKeysHandler extends Disposable { private updateEditorContextKeys(): void { const activeGroup = this.editorGroupService.activeGroup; - const activeControl = this.editorService.activeControl; - const visibleEditors = this.editorService.visibleControls; + const activeEditorPane = this.editorService.activeEditorPane; + const visibleEditorPanes = this.editorService.visibleEditorPanes; - this.textCompareEditorActiveContext.set(activeControl?.getId() === TEXT_DIFF_EDITOR_ID); - this.textCompareEditorVisibleContext.set(visibleEditors.some(control => control.getId() === TEXT_DIFF_EDITOR_ID)); + this.textCompareEditorActiveContext.set(activeEditorPane?.getId() === TEXT_DIFF_EDITOR_ID); + this.textCompareEditorVisibleContext.set(visibleEditorPanes.some(editorPane => editorPane.getId() === TEXT_DIFF_EDITOR_ID)); - if (visibleEditors.length > 0) { + if (visibleEditorPanes.length > 0) { this.editorsVisibleContext.set(true); } else { this.editorsVisibleContext.reset(); @@ -221,9 +204,14 @@ export class WorkbenchContextKeysHandler extends Disposable { this.activeEditorGroupIndex.set(activeGroup.index + 1); // not zero-indexed this.activeEditorGroupLast.set(activeGroup.index === groupCount - 1); - if (activeControl) { - this.activeEditorContext.set(activeControl.getId()); - this.activeEditorIsReadonly.set(activeControl.input.isReadonly()); + if (activeEditorPane) { + this.activeEditorContext.set(activeEditorPane.getId()); + try { + this.activeEditorIsReadonly.set(activeEditorPane.input.isReadonly()); + } catch (error) { + // TODO@ben for https://github.com/microsoft/vscode/issues/93224 + throw new Error(`${error.message}: editor id ${activeEditorPane.getId()}`); + } } else { this.activeEditorContext.reset(); this.activeEditorIsReadonly.reset(); diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index 6467fe70efd..cbd49d96a02 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri'; import { ITextFileService, stringToSnapshot } from 'vs/workbench/services/textfile/common/textfiles'; import { Schemas } from 'vs/base/common/network'; import { IEditorViewState } from 'vs/editor/common/editorCommon'; -import { DataTransfers } from 'vs/base/browser/dnd'; +import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd'; import { DragMouseEvent } from 'vs/base/browser/mouseEvent'; import { normalizeDriveLetter } from 'vs/base/common/labels'; import { MIME_BINARY } from 'vs/base/common/mime'; @@ -20,8 +20,8 @@ import { isWindows, isWeb } from 'vs/base/common/platform'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorIdentifier, GroupIdentifier } from 'vs/workbench/common/editor'; -import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService'; +import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { addDisposableListener, EventType, asDomUri } from 'vs/base/browser/dom'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; @@ -29,6 +29,7 @@ import { withNullAsUndefined } from 'vs/base/common/types'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { isStandalone } from 'vs/base/browser/browser'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { Emitter } from 'vs/base/common/event'; export interface IDraggedResource { resource: URI; @@ -196,7 +197,7 @@ export class ResourcesDropHandler { this.workspacesService.addRecentlyOpened(recentFiles); } - const editors: IResourceEditor[] = untitledOrFileResources.map(untitledOrFileResource => ({ + const editors: IResourceEditorInputType[] = untitledOrFileResources.map(untitledOrFileResource => ({ resource: untitledOrFileResource.resource, encoding: (untitledOrFileResource as IDraggedEditor).encoding, mode: (untitledOrFileResource as IDraggedEditor).mode, @@ -239,14 +240,14 @@ export class ResourcesDropHandler { // Untitled: always ensure that we open a new untitled editor for each file we drop if (droppedDirtyEditor.resource.scheme === Schemas.untitled) { - const untitledEditorResource = this.editorService.createInput({ mode: droppedDirtyEditor.mode, encoding: droppedDirtyEditor.encoding, forceUntitled: true }).resource; + const untitledEditorResource = this.editorService.createEditorInput({ mode: droppedDirtyEditor.mode, encoding: droppedDirtyEditor.encoding, forceUntitled: true }).resource; if (untitledEditorResource) { droppedDirtyEditor.resource = untitledEditorResource; } } // File: ensure the file is not dirty or opened already - else if (this.textFileService.isDirty(droppedDirtyEditor.resource) || this.editorService.isOpen(this.editorService.createInput({ resource: droppedDirtyEditor.resource, forceFile: true }))) { + else if (this.textFileService.isDirty(droppedDirtyEditor.resource) || this.editorService.isOpen({ resource: droppedDirtyEditor.resource })) { return false; } @@ -348,12 +349,12 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources: // Try to find editor view state from the visible editors that match given resource let viewState: IEditorViewState | undefined = undefined; - const textEditorWidgets = editorService.visibleTextEditorWidgets; - for (const textEditorWidget of textEditorWidgets) { - if (isCodeEditor(textEditorWidget)) { - const model = textEditorWidget.getModel(); + const textEditorControls = editorService.visibleTextEditorControls; + for (const textEditorControl of textEditorControls) { + if (isCodeEditor(textEditorControl)) { + const model = textEditorControl.getModel(); if (model?.uri?.toString() === file.resource.toString()) { - viewState = withNullAsUndefined(textEditorWidget.saveViewState()); + viewState = withNullAsUndefined(textEditorControl.saveViewState()); break; } } @@ -507,3 +508,226 @@ export function containsDragType(event: DragEvent, ...dragTypesToFind: string[]) return false; } + +export type Before2D = { verticallyBefore: boolean; horizontallyBefore: boolean; }; + +export interface ICompositeDragAndDrop { + drop(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent, before?: Before2D): void; + onDragOver(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent): boolean; + onDragEnter(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent): boolean; +} + +export interface ICompositeDragAndDropObserverCallbacks { + onDragEnter?: (e: IDraggedCompositeData) => void; + onDragLeave?: (e: IDraggedCompositeData) => void; + onDrop?: (e: IDraggedCompositeData) => void; + onDragOver?: (e: IDraggedCompositeData) => void; + onDragStart?: (e: IDraggedCompositeData) => void; + onDragEnd?: (e: IDraggedCompositeData) => void; +} + +export class CompositeDragAndDropData implements IDragAndDropData { + constructor(private type: 'view' | 'composite', private id: string) { } + update(dataTransfer: DataTransfer): void { + // no-op + } + getData(): { + type: 'view' | 'composite'; + id: string; + } { + return { type: this.type, id: this.id }; + } +} + +export interface IDraggedCompositeData { + eventData: DragEvent; + dragAndDropData: CompositeDragAndDropData; +} + +export class DraggedCompositeIdentifier { + constructor(private _compositeId: string) { } + + get id(): string { + return this._compositeId; + } +} + +export class DraggedViewIdentifier { + constructor(private _viewId: string) { } + + get id(): string { + return this._viewId; + } +} + +export type ViewType = 'composite' | 'view'; + +export class CompositeDragAndDropObserver extends Disposable { + private transferData: LocalSelectionTransfer; + private _onDragStart = this._register(new Emitter()); + private _onDragEnd = this._register(new Emitter()); + private static _instance: CompositeDragAndDropObserver | undefined; + static get INSTANCE(): CompositeDragAndDropObserver { + if (!CompositeDragAndDropObserver._instance) { + CompositeDragAndDropObserver._instance = new CompositeDragAndDropObserver(); + } + return CompositeDragAndDropObserver._instance; + } + private constructor() { + super(); + this.transferData = LocalSelectionTransfer.getInstance(); + } + private readDragData(type: ViewType): CompositeDragAndDropData | undefined { + if (this.transferData.hasData(type === 'view' ? DraggedViewIdentifier.prototype : DraggedCompositeIdentifier.prototype)) { + const data = this.transferData.getData(type === 'view' ? DraggedViewIdentifier.prototype : DraggedCompositeIdentifier.prototype); + if (data && data[0]) { + return new CompositeDragAndDropData(type, data[0].id); + } + } + return undefined; + } + private writeDragData(id: string, type: ViewType): void { + this.transferData.setData([type === 'view' ? new DraggedViewIdentifier(id) : new DraggedCompositeIdentifier(id)], type === 'view' ? DraggedViewIdentifier.prototype : DraggedCompositeIdentifier.prototype); + } + registerTarget(element: HTMLElement, callbacks: ICompositeDragAndDropObserverCallbacks): IDisposable { + const disposableStore = new DisposableStore(); + disposableStore.add(new DragAndDropObserver(element, { + onDragEnd: e => { + // no-op + }, + onDragEnter: e => { + e.preventDefault(); + if (callbacks.onDragEnter) { + const data = this.readDragData('composite') || this.readDragData('view'); + if (data) { + callbacks.onDragEnter({ eventData: e, dragAndDropData: data! }); + } + } + }, + onDragLeave: e => { + const data = this.readDragData('composite') || this.readDragData('view'); + if (callbacks.onDragLeave && data) { + callbacks.onDragLeave({ eventData: e, dragAndDropData: data! }); + } + }, + onDrop: e => { + if (callbacks.onDrop) { + const data = this.readDragData('composite') || this.readDragData('view'); + if (!data) { + return; + } + + callbacks.onDrop({ eventData: e, dragAndDropData: data! }); + + // Fire drag event in case drop handler destroys the dragged element + this._onDragEnd.fire({ eventData: e, dragAndDropData: data! }); + } + }, + onDragOver: e => { + e.preventDefault(); + if (callbacks.onDragOver) { + const data = this.readDragData('composite') || this.readDragData('view'); + if (!data) { + return; + } + + callbacks.onDragOver({ eventData: e, dragAndDropData: data! }); + } + } + })); + if (callbacks.onDragStart) { + this._onDragStart.event(e => { + callbacks.onDragStart!(e); + }, this, disposableStore); + } + if (callbacks.onDragEnd) { + this._onDragEnd.event(e => { + callbacks.onDragEnd!(e); + }); + } + return this._register(disposableStore); + } + + registerDraggable(element: HTMLElement, draggedItemProvider: () => { type: ViewType, id: string }, callbacks: ICompositeDragAndDropObserverCallbacks): IDisposable { + element.draggable = true; + const disposableStore = new DisposableStore(); + disposableStore.add(addDisposableListener(element, EventType.DRAG_START, e => { + const { id, type } = draggedItemProvider(); + this.writeDragData(id, type); + this._onDragStart.fire({ eventData: e, dragAndDropData: this.readDragData(type)! }); + })); + disposableStore.add(new DragAndDropObserver(element, { + onDragEnd: e => { + const { id, type } = draggedItemProvider(); + + const data = this.readDragData(type); + if (data && data.getData().id === id) { + this.transferData.clearData(type === 'view' ? DraggedViewIdentifier.prototype : DraggedCompositeIdentifier.prototype); + } + + if (!data) { + return; + } + + this._onDragEnd.fire({ eventData: e, dragAndDropData: data! }); + }, + onDragEnter: e => { + + if (callbacks.onDragEnter) { + const data = this.readDragData('composite') || this.readDragData('view'); + if (!data) { + return; + } + + if (data) { + callbacks.onDragEnter({ eventData: e, dragAndDropData: data! }); + } + } + }, + onDragLeave: e => { + const data = this.readDragData('composite') || this.readDragData('view'); + if (!data) { + return; + } + + if (callbacks.onDragLeave) { + callbacks.onDragLeave({ eventData: e, dragAndDropData: data! }); + } + }, + onDrop: e => { + if (callbacks.onDrop) { + const data = this.readDragData('composite') || this.readDragData('view'); + + if (!data) { + return; + } + callbacks.onDrop({ eventData: e, dragAndDropData: data! }); + + // Fire drag event in case drop handler destroys the dragged element + this._onDragEnd.fire({ eventData: e, dragAndDropData: data! }); + } + }, + onDragOver: e => { + if (callbacks.onDragOver) { + const data = this.readDragData('composite') || this.readDragData('view'); + if (!data) { + return; + } + + callbacks.onDragOver({ eventData: e, dragAndDropData: data! }); + } + } + })); + if (callbacks.onDragStart) { + this._onDragStart.event(e => { + callbacks.onDragStart!(e); + }, this, disposableStore); + } + if (callbacks.onDragEnd) { + this._onDragEnd.event(e => { + callbacks.onDragEnd!(e); + }); + } + return this._register(disposableStore); + } +} diff --git a/src/vs/workbench/browser/editor.ts b/src/vs/workbench/browser/editor.ts index af3e39d295e..f8fc30b6144 100644 --- a/src/vs/workbench/browser/editor.ts +++ b/src/vs/workbench/browser/editor.ts @@ -8,7 +8,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { IConstructorSignature0, IInstantiationService, BrandedService } from 'vs/platform/instantiation/common/instantiation'; -import { find } from 'vs/base/common/arrays'; +import { find, insert } from 'vs/base/common/arrays'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; export interface IEditorDescriptor { @@ -94,15 +94,11 @@ class EditorRegistry implements IEditorRegistry { registerEditor(descriptor: EditorDescriptor, inputDescriptors: readonly SyncDescriptor[]): IDisposable { this.mapEditorToInputs.set(descriptor, inputDescriptors); - this.editors.push(descriptor); + const remove = insert(this.editors, descriptor); return toDisposable(() => { this.mapEditorToInputs.delete(descriptor); - - const index = this.editors.indexOf(descriptor); - if (index !== -1) { - this.editors.splice(index, 1); - } + remove(); }); } diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index eece5353caa..426e1929d30 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -6,7 +6,6 @@ import { URI } from 'vs/base/common/uri'; import { dirname, isEqual, basenameOrAuthority } from 'vs/base/common/resources'; import { IconLabel, IIconLabelValueOptions, IIconLabelCreationOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -25,11 +24,23 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { withNullAsUndefined } from 'vs/base/common/types'; export interface IResourceLabelProps { - resource?: URI; + resource?: URI | { master?: URI, detail?: URI }; name?: string | string[]; description?: string; } +function toResource(props: IResourceLabelProps | undefined): URI | undefined { + if (!props || !props.resource) { + return undefined; + } + + if (URI.isUri(props.resource)) { + return props.resource; + } + + return props.resource.master; +} + export interface IResourceLabelOptions extends IIconLabelValueOptions { fileKind?: FileKind; fileDecorations?: { colors: boolean, badges: boolean }; @@ -82,9 +93,9 @@ export class ResourceLabels extends Disposable { constructor( container: IResourceLabelsContainer, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IExtensionService private readonly extensionService: IExtensionService, @IConfigurationService private readonly configurationService: IConfigurationService, @IModelService private readonly modelService: IModelService, + @IModeService private readonly modeService: IModeService, @IDecorationsService private readonly decorationsService: IDecorationsService, @IThemeService private readonly themeService: IThemeService, @ILabelService private readonly labelService: ILabelService, @@ -103,7 +114,7 @@ export class ResourceLabels extends Disposable { })); // notify when extensions are registered with potentially new languages - this._register(this.extensionService.onDidRegisterExtensions(() => this._widgets.forEach(widget => widget.notifyExtensionsRegistered()))); + this._register(this.modeService.onLanguagesMaybeChanged(() => this._widgets.forEach(widget => widget.notifyExtensionsRegistered()))); // notify when model mode changes this._register(this.modelService.onModelModeChanged(e => { @@ -127,7 +138,7 @@ export class ResourceLabels extends Disposable { this._register(this.decorationsService.onDidChangeDecorations(e => this._widgets.forEach(widget => widget.notifyFileDecorationsChanges(e)))); // notify when theme changes - this._register(this.themeService.onThemeChange(() => this._widgets.forEach(widget => widget.notifyThemeChange()))); + this._register(this.themeService.onDidColorThemeChange(() => this._widgets.forEach(widget => widget.notifyThemeChange()))); // notify when files.associations changes this._register(this.configurationService.onDidChangeConfiguration(e => { @@ -137,14 +148,14 @@ export class ResourceLabels extends Disposable { })); // notify when label formatters change - this._register(this.labelService.onDidChangeFormatters(() => { - this._widgets.forEach(widget => widget.notifyFormattersChange()); + this._register(this.labelService.onDidChangeFormatters(e => { + this._widgets.forEach(widget => widget.notifyFormattersChange(e.scheme)); })); // notify when untitled labels change - this.textFileService.untitled.onDidChangeLabel(model => { + this._register(this.textFileService.untitled.onDidChangeLabel(model => { this._widgets.forEach(widget => widget.notifyUntitledLabelChange(model.resource)); - }); + })); } get(index: number): IResourceLabel { @@ -206,15 +217,15 @@ export class ResourceLabel extends ResourceLabels { container: HTMLElement, options: IIconLabelCreationOptions | undefined, @IInstantiationService instantiationService: IInstantiationService, - @IExtensionService extensionService: IExtensionService, @IConfigurationService configurationService: IConfigurationService, @IModelService modelService: IModelService, + @IModeService modeService: IModeService, @IDecorationsService decorationsService: IDecorationsService, @IThemeService themeService: IThemeService, @ILabelService labelService: ILabelService, @ITextFileService textFileService: ITextFileService ) { - super(DEFAULT_LABELS_CONTAINER, instantiationService, extensionService, configurationService, modelService, decorationsService, themeService, labelService, textFileService); + super(DEFAULT_LABELS_CONTAINER, instantiationService, configurationService, modelService, modeService, decorationsService, themeService, labelService, textFileService); this._label = this._register(this.create(container, options)); } @@ -290,11 +301,16 @@ class ResourceLabelWidget extends IconLabel { } notifyFileDecorationsChanges(e: IResourceDecorationChangeEvent): void { - if (!this.options || !this.label || !this.label.resource) { + if (!this.options) { return; } - if (this.options.fileDecorations && e.affectsResource(this.label.resource)) { + const resource = toResource(this.label); + if (!resource) { + return; + } + + if (this.options.fileDecorations && e.affectsResource(resource)) { this.render(false); } } @@ -311,12 +327,14 @@ class ResourceLabelWidget extends IconLabel { this.render(true); } - notifyFormattersChange(): void { - this.render(false); + notifyFormattersChange(scheme: string): void { + if (toResource(this.label)?.scheme === scheme) { + this.render(false); + } } notifyUntitledLabelChange(resource: URI): void { - if (isEqual(resource, this.label?.resource)) { + if (isEqual(resource, toResource(this.label))) { this.render(false); } } @@ -346,7 +364,10 @@ class ResourceLabelWidget extends IconLabel { } setResource(label: IResourceLabelProps, options: IResourceLabelOptions = Object.create(null)): void { - if (label.resource?.scheme === Schemas.untitled) { + const resource = toResource(label); + const isMasterDetail = label?.resource && !URI.isUri(label.resource); + + if (!isMasterDetail && resource?.scheme === Schemas.untitled) { // Untitled labels are very dynamic because they may change // whenever the content changes (unless a path is associated). // As such we always ask the actual editor for it's name and @@ -354,7 +375,11 @@ class ResourceLabelWidget extends IconLabel { // provided. If they are not provided from the label we got // we assume that the client does not want to display them // and as such do not override. - const untitledModel = this.textFileService.untitled.get(label.resource); + // + // We do not touch the label if it represents a master-detail + // because in that case we expect it to carry a proper label + // and description. + const untitledModel = this.textFileService.untitled.get(resource); if (untitledModel && !untitledModel.hasAssociatedFilePath) { if (typeof label.name === 'string') { label.name = untitledModel.name; @@ -414,7 +439,7 @@ class ResourceLabelWidget extends IconLabel { } private hasPathLabelChanged(newLabel: IResourceLabelProps, newOptions?: IResourceLabelOptions): boolean { - const newResource = newLabel ? newLabel.resource : undefined; + const newResource = toResource(newLabel); return !!newResource && this.computedPathLabel !== this.labelService.getUriLabel(newResource); } @@ -443,7 +468,8 @@ class ResourceLabelWidget extends IconLabel { } if (this.label) { - const detectedModeId = this.label.resource ? withNullAsUndefined(detectModeId(this.modelService, this.modeService, this.label.resource)) : undefined; + const resource = toResource(this.label); + const detectedModeId = resource ? withNullAsUndefined(detectModeId(this.modelService, this.modeService, resource)) : undefined; if (this.lastKnownDetectedModeId !== detectedModeId) { clearIconCache = true; this.lastKnownDetectedModeId = detectedModeId; @@ -462,14 +488,15 @@ class ResourceLabelWidget extends IconLabel { const iconLabelOptions: IIconLabelValueOptions & { extraClasses: string[] } = { title: '', - italic: this.options && this.options.italic, - matches: this.options && this.options.matches, + italic: this.options?.italic, + strikethrough: this.options?.strikethrough, + matches: this.options?.matches, extraClasses: [], separator: this.options?.separator, domId: this.options?.domId }; - const resource = this.label.resource; + const resource = toResource(this.label); const label = this.label.name; if (this.options && typeof this.options.title === 'string') { diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 943d5ad7988..43f89e056d8 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -5,7 +5,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Emitter } from 'vs/base/common/event'; -import { EventType, addDisposableListener, addClass, removeClass, isAncestor, getClientArea, Dimension, toggleClass, position, size } from 'vs/base/browser/dom'; +import { EventType, addDisposableListener, addClass, removeClass, isAncestor, getClientArea, Dimension, toggleClass, position, size, IDimension } from 'vs/base/browser/dom'; import { onDidChangeFullscreen, isFullscreen } from 'vs/base/browser/browser'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -27,10 +27,9 @@ import { MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility } from 'vs/pl import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IEditor } from 'vs/editor/common/editorCommon'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { SerializableGrid, ISerializableView, ISerializedGrid, Orientation, ISerializedNode, ISerializedLeafNode, Direction, IViewSize } from 'vs/base/browser/ui/grid/grid'; -import { IDimension } from 'vs/platform/layout/browser/layoutService'; import { Part } from 'vs/workbench/browser/part'; import { IStatusbarService } from 'vs/workbench/services/statusbar/common/statusbar'; import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; @@ -118,6 +117,19 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private _container: HTMLElement = document.createElement('div'); get container(): HTMLElement { return this._container; } + get offset() { + return { + top: (() => { + let offset = 0; + if (this.isVisible(Parts.TITLEBAR_PART)) { + offset = this.getPart(Parts.TITLEBAR_PART).maximumHeight; + } + + return offset; + })() + }; + } + private parts: Map = new Map(); private workbenchGrid!: SerializableGrid; @@ -173,7 +185,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi centered: false, restoreCentered: false, restoreEditors: false, - editorsToOpen: [] as Promise | IResourceEditor[] + editorsToOpen: [] as Promise | IResourceEditorInputType[] }, panel: { @@ -274,7 +286,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } // Theme changes - this._register(this.themeService.onThemeChange(theme => this.updateStyles())); + this._register(this.themeService.onDidColorThemeChange(theme => this.updateStyles())); // Window focus changes this._register(this.hostService.onDidChangeFocus(e => this.onWindowFocusChanged(e))); @@ -403,7 +415,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return; } - const theme = this.themeService.getTheme(); + const theme = this.themeService.getColorTheme(); const activeBorder = theme.getColor(WINDOW_ACTIVE_BORDER); const inactiveBorder = theme.getColor(WINDOW_INACTIVE_BORDER); @@ -412,11 +424,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (!this.state.fullscreen && !this.state.maximized && (activeBorder || inactiveBorder)) { windowBorder = true; - // If one color is missing, just fallback to the other one - const borderColor = this.state.hasFocus - ? activeBorder ?? inactiveBorder - : inactiveBorder ?? activeBorder; - this.container.style.setProperty('--window-border-color', borderColor ? borderColor.toString() : 'transparent'); + // If the inactive color is missing, fallback to the active one + const borderColor = this.state.hasFocus ? activeBorder : inactiveBorder ?? activeBorder; + this.container.style.setProperty('--window-border-color', borderColor?.toString() ?? 'transparent'); } if (windowBorder === this.state.windowBorder) { @@ -514,7 +524,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } - private resolveEditorsToOpen(fileService: IFileService): Promise | IResourceEditor[] { + private resolveEditorsToOpen(fileService: IFileService): Promise | IResourceEditorInputType[] { const configuration = this.environmentService.configuration; const hasInitialFilesToOpen = this.hasInitialFilesToOpen(); @@ -650,17 +660,12 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return true; // any other part cannot be hidden } - getDimension(part: Parts): Dimension | undefined { - return this.getPart(part).dimension; + focus(): void { + this.editorGroupService.activeGroup.focus(); } - getTitleBarOffset(): number { - let offset = 0; - if (this.isVisible(Parts.TITLEBAR_PART)) { - offset = this.getPart(Parts.TITLEBAR_PART).maximumHeight; - } - - return offset; + getDimension(part: Parts): Dimension | undefined { + return this.getPart(part).dimension; } getMaximumEditorDimensions(): Dimension { @@ -685,10 +690,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return this.parent; } - getWorkbenchElement(): HTMLElement { - return this.container; - } - toggleZenMode(skipLayout?: boolean, restoring = false): void { this.state.zenMode.active = !this.state.zenMode.active; this.state.zenMode.transitionDisposables.clear(); @@ -707,22 +708,22 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi editor.updateOptions({ lineNumbers }); }; - const editorWidgetSet = this.state.zenMode.editorWidgetSet; + const editorControlSet = this.state.zenMode.editorWidgetSet; if (!lineNumbers) { // Reset line numbers on all editors visible and non-visible - for (const editor of editorWidgetSet) { + for (const editor of editorControlSet) { setEditorLineNumbers(editor); } - editorWidgetSet.clear(); + editorControlSet.clear(); } else { - this.editorService.visibleTextEditorWidgets.forEach(editor => { - if (!editorWidgetSet.has(editor)) { - editorWidgetSet.add(editor); - this.state.zenMode.transitionDisposables.add(editor.onDidDispose(() => { - editorWidgetSet.delete(editor); + this.editorService.visibleTextEditorControls.forEach(editorControl => { + if (!editorControlSet.has(editorControl)) { + editorControlSet.add(editorControl); + this.state.zenMode.transitionDisposables.add(editorControl.onDidDispose(() => { + editorControlSet.delete(editorControl); })); } - setEditorLineNumbers(editor); + setEditorLineNumbers(editorControl); }); } }; @@ -806,7 +807,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Status bar and activity bar visibility come from settings -> update their visibility. this.doUpdateLayoutConfiguration(true); - this.editorGroupService.activeGroup.focus(); + this.focus(); if (this.state.zenMode.setNotificationsFilter) { this.notificationService.setFilter(NotificationsFilter.OFF); } @@ -1090,7 +1091,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (this.hasFocus(Parts.PANEL_PART) && activePanel) { activePanel.focus(); } else { - this.editorGroupService.activeGroup.focus(); + this.focus(); } } diff --git a/src/vs/workbench/browser/media/part.css b/src/vs/workbench/browser/media/part.css index 89dd7fe4c9b..f31b7454f8c 100644 --- a/src/vs/workbench/browser/media/part.css +++ b/src/vs/workbench/browser/media/part.css @@ -8,6 +8,23 @@ overflow: hidden; } + +.monaco-workbench .part > .drop-block-overlay.visible { + display: block; + backdrop-filter: brightness(97%) blur(2px); + opacity: 1; + z-index: 10; +} + +.monaco-workbench .part > .drop-block-overlay { + display: none; + width: 100%; + height: 100%; + position: absolute; + top: 0; + pointer-events: none; +} + .monaco-workbench .part > .title { display: none; /* Parts have to opt in to show title area */ } diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index 72c43f410d8..d63b7af49f4 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -23,9 +23,9 @@ .linux:lang(ja) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; } .linux:lang(ko) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; } -.mac { --monaco-monospace-font: "SF Mono", Monaco, Menlo, Inconsolata, "Courier New", monospace; } -.windows { --monaco-monospace-font: Consolas, Inconsolata, "Courier New", monospace; } -.linux { --monaco-monospace-font: "Droid Sans Mono", Inconsolata, "Courier New", monospace, "Droid Sans Fallback"; } +.mac { --monaco-monospace-font: "SF Mono", Monaco, Menlo, Courier, monospace; } +.windows { --monaco-monospace-font: Consolas, "Courier New", monospace; } +.linux { --monaco-monospace-font: "Ubuntu Mono", "Liberation Mono", "DejaVu Sans Mono", "Courier New", monospace; } /* Global Styles */ @@ -223,9 +223,7 @@ body.web { .monaco-workbench [tabindex="-1"]:active, .monaco-workbench select:active, .monaco-workbench input[type="button"]:active, -.monaco-workbench input[type="checkbox"]:active, -.monaco-workbench .monaco-tree .monaco-tree-row -.monaco-workbench .monaco-tree.focused.no-focused-item:active:before { +.monaco-workbench input[type="checkbox"]:active { outline: 0 !important; /* fixes some flashing outlines from showing up when clicking */ } @@ -233,12 +231,6 @@ body.web { border-color: transparent; /* outline is a square, but border has a radius, so we avoid this glitch when focused (https://github.com/Microsoft/vscode/issues/26045) */ } -.monaco-workbench .monaco-tree.focused .monaco-tree-row.focused [tabindex="0"]:focus { - outline-width: 1px; /* higher contrast color for focusable elements in a row that shows focus feedback */ - outline-style: solid; -} - -.monaco-workbench .monaco-tree.focused.no-focused-item:focus:before, .monaco-workbench .monaco-list:not(.element-focused):focus:before { position: absolute; top: 0; @@ -267,7 +259,6 @@ body.web { outline: 0 !important; /* outline is not going well with decoration */ } -.monaco-workbench .monaco-tree.focused:focus, .monaco-workbench .monaco-list:focus { outline: 0 !important; /* tree indicates focus not via outline but through the focused item */ } diff --git a/src/vs/workbench/browser/panel.ts b/src/vs/workbench/browser/panel.ts index 89830e45418..bc41a446d73 100644 --- a/src/vs/workbench/browser/panel.ts +++ b/src/vs/workbench/browser/panel.ts @@ -6,11 +6,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IPanel } from 'vs/workbench/common/panel'; import { Composite, CompositeDescriptor, CompositeRegistry } from 'vs/workbench/browser/composite'; -import { Action } from 'vs/base/common/actions'; -import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { IConstructorSignature0, BrandedService } from 'vs/platform/instantiation/common/instantiation'; -import { isAncestor } from 'vs/base/browser/dom'; import { assertIsDefined } from 'vs/base/common/types'; import { PaneComposite } from 'vs/workbench/browser/panecomposite'; @@ -85,44 +81,6 @@ export class PanelRegistry extends CompositeRegistry { } } -/** - * A reusable action to toggle a panel with a specific id depending on focus. - */ -export abstract class TogglePanelAction extends Action { - - constructor( - id: string, - label: string, - private readonly panelId: string, - protected panelService: IPanelService, - private layoutService: IWorkbenchLayoutService, - cssClass?: string - ) { - super(id, label, cssClass); - } - - async run(): Promise { - if (this.isPanelFocused()) { - this.layoutService.setPanelHidden(true); - } else { - await this.panelService.openPanel(this.panelId, true); - } - } - - private isPanelActive(): boolean { - const activePanel = this.panelService.getActivePanel(); - - return activePanel?.getId() === this.panelId; - } - - private isPanelFocused(): boolean { - const activeElement = document.activeElement; - const panelPart = this.layoutService.getContainer(Parts.PANEL_PART); - - return !!(this.isPanelActive() && activeElement && panelPart && isAncestor(activeElement, panelPart)); - } -} - export const Extensions = { Panels: 'workbench.contributions.panels' }; diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index cec01038567..dfab3bc5cfc 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -5,10 +5,9 @@ import 'vs/css!./media/part'; import { Component } from 'vs/workbench/common/component'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; -import { Dimension, size } from 'vs/base/browser/dom'; +import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; +import { Dimension, size, IDimension } from 'vs/base/browser/dom'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IDimension } from 'vs/platform/layout/browser/layoutService'; import { ISerializableView, IViewSize } from 'vs/base/browser/ui/grid/grid'; import { Event, Emitter } from 'vs/base/common/event'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; @@ -53,7 +52,7 @@ export abstract class Part extends Component implements ISerializableView { layoutService.registerPart(this); } - protected onThemeChange(theme: ITheme): void { + protected onThemeChange(theme: IColorTheme): void { // only call if our create() method has been called if (this.parent) { diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index eb88e507397..ffc31afca7e 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -17,7 +17,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { Registry } from 'vs/platform/registry/common/platform'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; -import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ActivityAction, ActivityActionViewItem, ICompositeBar, ICompositeBarColors, ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions'; import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; @@ -134,11 +134,66 @@ export class ToggleViewletAction extends Action { } } +export class AccountsActionViewItem extends ActivityActionViewItem { + constructor( + action: ActivityAction, + colors: (theme: IColorTheme) => ICompositeBarColors, + @IThemeService themeService: IThemeService, + @IContextMenuService protected contextMenuService: IContextMenuService, + @IMenuService protected menuService: IMenuService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + ) { + super(action, { draggable: false, colors, icon: true }, themeService); + } + + render(container: HTMLElement): void { + super.render(container); + + // Context menus are triggered on mouse down so that an item can be picked + // and executed with releasing the mouse over it + + this._register(DOM.addDisposableListener(this.container, DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => { + DOM.EventHelper.stop(e, true); + this.showContextMenu(); + })); + + this._register(DOM.addDisposableListener(this.container, DOM.EventType.KEY_UP, (e: KeyboardEvent) => { + let event = new StandardKeyboardEvent(e); + if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { + DOM.EventHelper.stop(e, true); + this.showContextMenu(); + } + })); + + this._register(DOM.addDisposableListener(this.container, TouchEventType.Tap, (e: GestureEvent) => { + DOM.EventHelper.stop(e, true); + this.showContextMenu(); + })); + } + + private showContextMenu(): void { + const accountsActions: IAction[] = []; + const accountsMenu = this.menuService.createMenu(MenuId.AccountsContext, this.contextKeyService); + const actionsDisposable = createAndFillInActionBarActions(accountsMenu, undefined, { primary: [], secondary: accountsActions }); + + const containerPosition = DOM.getDomNodePagePosition(this.container); + const location = { x: containerPosition.left + containerPosition.width / 2, y: containerPosition.top }; + this.contextMenuService.showContextMenu({ + getAnchor: () => location, + getActions: () => accountsActions, + onHide: () => { + accountsMenu.dispose(); + dispose(actionsDisposable); + } + }); + } +} + export class GlobalActivityActionViewItem extends ActivityActionViewItem { constructor( action: ActivityAction, - colors: (theme: ITheme) => ICompositeBarColors, + colors: (theme: IColorTheme) => ICompositeBarColors, @IThemeService themeService: IThemeService, @IMenuService private readonly menuService: IMenuService, @IContextMenuService protected contextMenuService: IContextMenuService, @@ -231,7 +286,7 @@ class SwitchSideBarViewAction extends Action { const activeViewlet = this.viewletService.getActiveViewlet(); if (!activeViewlet) { - return Promise.resolve(); + return; } let targetViewletId: string | undefined; for (let i = 0; i < pinnedViewletIds.length; i++) { @@ -287,7 +342,7 @@ const registry = Registry.as(ActionExtensions.Workbenc registry.registerWorkbenchAction(SyncActionDescriptor.create(PreviousSideBarViewAction, PreviousSideBarViewAction.ID, PreviousSideBarViewAction.LABEL), 'View: Previous Side Bar View', nls.localize('view', "View")); registry.registerWorkbenchAction(SyncActionDescriptor.create(NextSideBarViewAction, NextSideBarViewAction.ID, NextSideBarViewAction.LABEL), 'View: Next Side Bar View', nls.localize('view', "View")); -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const activeForegroundColor = theme.getColor(ACTIVITY_BAR_FOREGROUND); if (activeForegroundColor) { diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index c64e87f1fee..015130767ef 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -9,14 +9,14 @@ import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/acti import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; import { Registry } from 'vs/platform/registry/common/platform'; import { Part } from 'vs/workbench/browser/part'; -import { GlobalActivityActionViewItem, ViewletActivityAction, ToggleViewletAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewletActivityAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; +import { GlobalActivityActionViewItem, ViewletActivityAction, ToggleViewletAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewletActivityAction, AccountsActionViewItem } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IWorkbenchLayoutService, Parts, Position as SideBarPosition } from 'vs/workbench/services/layout/browser/layoutService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; import { ToggleActivityBarVisibilityAction, ToggleMenuBarAction } from 'vs/workbench/browser/actions/layoutActions'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND, ACTIVITY_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { CompositeBar, ICompositeBarItem, CompositeDragAndDrop } from 'vs/workbench/browser/parts/compositeBar'; @@ -39,6 +39,24 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { getMenuBarVisibility } from 'vs/platform/windows/common/windows'; import { isWeb } from 'vs/base/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; +import { getUserDataSyncStore } from 'vs/platform/userDataSync/common/userDataSync'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { Before2D } from 'vs/workbench/browser/dnd'; + +interface IPlaceholderViewlet { + id: string; + name?: string; + iconUrl?: UriComponents; + views?: { when?: string }[]; +} + +interface IPinnedViewlet { + id: string; + pinned: boolean; + order?: number; + visible: boolean; +} interface ICachedViewlet { id: string; @@ -55,7 +73,8 @@ export class ActivitybarPart extends Part implements IActivityBarService { _serviceBrand: undefined; private static readonly ACTION_HEIGHT = 48; - private static readonly PINNED_VIEWLETS = 'workbench.activity.pinnedViewlets'; + private static readonly PINNED_VIEWLETS = 'workbench.activity.pinnedViewlets2'; + private static readonly PLACEHOLDER_VIEWLETS = 'workbench.activity.placeholderViewlets'; //#region IView @@ -92,9 +111,13 @@ export class ActivitybarPart extends Part implements IActivityBarService { @IContextKeyService private readonly contextKeyService: IContextKeyService, @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService, - @IEnvironmentService private readonly environmentService: IEnvironmentService + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, + @IProductService private readonly productService: IProductService ) { super(Parts.ACTIVITYBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); + storageKeysSyncRegistryService.registerStorageKey({ key: ActivitybarPart.PINNED_VIEWLETS, version: 1 }); + this.migrateFromOldCachedViewletsValue(); this.cachedViewlets = this.getCachedViewlets(); for (const cachedViewlet of this.cachedViewlets) { @@ -130,11 +153,10 @@ export class ActivitybarPart extends Part implements IActivityBarService { hidePart: () => this.layoutService.setSideBarHidden(true), dndHandler: new CompositeDragAndDrop(this.viewDescriptorService, ViewContainerLocation.Sidebar, (id: string, focus?: boolean) => this.viewletService.openViewlet(id, focus), - (from: string, to: string) => this.compositeBar.move(from, to), - () => this.getPinnedViewletIds() + (from: string, to: string, before?: Before2D) => this.compositeBar.move(from, to, before?.verticallyBefore) ), compositeSize: 50, - colors: (theme: ITheme) => this.getActivitybarItemColors(theme), + colors: (theme: IColorTheme) => this.getActivitybarItemColors(theme), overflowActionSize: ActivitybarPart.ACTION_HEIGHT })); @@ -337,9 +359,10 @@ export class ActivitybarPart extends Part implements IActivityBarService { container.style.borderLeftWidth = borderColor && !isPositionLeft ? '1px' : ''; container.style.borderLeftStyle = borderColor && !isPositionLeft ? 'solid' : ''; container.style.borderLeftColor = !isPositionLeft ? borderColor : ''; + // container.style.outlineColor = this.getColor(ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND) ?? ''; } - private getActivitybarItemColors(theme: ITheme): ICompositeBarColors { + private getActivitybarItemColors(theme: IColorTheme): ICompositeBarColors { return { activeForegroundColor: theme.getColor(ACTIVITY_BAR_FOREGROUND), inactiveForegroundColor: theme.getColor(ACTIVITY_BAR_INACTIVE_FOREGROUND), @@ -354,7 +377,17 @@ export class ActivitybarPart extends Part implements IActivityBarService { private createGlobalActivityActionBar(container: HTMLElement): void { this.globalActivityActionBar = this._register(new ActionBar(container, { - actionViewItemProvider: action => this.instantiationService.createInstance(GlobalActivityActionViewItem, action as ActivityAction, (theme: ITheme) => this.getActivitybarItemColors(theme)), + actionViewItemProvider: action => { + if (action.id === 'workbench.actions.manage') { + return this.instantiationService.createInstance(GlobalActivityActionViewItem, action as ActivityAction, (theme: IColorTheme) => this.getActivitybarItemColors(theme)); + } + + if (action.id === 'workbench.actions.accounts') { + return this.instantiationService.createInstance(AccountsActionViewItem, action as ActivityAction, (theme: IColorTheme) => this.getActivitybarItemColors(theme)); + } + + throw new Error(`No view item for action '${action.id}'`); + }, orientation: ActionsOrientation.VERTICAL, ariaLabel: nls.localize('manage', "Manage"), animated: false @@ -366,6 +399,16 @@ export class ActivitybarPart extends Part implements IActivityBarService { cssClass: 'codicon-settings-gear' }); + if (getUserDataSyncStore(this.productService, this.configurationService)) { + const profileAction = new ActivityAction({ + id: 'workbench.actions.accounts', + name: nls.localize('accounts', "Accounts"), + cssClass: 'codicon-account' + }); + + this.globalActivityActionBar.push(profileAction); + } + this.globalActivityActionBar.push(this.globalActivityAction); } @@ -511,10 +554,15 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.compositeBar.layout(new Dimension(width, availableHeight)); } + private getViewContainer(viewletId: string): ViewContainer | undefined { + const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); + return viewContainerRegistry.get(viewletId); + } + private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void { if (e.key === ActivitybarPart.PINNED_VIEWLETS && e.scope === StorageScope.GLOBAL - && this.cachedViewletsValue !== this.getStoredCachedViewletsValue() /* This checks if current window changed the value or not */) { - this._cachedViewletsValue = undefined; + && this.pinnedViewletsValue !== this.getStoredPinnedViewletsValue() /* This checks if current window changed the value or not */) { + this._pinnedViewletsValue = undefined; const newCompositeItems: ICompositeBarItem[] = []; const compositeItems = this.compositeBar.getCompositeBarItems(); const cachedViewlets = this.getCachedViewlets(); @@ -566,64 +614,88 @@ export class ActivitybarPart extends Part implements IActivityBarService { } } - this.cachedViewletsValue = JSON.stringify(state); + this.storeCachedViewletsState(state); } private getCachedViewlets(): ICachedViewlet[] { - const storedStates: Array = JSON.parse(this.cachedViewletsValue); - const cachedViewlets = storedStates.map(c => { - const serialized: ICachedViewlet = typeof c === 'string' /* migration from pinned states to composites states */ ? { id: c, pinned: true, order: undefined, visible: true, name: undefined, iconUrl: undefined, views: undefined } : c; - serialized.visible = isUndefinedOrNull(serialized.visible) ? true : serialized.visible; - return serialized; - }); - - for (const old of this.loadOldCachedViewlets()) { - const cachedViewlet = cachedViewlets.filter(cached => cached.id === old.id)[0]; + const cachedViewlets: Array = JSON.parse(this.pinnedViewletsValue); + for (const placeholderViewlet of JSON.parse(this.placeholderViewletsValue)) { + const cachedViewlet = cachedViewlets.filter(cached => cached.id === placeholderViewlet.id)[0]; if (cachedViewlet) { - cachedViewlet.name = old.name; - cachedViewlet.iconUrl = old.iconUrl; - cachedViewlet.views = old.views; + cachedViewlet.name = placeholderViewlet.name; + cachedViewlet.iconUrl = placeholderViewlet.iconUrl; + cachedViewlet.views = placeholderViewlet.views; } } return cachedViewlets; } - private loadOldCachedViewlets(): ICachedViewlet[] { - const previousState = this.storageService.get('workbench.activity.placeholderViewlets', StorageScope.GLOBAL, '[]'); - const result: ICachedViewlet[] = JSON.parse(previousState); - this.storageService.remove('workbench.activity.placeholderViewlets', StorageScope.GLOBAL); - - return result; + private storeCachedViewletsState(cachedViewlets: ICachedViewlet[]): void { + this.pinnedViewletsValue = JSON.stringify(cachedViewlets.map(({ id, pinned, visible, order }) => ({ id, pinned, visible, order }))); + this.placeholderViewletsValue = JSON.stringify(cachedViewlets.map(({ id, iconUrl, name, views }) => ({ id, iconUrl, name, views }))); } - private _cachedViewletsValue: string | undefined; - private get cachedViewletsValue(): string { - if (!this._cachedViewletsValue) { - this._cachedViewletsValue = this.getStoredCachedViewletsValue(); + private _pinnedViewletsValue: string | undefined; + private get pinnedViewletsValue(): string { + if (!this._pinnedViewletsValue) { + this._pinnedViewletsValue = this.getStoredPinnedViewletsValue(); } - return this._cachedViewletsValue; + return this._pinnedViewletsValue; } - private set cachedViewletsValue(cachedViewletsValue: string) { - if (this.cachedViewletsValue !== cachedViewletsValue) { - this._cachedViewletsValue = cachedViewletsValue; - this.setStoredCachedViewletsValue(cachedViewletsValue); + private set pinnedViewletsValue(pinnedViewletsValue: string) { + if (this.pinnedViewletsValue !== pinnedViewletsValue) { + this._pinnedViewletsValue = pinnedViewletsValue; + this.setStoredPinnedViewletsValue(pinnedViewletsValue); } } - private getStoredCachedViewletsValue(): string { + private getStoredPinnedViewletsValue(): string { return this.storageService.get(ActivitybarPart.PINNED_VIEWLETS, StorageScope.GLOBAL, '[]'); } - private setStoredCachedViewletsValue(value: string): void { + private setStoredPinnedViewletsValue(value: string): void { this.storageService.store(ActivitybarPart.PINNED_VIEWLETS, value, StorageScope.GLOBAL); } - private getViewContainer(viewletId: string): ViewContainer | undefined { - const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); - return viewContainerRegistry.get(viewletId); + private _placeholderViewletsValue: string | undefined; + private get placeholderViewletsValue(): string { + if (!this._placeholderViewletsValue) { + this._placeholderViewletsValue = this.getStoredPlaceholderViewletsValue(); + } + + return this._placeholderViewletsValue; + } + + private set placeholderViewletsValue(placeholderViewletsValue: string) { + if (this.placeholderViewletsValue !== placeholderViewletsValue) { + this._placeholderViewletsValue = placeholderViewletsValue; + this.setStoredPlaceholderViewletsValue(placeholderViewletsValue); + } + } + + private getStoredPlaceholderViewletsValue(): string { + return this.storageService.get(ActivitybarPart.PLACEHOLDER_VIEWLETS, StorageScope.GLOBAL, '[]'); + } + + private setStoredPlaceholderViewletsValue(value: string): void { + this.storageService.store(ActivitybarPart.PLACEHOLDER_VIEWLETS, value, StorageScope.GLOBAL); + } + + private migrateFromOldCachedViewletsValue(): void { + const value = this.storageService.get('workbench.activity.pinnedViewlets', StorageScope.GLOBAL); + if (value !== undefined) { + const storedStates: Array = JSON.parse(value); + const cachedViewlets = storedStates.map(c => { + const serialized: ICachedViewlet = typeof c === 'string' /* migration from pinned states to composites states */ ? { id: c, pinned: true, order: undefined, visible: true, name: undefined, iconUrl: undefined, views: undefined } : c; + serialized.visible = isUndefinedOrNull(serialized.visible) ? true : serialized.visible; + return serialized; + }); + this.storeCachedViewletsState(cachedViewlets); + this.storageService.remove('workbench.activity.pinnedViewlets', StorageScope.GLOBAL); + } } toJSON(): object { diff --git a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css index 5d5cf2b498a..4859ec83fed 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css @@ -9,6 +9,49 @@ margin-bottom: 4px; } +.monaco-workbench .activitybar > .content .composite-bar > .monaco-action-bar .action-item::before, +.monaco-workbench .activitybar > .content .composite-bar > .monaco-action-bar .action-item::after { + position: absolute; + content: ''; + width: 48px; + height: 2px; + display: block; + background-color: var(--insert-border-color); + opacity: 0; + transition-property: opacity; + transition-duration: 0ms; + transition-delay: 100ms; +} + +.monaco-workbench .activitybar > .content > .composite-bar > .monaco-action-bar .action-item::before { + margin-top: -3px; + margin-bottom: 1px; +} + +/* Override top element since it would be cut off */ +.monaco-workbench .activitybar > .content > .composite-bar > .monaco-action-bar .action-item:first-of-type::before { + margin-top: 0px; + margin-bottom: 0px; +} + +.monaco-workbench .activitybar > .content > .composite-bar > .monaco-action-bar .action-item::after { + margin-top: 1px; + margin-bottom: -3px; +} + +.monaco-workbench .activitybar > .content > .composite-bar > .monaco-action-bar .action-item.top::before, +.monaco-workbench .activitybar > .content > .composite-bar > .monaco-action-bar .action-item.top::after, +.monaco-workbench .activitybar > .content > .composite-bar > .monaco-action-bar .action-item.bottom::before, +.monaco-workbench .activitybar > .content > .composite-bar > .monaco-action-bar .action-item.bottom::after { + transition-delay: 0s; +} + +.monaco-workbench .activitybar > .content > .composite-bar > .monaco-action-bar .action-item.top::before, +.monaco-workbench .activitybar > .content > .composite-bar > .monaco-action-bar .action-item.bottom::after, +.monaco-workbench .activitybar > .content.dragged-over > .composite-bar > .monaco-action-bar .action-item:last-of-type::after { + opacity: 1; +} + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-label { position: relative; z-index: 1; diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 8ff7cfd6917..d09d7fc37a5 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -11,21 +11,19 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IBadge } from 'vs/workbench/services/activity/common/activity'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ActionBar, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { CompositeActionViewItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionViewItem, ActivityAction, ICompositeBar, ICompositeBarColors, DraggedCompositeIdentifier } from 'vs/workbench/browser/parts/compositeBarActions'; -import { Dimension, $, addDisposableListener, EventType, EventHelper } from 'vs/base/browser/dom'; +import { CompositeActionViewItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionViewItem, ActivityAction, ICompositeBar, ICompositeBarColors } from 'vs/workbench/browser/parts/compositeBarActions'; +import { Dimension, $, addDisposableListener, EventType, EventHelper, toggleClass, isAncestor } from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Widget } from 'vs/base/browser/ui/widget'; import { isUndefinedOrNull } from 'vs/base/common/types'; -import { LocalSelectionTransfer, DragAndDropObserver } from 'vs/workbench/browser/dnd'; -import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; +import { IColorTheme } from 'vs/platform/theme/common/themeService'; import { Emitter } from 'vs/base/common/event'; -import { DraggedViewIdentifier } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { Registry } from 'vs/platform/registry/common/platform'; import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; -import { ICompositeDragAndDrop, CompositeDragAndDropData } from 'vs/base/parts/composite/browser/compositeDnd'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; import { IComposite } from 'vs/workbench/common/composite'; +import { CompositeDragAndDropData, CompositeDragAndDropObserver, IDraggedCompositeData, ICompositeDragAndDrop, Before2D } from 'vs/workbench/browser/dnd'; export interface ICompositeBarItem { id: string; @@ -41,30 +39,38 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { private viewDescriptorService: IViewDescriptorService, private targetContainerLocation: ViewContainerLocation, private openComposite: (id: string, focus?: boolean) => Promise, - private moveComposite: (from: string, to: string) => void, - private getVisibleCompositeIds: () => string[] + private moveComposite: (from: string, to: string, before?: Before2D) => void, ) { } - drop(data: CompositeDragAndDropData, targetCompositeId: string | undefined, originalEvent: DragEvent): void { + drop(data: CompositeDragAndDropData, targetCompositeId: string | undefined, originalEvent: DragEvent, before?: Before2D): void { const dragData = data.getData(); const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); if (dragData.type === 'composite') { const currentContainer = viewContainerRegistry.get(dragData.id)!; const currentLocation = viewContainerRegistry.getViewContainerLocation(currentContainer); + + // Inserting a composite between composites if (targetCompositeId) { - if (currentLocation !== this.targetContainerLocation && this.targetContainerLocation !== ViewContainerLocation.Panel) { - const destinationContainer = viewContainerRegistry.get(targetCompositeId); - if (destinationContainer && !destinationContainer.rejectAddedViews) { - const viewsToMove = this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors.filter(vd => vd.canMoveView); - this.viewDescriptorService.moveViewsToContainer(viewsToMove, destinationContainer); - this.openComposite(targetCompositeId, true).then(composite => { + // ... on the same composite bar + if (currentLocation === this.targetContainerLocation) { + this.moveComposite(dragData.id, targetCompositeId, before); + } + // ... on a different composite bar + else { + const viewsToMove = this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors.filter(vd => vd.canMoveView); + if (viewsToMove.length === 1) { + this.viewDescriptorService.moveViewToLocation(viewsToMove[0], this.targetContainerLocation); + + const newContainer = this.viewDescriptorService.getViewContainer(viewsToMove[0].id)!; + + this.moveComposite(newContainer.id, targetCompositeId, before); + + this.openComposite(newContainer.id, true).then(composite => { if (composite && viewsToMove.length === 1) { composite.openView(viewsToMove[0].id, true); } }); } - } else { - this.moveComposite(dragData.id, targetCompositeId); } } else { const draggedViews = this.viewDescriptorService.getViewDescriptors(currentContainer).allViewDescriptors; @@ -76,38 +82,24 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { } if (dragData.type === 'view') { - const viewDescriptor = this.viewDescriptorService.getViewDescriptor(dragData.id); - if (viewDescriptor && viewDescriptor.canMoveView) { - if (targetCompositeId) { - const destinationContainer = viewContainerRegistry.get(targetCompositeId); - if (destinationContainer && !destinationContainer.rejectAddedViews) { - if (this.targetContainerLocation === ViewContainerLocation.Sidebar) { - this.viewDescriptorService.moveViewsToContainer([viewDescriptor], destinationContainer); - this.openComposite(targetCompositeId, true).then(composite => { - if (composite) { - composite.openView(viewDescriptor.id, true); - } - }); - } else { - this.viewDescriptorService.moveViewToLocation(viewDescriptor, this.targetContainerLocation); - this.moveComposite(this.viewDescriptorService.getViewContainer(viewDescriptor.id)!.id, targetCompositeId); - } - } - } else { - this.viewDescriptorService.moveViewToLocation(viewDescriptor, this.targetContainerLocation); - const newCompositeId = this.viewDescriptorService.getViewContainer(dragData.id)!.id; - const visibleItems = this.getVisibleCompositeIds(); - const targetId = visibleItems.length ? visibleItems[visibleItems.length - 1] : undefined; - if (targetId && targetId !== newCompositeId) { - this.moveComposite(newCompositeId, targetId); - } + if (targetCompositeId) { + const viewToMove = this.viewDescriptorService.getViewDescriptor(dragData.id)!; - this.openComposite(newCompositeId, true).then(composite => { + if (viewToMove && viewToMove.canMoveView) { + this.viewDescriptorService.moveViewToLocation(viewToMove, this.targetContainerLocation); + + const newContainer = this.viewDescriptorService.getViewContainer(viewToMove.id)!; + + this.moveComposite(newContainer.id, targetCompositeId, before); + + this.openComposite(newContainer.id, true).then(composite => { if (composite) { - composite.openView(viewDescriptor.id, true); + composite.openView(viewToMove.id, true); } }); } + } else { + } } } @@ -129,41 +121,19 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { const currentContainer = viewContainerRegistry.get(dragData.id)!; const currentLocation = viewContainerRegistry.getViewContainerLocation(currentContainer); - // ... to the same location + // ... to the same composite location if (currentLocation === this.targetContainerLocation) { return true; } - // ... across view containers but without a destination composite - if (!targetCompositeId) { - const draggedViews = this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors; - if (draggedViews.some(vd => !vd.canMoveView)) { - return false; - } - - if (draggedViews.length !== 1) { - return false; - } - - const defaultLocation = viewContainerRegistry.getViewContainerLocation(this.viewDescriptorService.getDefaultContainer(draggedViews[0].id)!); - if (this.targetContainerLocation === ViewContainerLocation.Sidebar && this.targetContainerLocation !== defaultLocation) { - return false; - } - - return true; - } - - // ... from panel to the sidebar - if (this.targetContainerLocation === ViewContainerLocation.Sidebar) { - const destinationContainer = viewContainerRegistry.get(targetCompositeId); - return !!destinationContainer && - !destinationContainer.rejectAddedViews && - this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors.some(vd => vd.canMoveView); - } - // ... from sidebar to the panel - else { + // ... to another composite location + const draggedViews = this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors; + if (draggedViews.length !== 1) { return false; } + + // ... single view + return !!draggedViews[0].canMoveView; } else { // Dragging an individual view const viewDescriptor = this.viewDescriptorService.getViewDescriptor(dragData.id); @@ -174,13 +144,7 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { } // ... to create a view container - if (!targetCompositeId) { - return this.targetContainerLocation === ViewContainerLocation.Panel; - } - - // ... into a destination - const destinationContainer = viewContainerRegistry.get(targetCompositeId); - return !!destinationContainer && !destinationContainer.rejectAddedViews; + return true; } } } @@ -188,7 +152,7 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { export interface ICompositeBarOptions { readonly icon: boolean; readonly orientation: ActionsOrientation; - readonly colors: (theme: ITheme) => ICompositeBarColors; + readonly colors: (theme: IColorTheme) => ICompositeBarColors; readonly compositeSize: number; readonly overflowActionSize: number; readonly dndHandler: ICompositeDragAndDrop; @@ -215,15 +179,12 @@ export class CompositeBar extends Widget implements ICompositeBar { private visibleComposites: string[]; private compositeSizeInBar: Map; - private compositeTransfer: LocalSelectionTransfer; - private readonly _onDidChange: Emitter = this._register(new Emitter()); readonly onDidChange = this._onDidChange.event; constructor( items: ICompositeBarItem[], private options: ICompositeBarOptions, - @IThemeService private readonly themeService: IThemeService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextMenuService private readonly contextMenuService: IContextMenuService ) { @@ -232,7 +193,6 @@ export class CompositeBar extends Widget implements ICompositeBar { this.model = new CompositeBarModel(items, options); this.visibleComposites = []; this.compositeSizeInBar = new Map(); - this.compositeTransfer = LocalSelectionTransfer.getInstance(); this.computeSizes(this.model.visibleItems); } @@ -252,8 +212,6 @@ export class CompositeBar extends Widget implements ICompositeBar { create(parent: HTMLElement): HTMLElement { const actionBarDiv = parent.appendChild($('.composite-bar')); - const excessDiv = parent.appendChild($('.composite-bar-excess')); - this.compositeSwitcherBar = this._register(new ActionBar(actionBarDiv, { actionViewItemProvider: (action: IAction) => { if (action instanceof CompositeOverflowActivityAction) { @@ -278,100 +236,28 @@ export class CompositeBar extends Widget implements ICompositeBar { // Contextmenu for composites this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, e => this.showContextMenu(e))); - // Allow to drop at the end to move composites to the end - this._register(new DragAndDropObserver(excessDiv, { - onDragOver: (e: DragEvent) => { - if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) { - EventHelper.stop(e, true); - - const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype); - if (Array.isArray(data)) { - const draggedCompositeId = data[0].id; - - // Check if drop is allowed - if (e.dataTransfer && !this.options.dndHandler.onDragOver(new CompositeDragAndDropData('composite', draggedCompositeId), undefined, e)) { - e.dataTransfer.dropEffect = 'none'; - } - } + // Register a drop target on the whole bar to prevent forbidden feedback + this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(parent, { + onDragOver: (e: IDraggedCompositeData) => { + // don't add feedback if this is over the composite bar actions + if (e.eventData.target && isAncestor(e.eventData.target as HTMLElement, actionBarDiv)) { + toggleClass(parent, 'dragged-over', false); + return; } - if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) { - EventHelper.stop(e, true); - - const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype); - if (Array.isArray(data)) { - const draggedViewId = data[0].id; - - // Check if drop is allowed - if (e.dataTransfer && !this.options.dndHandler.onDragOver(new CompositeDragAndDropData('view', draggedViewId), undefined, e)) { - e.dataTransfer.dropEffect = 'none'; - } - } - } + const pinnedItems = this.getPinnedComposites(); + const validDropTarget = this.options.dndHandler.onDragOver(e.dragAndDropData, pinnedItems[pinnedItems.length - 1].id, e.eventData); + toggleClass(parent, 'dragged-over', validDropTarget); }, - onDragEnter: (e: DragEvent) => { - if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) { - EventHelper.stop(e, true); - - const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype); - if (Array.isArray(data)) { - const draggedCompositeId = data[0].id; - - // Check if drop is allowed - const validDropTarget = this.options.dndHandler.onDragEnter(new CompositeDragAndDropData('composite', draggedCompositeId), undefined, e); - this.updateFromDragging(excessDiv, validDropTarget); - } - } - - if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) { - EventHelper.stop(e, true); - - const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype); - if (Array.isArray(data)) { - const draggedViewId = data[0].id; - - // Check if drop is allowed - const validDropTarget = this.options.dndHandler.onDragEnter(new CompositeDragAndDropData('view', draggedViewId), undefined, e); - this.updateFromDragging(excessDiv, validDropTarget); - } - } - }, - - onDragLeave: (e: DragEvent) => { - if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype) || - this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) { - this.updateFromDragging(excessDiv, false); - } - }, - onDragEnd: (e: DragEvent) => { - // no-op, will not be called - }, - onDrop: (e: DragEvent) => { - if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) { - EventHelper.stop(e, true); - - const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype); - if (Array.isArray(data)) { - const draggedCompositeId = data[0].id; - this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype); - - this.options.dndHandler.drop(new CompositeDragAndDropData('composite', draggedCompositeId), undefined, e); - this.updateFromDragging(excessDiv, false); - } - } - - if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) { - const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype); - if (Array.isArray(data)) { - const draggedViewId = data[0].id; - this.compositeTransfer.clearData(DraggedViewIdentifier.prototype); - - this.options.dndHandler.drop(new CompositeDragAndDropData('view', draggedViewId), undefined, e); - this.updateFromDragging(excessDiv, false); - } - } + onDragLeave: (e: IDraggedCompositeData) => { + toggleClass(parent, 'dragged-over', false); }, + onDrop: (e: IDraggedCompositeData) => { + const pinnedItems = this.getPinnedComposites(); + this.options.dndHandler.drop(e.dragAndDropData, pinnedItems[pinnedItems.length - 1].id, e.eventData, { horizontallyBefore: false, verticallyBefore: false }); + toggleClass(parent, 'dragged-over', false); + } })); return actionBarDiv; @@ -476,13 +362,6 @@ export class CompositeBar extends Widget implements ICompositeBar { } } - private updateFromDragging(element: HTMLElement, isDragging: boolean): void { - const theme = this.themeService.getTheme(); - const dragBackground = this.options.colors(theme).dragAndDropBackground; - - element.style.backgroundColor = isDragging && dragBackground ? dragBackground.toString() : ''; - } - private resetActiveComposite(compositeId: string) { const defaultCompositeId = this.options.getDefaultCompositeId(); @@ -519,10 +398,34 @@ export class CompositeBar extends Widget implements ICompositeBar { return item?.pinned; } - move(compositeId: string, toCompositeId: string): void { - if (this.model.move(compositeId, toCompositeId)) { - // timeout helps to prevent artifacts from showing up - setTimeout(() => this.updateCompositeSwitcher(), 0); + move(compositeId: string, toCompositeId: string, before?: boolean): void { + + if (before !== undefined) { + const fromIndex = this.model.items.findIndex(c => c.id === compositeId); + let toIndex = this.model.items.findIndex(c => c.id === toCompositeId); + + if (fromIndex >= 0 && toIndex >= 0) { + if (!before && fromIndex > toIndex) { + toIndex++; + } + + if (before && fromIndex < toIndex) { + toIndex--; + } + + if (toIndex < this.model.items.length && toIndex >= 0 && toIndex !== fromIndex) { + if (this.model.move(this.model.items[fromIndex].id, this.model.items[toIndex].id)) { + // timeout helps to prevent artifacts from showing up + setTimeout(() => this.updateCompositeSwitcher(), 0); + } + } + } + + } else { + if (this.model.move(compositeId, toCompositeId)) { + // timeout helps to prevent artifacts from showing up + setTimeout(() => this.updateCompositeSwitcher(), 0); + } } } diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index af39c3cfdf6..16d5594cf6f 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -10,7 +10,7 @@ import { BaseActionViewItem, IBaseActionViewItemOptions, Separator } from 'vs/ba import { ICommandService } from 'vs/platform/commands/common/commands'; import { dispose, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { TextBadge, NumberBadge, IBadge, IconBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; @@ -18,10 +18,8 @@ import { DelayedDragHandler } from 'vs/base/browser/dnd'; import { IActivity } from 'vs/workbench/common/activity'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Emitter } from 'vs/base/common/event'; -import { DragAndDropObserver, LocalSelectionTransfer } from 'vs/workbench/browser/dnd'; +import { LocalSelectionTransfer, DraggedCompositeIdentifier, DraggedViewIdentifier, CompositeDragAndDropObserver, ICompositeDragAndDrop, Before2D } from 'vs/workbench/browser/dnd'; import { Color } from 'vs/base/common/color'; -import { DraggedViewIdentifier } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { ICompositeDragAndDrop, CompositeDragAndDropData } from 'vs/base/parts/composite/browser/compositeDnd'; export interface ICompositeActivity { badge: IBadge; @@ -124,7 +122,7 @@ export interface ICompositeBarColors { export interface IActivityActionViewItemOptions extends IBaseActionViewItemOptions { icon?: boolean; - colors: (theme: ITheme) => ICompositeBarColors; + colors: (theme: IColorTheme) => ICompositeBarColors; } export class ActivityActionViewItem extends BaseActionViewItem { @@ -144,7 +142,7 @@ export class ActivityActionViewItem extends BaseActionViewItem { ) { super(null, action, options); - this._register(this.themeService.onThemeChange(this.onThemeChange, this)); + this._register(this.themeService.onDidColorThemeChange(this.onThemeChange, this)); this._register(action.onDidChangeActivity(this.updateActivity, this)); this._register(action.onDidChangeBadge(this.updateBadge, this)); } @@ -154,7 +152,7 @@ export class ActivityActionViewItem extends BaseActionViewItem { } protected updateStyles(): void { - const theme = this.themeService.getTheme(); + const theme = this.themeService.getColorTheme(); const colors = this.options.colors(theme); if (this.label) { @@ -167,11 +165,15 @@ export class ActivityActionViewItem extends BaseActionViewItem { // Apply foreground color to activity bar items provided with codicons this.label.style.color = foreground ? foreground.toString() : ''; } + + const dragColor = colors.activeBackgroundColor || colors.activeForegroundColor; + this.container.style.setProperty('--insert-border-color', dragColor ? dragColor.toString() : ''); } else { const foreground = this._action.checked ? colors.activeForegroundColor : colors.inactiveForegroundColor; const borderBottomColor = this._action.checked ? colors.activeBorderBottomColor : null; this.label.style.color = foreground ? foreground.toString() : ''; this.label.style.borderBottomColor = borderBottomColor ? borderBottomColor.toString() : ''; + this.container.style.setProperty('--insert-border-color', colors.activeForegroundColor ? colors.activeForegroundColor.toString() : ''); } } @@ -233,7 +235,7 @@ export class ActivityActionViewItem extends BaseActionViewItem { this.updateStyles(); } - private onThemeChange(theme: ITheme): void { + private onThemeChange(theme: IColorTheme): void { this.updateStyles(); } @@ -378,7 +380,7 @@ export class CompositeOverflowActivityActionViewItem extends ActivityActionViewI private getActiveCompositeId: () => string | undefined, private getBadge: (compositeId: string) => IBadge, private getCompositeOpenAction: (compositeId: string) => Action, - colors: (theme: ITheme) => ICompositeBarColors, + colors: (theme: IColorTheme) => ICompositeBarColors, @IContextMenuService private readonly contextMenuService: IContextMenuService, @IThemeService themeService: IThemeService ) { @@ -445,14 +447,6 @@ class ManageExtensionAction extends Action { } } -export class DraggedCompositeIdentifier { - constructor(private _compositeId: string) { } - - get id(): string { - return this._compositeId; - } -} - export class CompositeActionViewItem extends ActivityActionViewItem { private static manageExtensionAction: ManageExtensionAction; @@ -465,7 +459,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem { private toggleCompositePinnedAction: Action, private compositeContextMenuActionsProvider: (compositeId: string) => ReadonlyArray, private contextMenuActionsProvider: () => ReadonlyArray, - colors: (theme: ITheme) => ICompositeBarColors, + colors: (theme: IColorTheme) => ICompositeBarColors, icon: boolean, private dndHandler: ICompositeDragAndDrop, private compositeBar: ICompositeBar, @@ -522,114 +516,46 @@ export class CompositeActionViewItem extends ActivityActionViewItem { this.showContextMenu(container); })); + let insertDropBefore: Before2D | undefined = undefined; // Allow to drag - this._register(dom.addDisposableListener(this.container, dom.EventType.DRAG_START, (e: DragEvent) => { - if (e.dataTransfer) { - e.dataTransfer.effectAllowed = 'move'; - } - - // Registe as dragged to local transfer - this.compositeTransfer.setData([new DraggedCompositeIdentifier(this.activity.id)], DraggedCompositeIdentifier.prototype); - - // Trigger the action even on drag start to prevent clicks from failing that started a drag - if (!this.getAction().checked) { - this.getAction().run(); - } - })); - - this._register(new DragAndDropObserver(this.container, { - onDragEnter: e => { - if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) { - const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype); - if (Array.isArray(data) && data[0].id !== this.activity.id) { - const validDropTarget = this.dndHandler.onDragEnter(new CompositeDragAndDropData('composite', data[0].id), this.activity.id, e); - this.updateFromDragging(container, validDropTarget); - } - } - - if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) { - const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype); - if (Array.isArray(data) && data[0].id !== this.activity.id) { - const validDropTarget = this.dndHandler.onDragEnter(new CompositeDragAndDropData('view', data[0].id), this.activity.id, e); - this.updateFromDragging(container, validDropTarget); - } - } - }, - + this._register(CompositeDragAndDropObserver.INSTANCE.registerDraggable(this.container, () => { return { type: 'composite', id: this.activity.id }; }, { onDragOver: e => { - dom.EventHelper.stop(e, true); - if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) { - const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype); - if (Array.isArray(data)) { - const draggedCompositeId = data[0].id; - if (draggedCompositeId !== this.activity.id) { - if (e.dataTransfer && !this.dndHandler.onDragOver(new CompositeDragAndDropData('composite', draggedCompositeId), this.activity.id, e)) { - e.dataTransfer.dropEffect = 'none'; - } - } - } - } - - if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) { - const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype); - if (Array.isArray(data)) { - const draggedViewId = data[0].id; - if (e.dataTransfer && !this.dndHandler.onDragOver(new CompositeDragAndDropData('view', draggedViewId), this.activity.id, e)) { - e.dataTransfer.dropEffect = 'none'; - } - } - } + const isValidMove = e.dragAndDropData.getData().id !== this.activity.id && this.dndHandler.onDragOver(e.dragAndDropData, this.activity.id, e.eventData); + insertDropBefore = this.updateFromDragging(container, isValidMove, e.eventData); }, onDragLeave: e => { - if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype) || - this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) { - this.updateFromDragging(container, false); - } + insertDropBefore = this.updateFromDragging(container, false, e.eventData); }, onDragEnd: e => { - if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) { - this.updateFromDragging(container, false); - - this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype); - } + insertDropBefore = this.updateFromDragging(container, false, e.eventData); }, onDrop: e => { - dom.EventHelper.stop(e, true); - - if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) { - const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype); - if (Array.isArray(data)) { - const draggedCompositeId = data[0].id; - if (draggedCompositeId !== this.activity.id) { - this.updateFromDragging(container, false); - this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype); - - this.dndHandler.drop(new CompositeDragAndDropData('composite', draggedCompositeId), this.activity.id, e); - } - } + dom.EventHelper.stop(e.eventData, true); + this.dndHandler.drop(e.dragAndDropData, this.activity.id, e.eventData, insertDropBefore); + insertDropBefore = this.updateFromDragging(container, false, e.eventData); + }, + onDragStart: e => { + if (e.dragAndDropData.getData().id !== this.activity.id) { + return; } - if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) { - const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype); - if (Array.isArray(data)) { - const draggedViewId = data[0].id; - this.updateFromDragging(container, false); - this.compositeTransfer.clearData(DraggedViewIdentifier.prototype); + if (e.eventData.dataTransfer) { + e.eventData.dataTransfer.effectAllowed = 'move'; + } - this.dndHandler.drop(new CompositeDragAndDropData('view', draggedViewId), this.activity.id, e); - } + // Trigger the action even on drag start to prevent clicks from failing that started a drag + if (!this.getAction().checked) { + this.getAction().run(); } } })); // Activate on drag over to reveal targets [this.badge, this.label].forEach(b => this._register(new DelayedDragHandler(b, () => { - if (!(this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype) || - this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) && - !this.getAction().checked) { + if (!this.getAction().checked) { this.getAction().run(); } }))); @@ -637,11 +563,42 @@ export class CompositeActionViewItem extends ActivityActionViewItem { this.updateStyles(); } - private updateFromDragging(element: HTMLElement, isDragging: boolean): void { - const theme = this.themeService.getTheme(); - const dragBackground = this.options.colors(theme).dragAndDropBackground; + private updateFromDragging(element: HTMLElement, showFeedback: boolean, event: DragEvent): Before2D | undefined { + const rect = element.getBoundingClientRect(); + const posX = event.clientX; + const posY = event.clientY; + const height = rect.bottom - rect.top; + const width = rect.right - rect.left; - element.style.backgroundColor = isDragging && dragBackground ? dragBackground.toString() : ''; + const forceTop = posY <= rect.top + height * 0.4; + const forceBottom = posY > rect.bottom - height * 0.4; + const preferTop = posY <= rect.top + height * 0.5; + + const forceLeft = posX <= rect.left + width * 0.4; + const forceRight = posX > rect.right - width * 0.4; + const preferLeft = posX <= rect.left + width * 0.5; + + const classes = element.classList; + const lastClasses = { + vertical: classes.contains('top') ? 'top' : (classes.contains('bottom') ? 'bottom' : undefined), + horizontal: classes.contains('left') ? 'left' : (classes.contains('right') ? 'right' : undefined) + }; + + const top = forceTop || (preferTop && !lastClasses.vertical) || (!forceBottom && lastClasses.vertical === 'top'); + const bottom = forceBottom || (!preferTop && !lastClasses.vertical) || (!forceTop && lastClasses.vertical === 'bottom'); + const left = forceLeft || (preferLeft && !lastClasses.horizontal) || (!forceRight && lastClasses.horizontal === 'left'); + const right = forceRight || (!preferLeft && !lastClasses.horizontal) || (!forceLeft && lastClasses.horizontal === 'right'); + + dom.toggleClass(element, 'top', showFeedback && top); + dom.toggleClass(element, 'bottom', showFeedback && bottom); + dom.toggleClass(element, 'left', showFeedback && left); + dom.toggleClass(element, 'right', showFeedback && right); + + if (!showFeedback) { + return undefined; + } + + return { verticallyBefore: top, horizontallyBefore: left }; } private showContextMenu(container: HTMLElement): void { diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 27db5400aa3..83d8a6ddf87 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -11,9 +11,8 @@ import * as strings from 'vs/base/common/strings'; import { Emitter } from 'vs/base/common/event'; import * as errors from 'vs/base/common/errors'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; -import { IActionViewItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionViewItem, ActionsOrientation, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; -import { prepareActions } from 'vs/workbench/browser/actions'; import { IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import { Part, IPartOptions } from 'vs/workbench/browser/part'; import { Composite, CompositeRegistry } from 'vs/workbench/browser/composite'; diff --git a/src/vs/workbench/browser/parts/editor/baseEditor.ts b/src/vs/workbench/browser/parts/editor/baseEditor.ts index 1d49f9aed7a..35d96ad23a1 100644 --- a/src/vs/workbench/browser/parts/editor/baseEditor.ts +++ b/src/vs/workbench/browser/parts/editor/baseEditor.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Panel } from 'vs/workbench/browser/panel'; -import { EditorInput, EditorOptions, IEditor, GroupIdentifier, IEditorMemento } from 'vs/workbench/common/editor'; +import { Composite } from 'vs/workbench/browser/composite'; +import { EditorInput, EditorOptions, IEditorPane, GroupIdentifier, IEditorMemento } from 'vs/workbench/common/editor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -33,9 +33,9 @@ import { indexOfPath } from 'vs/base/common/extpath'; * * This class is only intended to be subclassed and not instantiated. */ -export abstract class BaseEditor extends Panel implements IEditor { +export abstract class BaseEditor extends Composite implements IEditorPane { - private static readonly EDITOR_MEMENTOS: Map> = new Map>(); + private static readonly EDITOR_MEMENTOS = new Map>(); readonly minimumWidth = DEFAULT_EDITOR_MIN_DIMENSIONS.width; readonly maximumWidth = DEFAULT_EDITOR_MAX_DIMENSIONS.width; @@ -45,9 +45,12 @@ export abstract class BaseEditor extends Panel implements IEditor { readonly onDidSizeConstraintsChange = Event.None; protected _input: EditorInput | undefined; + get input(): EditorInput | undefined { return this._input; } + protected _options: EditorOptions | undefined; private _group?: IEditorGroup; + get group(): IEditorGroup | undefined { return this._group; } constructor( id: string, @@ -58,18 +61,6 @@ export abstract class BaseEditor extends Panel implements IEditor { super(id, telemetryService, themeService, storageService); } - get input(): EditorInput | undefined { - return this._input; - } - - get options(): EditorOptions | undefined { - return this._options; - } - - get group(): IEditorGroup | undefined { - return this._group; - } - /** * Note: Clients should not call this method, the workbench calls this * method. Calling it otherwise may result in unexpected behavior. @@ -179,17 +170,13 @@ export class EditorMemento implements IEditorMemento { private cleanedUp = false; constructor( - private _id: string, + public readonly id: string, private key: string, private memento: MementoObject, private limit: number, private editorGroupService: IEditorGroupsService ) { } - get id(): string { - return this._id; - } - saveEditorState(group: IEditorGroup, resource: URI, state: T): void; saveEditorState(group: IEditorGroup, editor: EditorInput, state: T): void; saveEditorState(group: IEditorGroup, resourceOrEditor: URI | EditorInput, state: T): void { diff --git a/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts b/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts index 314b1ad3e71..964c8852ac5 100644 --- a/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts @@ -29,8 +29,8 @@ export class BinaryResourceDiffEditor extends SideBySideEditor { } getMetadata(): string | undefined { - const master = this.masterEditor; - const details = this.detailsEditor; + const master = this.masterEditorPane; + const details = this.detailsEditorPane; if (master instanceof BaseBinaryResourceEditor && details instanceof BaseBinaryResourceEditor) { return nls.localize('metadataDiff', "{0} ↔ {1}", details.getMetadata(), master.getMetadata()); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts index 43e0ec1a1ce..dc71187fd56 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts @@ -13,7 +13,7 @@ import { Extensions, IConfigurationRegistry, ConfigurationScope } from 'vs/platf import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; -import { GroupIdentifier } from 'vs/workbench/common/editor'; +import { GroupIdentifier, IEditorPartOptions } from 'vs/workbench/common/editor'; export const IBreadcrumbsService = createDecorator('IEditorBreadcrumbsService'); @@ -72,6 +72,7 @@ export abstract class BreadcrumbsConfig { static readonly SymbolPath = BreadcrumbsConfig._stub<'on' | 'off' | 'last'>('breadcrumbs.symbolPath'); static readonly SymbolSortOrder = BreadcrumbsConfig._stub<'position' | 'name' | 'type'>('breadcrumbs.symbolSortOrder'); static readonly Icons = BreadcrumbsConfig._stub('breadcrumbs.icons'); + static readonly TitleScrollbarSizing = BreadcrumbsConfig._stub('workbench.editor.titleScrollbarSizing'); static readonly FileExcludes = BreadcrumbsConfig._stub('files.exclude'); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 1ea22c49456..1ac222828b0 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -29,7 +29,7 @@ import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/file import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IListService, WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { ColorIdentifier, ColorFunction } from 'vs/platform/theme/common/colorRegistry'; import { attachBreadcrumbsStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -38,7 +38,7 @@ import { ResourceLabel } from 'vs/workbench/browser/labels'; import { BreadcrumbsConfig, IBreadcrumbsService } from 'vs/workbench/browser/parts/editor/breadcrumbs'; import { BreadcrumbElement, EditorBreadcrumbsModel, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel'; import { BreadcrumbsPicker, createBreadcrumbsPicker } from 'vs/workbench/browser/parts/editor/breadcrumbsPicker'; -import { SideBySideEditorInput } from 'vs/workbench/common/editor'; +import { SideBySideEditorInput, IEditorPartOptions } from 'vs/workbench/common/editor'; import { ACTIVE_GROUP, ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -134,6 +134,11 @@ export class BreadcrumbsControl { static readonly HEIGHT = 22; + private static readonly SCROLLBAR_SIZES = { + default: 3, + large: 8 + }; + static readonly Payload_Reveal = {}; static readonly Payload_RevealAside = {}; static readonly Payload_Pick = {}; @@ -148,6 +153,7 @@ export class BreadcrumbsControl { private readonly _cfUseQuickPick: BreadcrumbsConfig; private readonly _cfShowIcons: BreadcrumbsConfig; + private readonly _cfTitleScrollbarSizing: BreadcrumbsConfig; readonly domNode: HTMLDivElement; private readonly _widget: BreadcrumbsWidget; @@ -168,7 +174,7 @@ export class BreadcrumbsControl { @IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IThemeService private readonly _themeService: IThemeService, - @IQuickOpenService private readonly _quickOpenService: IQuickOpenService, + @IQuickInputService private readonly _quickInputService: IQuickInputService, @IConfigurationService private readonly _configurationService: IConfigurationService, @ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService, @IFileService private readonly _fileService: IFileService, @@ -180,7 +186,12 @@ export class BreadcrumbsControl { dom.addClass(this.domNode, 'breadcrumbs-control'); dom.append(container, this.domNode); - this._widget = new BreadcrumbsWidget(this.domNode); + this._cfUseQuickPick = BreadcrumbsConfig.UseQuickPick.bindTo(_configurationService); + this._cfShowIcons = BreadcrumbsConfig.Icons.bindTo(_configurationService); + this._cfTitleScrollbarSizing = BreadcrumbsConfig.TitleScrollbarSizing.bindTo(_configurationService); + + const sizing = this._cfTitleScrollbarSizing.getValue() ?? 'default'; + this._widget = new BreadcrumbsWidget(this.domNode, BreadcrumbsControl.SCROLLBAR_SIZES[sizing]); this._widget.onDidSelectItem(this._onSelectEvent, this, this._disposables); this._widget.onDidFocusItem(this._onFocusEvent, this, this._disposables); this._widget.onDidChangeFocus(this._updateCkBreadcrumbsActive, this, this._disposables); @@ -190,9 +201,6 @@ export class BreadcrumbsControl { this._ckBreadcrumbsVisible = BreadcrumbsControl.CK_BreadcrumbsVisible.bindTo(this._contextKeyService); this._ckBreadcrumbsActive = BreadcrumbsControl.CK_BreadcrumbsActive.bindTo(this._contextKeyService); - this._cfUseQuickPick = BreadcrumbsConfig.UseQuickPick.bindTo(_configurationService); - this._cfShowIcons = BreadcrumbsConfig.Icons.bindTo(_configurationService); - this._disposables.add(breadcrumbsService.register(this._editorGroup.id, this._widget)); } @@ -277,6 +285,14 @@ export class BreadcrumbsControl { this._breadcrumbsDisposables.add(listener); this._breadcrumbsDisposables.add(configListener); + const updateScrollbarSizing = () => { + const sizing = this._cfTitleScrollbarSizing.getValue() ?? 'default'; + this._widget.setHorizontalScrollbarSize(BreadcrumbsControl.SCROLLBAR_SIZES[sizing]); + }; + updateScrollbarSizing(); + const updateScrollbarSizeListener = this._cfTitleScrollbarSizing.onDidChange(updateScrollbarSizing); + this._breadcrumbsDisposables.add(updateScrollbarSizeListener); + // close picker on hide/update this._breadcrumbsDisposables.add({ dispose: () => { @@ -290,10 +306,10 @@ export class BreadcrumbsControl { } private _getActiveCodeEditor(): ICodeEditor | undefined { - if (!this._editorGroup.activeControl) { + if (!this._editorGroup.activeEditorPane) { return undefined; } - let control = this._editorGroup.activeControl.getControl(); + let control = this._editorGroup.activeEditorPane.getControl(); let editor: ICodeEditor | undefined; if (isCodeEditor(control)) { editor = control as ICodeEditor; @@ -343,7 +359,7 @@ export class BreadcrumbsControl { // using quick pick this._widget.setFocused(undefined); this._widget.setSelection(undefined); - this._quickOpenService.show(element instanceof TreeElement ? '@' : ''); + this._quickInputService.quickAccess.show(element instanceof TreeElement ? '@' : ''); return; } @@ -713,8 +729,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } widget.setFocused(undefined); widget.setSelection(undefined); - if (groups.activeGroup.activeControl) { - groups.activeGroup.activeControl.focus(); + if (groups.activeGroup.activeEditorPane) { + groups.activeGroup.activeEditorPane.focus(); } } }); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index 022b7645126..967cdb722c6 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -24,10 +24,11 @@ import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder } from 'vs/platf import { ResourceLabels, IResourceLabel, DEFAULT_LABELS_CONTAINER } from 'vs/workbench/browser/labels'; import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs'; import { BreadcrumbElement, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel'; -import { IFileIconTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; + import { IAsyncDataSource, ITreeRenderer, ITreeNode, ITreeFilter, TreeVisibility, ITreeSorter } from 'vs/base/browser/ui/tree/tree'; import { OutlineVirtualDelegate, OutlineGroupRenderer, OutlineElementRenderer, OutlineItemComparator, OutlineIdentityProvider, OutlineNavigationLabelProvider, OutlineDataSource, OutlineSortOrder, OutlineFilter } from 'vs/editor/contrib/documentSymbols/outlineTree'; import { IIdentityProvider, IListVirtualDelegate, IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list'; +import { IFileIconTheme, IThemeService } from 'vs/platform/theme/common/themeService'; export function createBreadcrumbsPicker(instantiationService: IInstantiationService, parent: HTMLElement, element: BreadcrumbElement): BreadcrumbsPicker { return element instanceof FileElement @@ -69,7 +70,7 @@ export abstract class BreadcrumbsPicker { constructor( parent: HTMLElement, @IInstantiationService protected readonly _instantiationService: IInstantiationService, - @IWorkbenchThemeService protected readonly _themeService: IWorkbenchThemeService, + @IThemeService protected readonly _themeService: IThemeService, @IConfigurationService protected readonly _configurationService: IConfigurationService, ) { this._domNode = document.createElement('div'); @@ -86,7 +87,7 @@ export abstract class BreadcrumbsPicker { show(input: any, maxHeight: number, width: number, arrowSize: number, arrowOffset: number): void { - const theme = this._themeService.getTheme(); + const theme = this._themeService.getColorTheme(); const color = theme.getColor(breadcrumbsPickerBackground); this._arrow = document.createElement('div'); @@ -97,7 +98,7 @@ export abstract class BreadcrumbsPicker { this._treeContainer = document.createElement('div'); this._treeContainer.style.background = color ? color.toString() : ''; this._treeContainer.style.paddingTop = '2px'; - this._treeContainer.style.boxShadow = `0px 5px 8px ${this._themeService.getTheme().getColor(widgetShadow)}`; + this._treeContainer.style.boxShadow = `0px 5px 8px ${this._themeService.getColorTheme().getColor(widgetShadow)}`; this._domNode.appendChild(this._treeContainer); this._layoutInfo = { maxHeight, width, arrowSize, arrowOffset, inputHeight: 0 }; @@ -351,7 +352,7 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { constructor( parent: HTMLElement, @IInstantiationService instantiationService: IInstantiationService, - @IWorkbenchThemeService themeService: IWorkbenchThemeService, + @IThemeService themeService: IThemeService, @IConfigurationService configService: IConfigurationService, @IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService, ) { @@ -433,7 +434,7 @@ export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker { constructor( parent: HTMLElement, @IInstantiationService instantiationService: IInstantiationService, - @IWorkbenchThemeService themeService: IWorkbenchThemeService, + @IThemeService themeService: IThemeService, @IConfigurationService configurationService: IConfigurationService, ) { super(parent, instantiationService, themeService, configurationService); diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 10f1f735dd4..c318be83c68 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -6,8 +6,6 @@ import { Registry } from 'vs/platform/registry/common/platform'; import * as nls from 'vs/nls'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { Action, IAction } from 'vs/base/common/actions'; -import { IEditorQuickOpenEntry, IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; import { EditorInput, IEditorInputFactory, SideBySideEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, TextCompareEditorActiveContext, EditorPinnedContext, EditorGroupEditorsCountContext } from 'vs/workbench/common/editor'; import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; @@ -21,30 +19,29 @@ import { SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/textf import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/binaryDiffEditor'; import { ChangeEncodingAction, ChangeEOLAction, ChangeModeAction, EditorStatus } from 'vs/workbench/browser/parts/editor/editorStatus'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; -import { Scope, IActionBarRegistry, Extensions as ActionBarExtensions, ActionBarContributor } from 'vs/workbench/browser/actions'; import { SyncActionDescriptor, MenuRegistry, MenuId, IMenuItem } from 'vs/platform/actions/common/actions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { - CloseEditorsInOtherGroupsAction, CloseAllEditorsAction, MoveGroupLeftAction, MoveGroupRightAction, SplitEditorAction, JoinTwoGroupsAction, OpenToSideFromQuickOpenAction, RevertAndCloseEditorAction, + CloseEditorsInOtherGroupsAction, CloseAllEditorsAction, MoveGroupLeftAction, MoveGroupRightAction, SplitEditorAction, JoinTwoGroupsAction, RevertAndCloseEditorAction, NavigateBetweenGroupsAction, FocusActiveGroupAction, FocusFirstGroupAction, ResetGroupSizesAction, MaximizeGroupAction, MinimizeOtherGroupsAction, FocusPreviousGroup, FocusNextGroup, - toEditorQuickOpenEntry, CloseLeftEditorsInGroupAction, OpenNextEditor, OpenPreviousEditor, NavigateBackwardsAction, NavigateForwardAction, NavigateLastAction, ReopenClosedEditorAction, - QuickOpenPreviousRecentlyUsedEditorInGroupAction, QuickOpenPreviousEditorFromHistoryAction, ShowAllEditorsByAppearanceAction, ClearEditorHistoryAction, MoveEditorRightInGroupAction, OpenNextEditorInGroup, + CloseLeftEditorsInGroupAction, OpenNextEditor, OpenPreviousEditor, NavigateBackwardsAction, NavigateForwardAction, NavigateLastAction, ReopenClosedEditorAction, + QuickAccessPreviousRecentlyUsedEditorInGroupAction, QuickAccessPreviousEditorFromHistoryAction, ShowAllEditorsByAppearanceAction, ClearEditorHistoryAction, MoveEditorRightInGroupAction, OpenNextEditorInGroup, OpenPreviousEditorInGroup, OpenNextRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction, MoveEditorToPreviousGroupAction, MoveEditorToNextGroupAction, MoveEditorToFirstGroupAction, MoveEditorLeftInGroupAction, ClearRecentFilesAction, OpenLastEditorInGroup, ShowEditorsInActiveGroupByMostRecentlyUsedAction, MoveEditorToLastGroupAction, OpenFirstEditorInGroup, MoveGroupUpAction, MoveGroupDownAction, FocusLastGroupAction, SplitEditorLeftAction, SplitEditorRightAction, SplitEditorUpAction, SplitEditorDownAction, MoveEditorToLeftGroupAction, MoveEditorToRightGroupAction, MoveEditorToAboveGroupAction, MoveEditorToBelowGroupAction, CloseAllEditorGroupsAction, JoinAllGroupsAction, FocusLeftGroup, FocusAboveGroup, FocusRightGroup, FocusBelowGroup, EditorLayoutSingleAction, EditorLayoutTwoColumnsAction, EditorLayoutThreeColumnsAction, EditorLayoutTwoByTwoGridAction, EditorLayoutTwoRowsAction, EditorLayoutThreeRowsAction, EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoRowsRightAction, NewEditorGroupLeftAction, NewEditorGroupRightAction, - NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction, CloseEditorInAllGroupsAction, NavigateToLastEditLocationAction, ToggleGroupSizesAction, ShowAllEditorsByMostRecentlyUsedAction, QuickOpenPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction, QuickOpenNextRecentlyUsedEditorAction as QuickOpenLeastRecentlyUsedEditorAction, QuickOpenLeastRecentlyUsedEditorInGroupAction + NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction, CloseEditorInAllGroupsAction, NavigateToLastEditLocationAction, ToggleGroupSizesAction, ShowAllEditorsByMostRecentlyUsedAction, + QuickAccessPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction, QuickAccessLeastRecentlyUsedEditorAction, QuickAccessLeastRecentlyUsedEditorInGroupAction } from 'vs/workbench/browser/parts/editor/editorActions'; import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommands'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { getQuickNavigateHandler, inQuickOpenContext } from 'vs/workbench/browser/parts/quickopen/quickopen'; +import { inQuickPickContext, getQuickNavigateHandler } from 'vs/workbench/browser/quickaccess'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { isMacintosh } from 'vs/base/common/platform'; -import { AllEditorsByAppearancePicker, ActiveGroupEditorsByMostRecentlyUsedPicker, AllEditorsByMostRecentlyUsedPicker } from 'vs/workbench/browser/parts/editor/editorPicker'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { OpenWorkspaceButtonContribution } from 'vs/workbench/browser/parts/editor/editorWidgets'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -56,6 +53,8 @@ import { IFilesConfigurationService } from 'vs/workbench/services/filesConfigura import { EditorAutoSave } from 'vs/workbench/browser/parts/editor/editorAutoSave'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; +import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; +import { ActiveGroupEditorsByMostRecentlyUsedQuickAccess, AllEditorsByAppearanceQuickAccess, AllEditorsByMostRecentlyUsedQuickAccess } from 'vs/workbench/browser/parts/editor/editorQuickAccess'; // Register String Editor Registry.as(EditorExtensions.Editors).registerEditor( @@ -163,7 +162,7 @@ class UntitledTextEditorInputFactory implements IEditorInputFactory { const mode = deserialized.modeId; const encoding = deserialized.encoding; - return accessor.get(IEditorService).createInput({ resource, mode, encoding, forceUntitled: true }) as UntitledTextEditorInput; + return accessor.get(IEditorService).createEditorInput({ resource, mode, encoding, forceUntitled: true }) as UntitledTextEditorInput; }); } } @@ -266,98 +265,34 @@ if (Object.keys(SUPPORTED_ENCODINGS).length > 1) { registry.registerWorkbenchAction(SyncActionDescriptor.create(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL), 'Change File Encoding'); } -export class QuickOpenActionContributor extends ActionBarContributor { - private openToSideActionInstance: OpenToSideFromQuickOpenAction | undefined; - - constructor(@IInstantiationService private readonly instantiationService: IInstantiationService) { - super(); - } - - hasActions(context: unknown): boolean { - const entry = this.getEntry(context); - - return !!entry; - } - - getActions(context: unknown): ReadonlyArray { - const actions: Action[] = []; - - const entry = this.getEntry(context); - if (entry) { - if (!this.openToSideActionInstance) { - this.openToSideActionInstance = this.instantiationService.createInstance(OpenToSideFromQuickOpenAction); - } else { - this.openToSideActionInstance.updateClass(); - } - - actions.push(this.openToSideActionInstance); - } - - return actions; - } - - private getEntry(context: any): IEditorQuickOpenEntry | null { - if (!context || !context.element) { - return null; - } - - return toEditorQuickOpenEntry(context.element); - } -} - -const actionBarRegistry = Registry.as(ActionBarExtensions.Actionbar); -actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionContributor); - +// Register Editor Quick Access +const quickAccessRegistry = Registry.as(QuickAccessExtensions.Quickaccess); const editorPickerContextKey = 'inEditorsPicker'; -const editorPickerContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyExpr.has(editorPickerContextKey)); +const editorPickerContext = ContextKeyExpr.and(inQuickPickContext, ContextKeyExpr.has(editorPickerContextKey)); -Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - QuickOpenHandlerDescriptor.create( - ActiveGroupEditorsByMostRecentlyUsedPicker, - ActiveGroupEditorsByMostRecentlyUsedPicker.ID, - editorCommands.NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX, - editorPickerContextKey, - [ - { - prefix: editorCommands.NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX, - needsEditor: false, - description: nls.localize('groupOnePicker', "Show Editors in Active Group By Most Recently Used") - } - ] - ) -); +quickAccessRegistry.registerQuickAccessProvider({ + ctor: ActiveGroupEditorsByMostRecentlyUsedQuickAccess, + prefix: ActiveGroupEditorsByMostRecentlyUsedQuickAccess.PREFIX, + contextKey: editorPickerContextKey, + placeholder: nls.localize('editorQuickAccessPlaceholder', "Type the name of an editor to open it."), + helpEntries: [{ description: nls.localize('activeGroupEditorsByMostRecentlyUsedQuickAccess', "Show Editors in Active Group by Most Recently Used"), needsEditor: false }] +}); -Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - QuickOpenHandlerDescriptor.create( - AllEditorsByAppearancePicker, - AllEditorsByAppearancePicker.ID, - editorCommands.NAVIGATE_ALL_EDITORS_BY_APPEARANCE_PREFIX, - editorPickerContextKey, - [ - { - prefix: editorCommands.NAVIGATE_ALL_EDITORS_BY_APPEARANCE_PREFIX, - needsEditor: false, - description: nls.localize('allEditorsPicker', "Show All Opened Editors By Appearance") - } - ] - ) -); +quickAccessRegistry.registerQuickAccessProvider({ + ctor: AllEditorsByAppearanceQuickAccess, + prefix: AllEditorsByAppearanceQuickAccess.PREFIX, + contextKey: editorPickerContextKey, + placeholder: nls.localize('editorQuickAccessPlaceholder', "Type the name of an editor to open it."), + helpEntries: [{ description: nls.localize('allEditorsByAppearanceQuickAccess', "Show All Opened Editors By Appearance"), needsEditor: false }] +}); -Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - QuickOpenHandlerDescriptor.create( - AllEditorsByMostRecentlyUsedPicker, - AllEditorsByMostRecentlyUsedPicker.ID, - editorCommands.NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX, - editorPickerContextKey, - [ - { - prefix: editorCommands.NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX, - needsEditor: false, - description: nls.localize('allEditorsPickerByMostRecentlyUsed', "Show All Opened Editors By Most Recently Used") - } - ] - ) -); +quickAccessRegistry.registerQuickAccessProvider({ + ctor: AllEditorsByMostRecentlyUsedQuickAccess, + prefix: AllEditorsByMostRecentlyUsedQuickAccess.PREFIX, + contextKey: editorPickerContextKey, + placeholder: nls.localize('editorQuickAccessPlaceholder', "Type the name of an editor to open it."), + helpEntries: [{ description: nls.localize('allEditorsByMostRecentlyUsedQuickAccess', "Show All Opened Editors By Most Recently Used"), needsEditor: false }] +}); // Register Editor Actions const category = nls.localize('view', "View"); @@ -438,29 +373,29 @@ registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutTwoColu // Register Quick Editor Actions including built in quick navigate support for some -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenPreviousRecentlyUsedEditorAction, QuickOpenPreviousRecentlyUsedEditorAction.ID, QuickOpenPreviousRecentlyUsedEditorAction.LABEL), 'View: Quick Open Previous Recently Used Editor', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenLeastRecentlyUsedEditorAction, QuickOpenLeastRecentlyUsedEditorAction.ID, QuickOpenLeastRecentlyUsedEditorAction.LABEL), 'View: Quick Open Least Recently Used Editor', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickAccessPreviousRecentlyUsedEditorAction, QuickAccessPreviousRecentlyUsedEditorAction.ID, QuickAccessPreviousRecentlyUsedEditorAction.LABEL), 'View: Quick Open Previous Recently Used Editor', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickAccessLeastRecentlyUsedEditorAction, QuickAccessLeastRecentlyUsedEditorAction.ID, QuickAccessLeastRecentlyUsedEditorAction.LABEL), 'View: Quick Open Least Recently Used Editor', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenPreviousRecentlyUsedEditorInGroupAction, QuickOpenPreviousRecentlyUsedEditorInGroupAction.ID, QuickOpenPreviousRecentlyUsedEditorInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyCode.Tab } }), 'View: Quick Open Previous Recently Used Editor in Group', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenLeastRecentlyUsedEditorInGroupAction, QuickOpenLeastRecentlyUsedEditorInGroupAction.ID, QuickOpenLeastRecentlyUsedEditorInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Tab } }), 'View: Quick Open Least Recently Used Editor in Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickAccessPreviousRecentlyUsedEditorInGroupAction, QuickAccessPreviousRecentlyUsedEditorInGroupAction.ID, QuickAccessPreviousRecentlyUsedEditorInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyCode.Tab } }), 'View: Quick Open Previous Recently Used Editor in Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickAccessLeastRecentlyUsedEditorInGroupAction, QuickAccessLeastRecentlyUsedEditorInGroupAction.ID, QuickAccessLeastRecentlyUsedEditorInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Tab } }), 'View: Quick Open Least Recently Used Editor in Group', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenPreviousEditorFromHistoryAction, QuickOpenPreviousEditorFromHistoryAction.ID, QuickOpenPreviousEditorFromHistoryAction.LABEL), 'Quick Open Previous Editor from History'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickAccessPreviousEditorFromHistoryAction, QuickAccessPreviousEditorFromHistoryAction.ID, QuickAccessPreviousEditorFromHistoryAction.LABEL), 'Quick Open Previous Editor from History'); -const quickOpenNavigateNextInEditorPickerId = 'workbench.action.quickOpenNavigateNextInEditorPicker'; +const quickAccessNavigateNextInEditorPickerId = 'workbench.action.quickOpenNavigateNextInEditorPicker'; KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: quickOpenNavigateNextInEditorPickerId, + id: quickAccessNavigateNextInEditorPickerId, weight: KeybindingWeight.WorkbenchContrib + 50, - handler: getQuickNavigateHandler(quickOpenNavigateNextInEditorPickerId, true), + handler: getQuickNavigateHandler(quickAccessNavigateNextInEditorPickerId, true), when: editorPickerContext, primary: KeyMod.CtrlCmd | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyCode.Tab } }); -const quickOpenNavigatePreviousInEditorPickerId = 'workbench.action.quickOpenNavigatePreviousInEditorPicker'; +const quickAccessNavigatePreviousInEditorPickerId = 'workbench.action.quickOpenNavigatePreviousInEditorPicker'; KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: quickOpenNavigatePreviousInEditorPickerId, + id: quickAccessNavigatePreviousInEditorPickerId, weight: KeybindingWeight.WorkbenchContrib + 50, - handler: getQuickNavigateHandler(quickOpenNavigatePreviousInEditorPickerId, false), + handler: getQuickNavigateHandler(quickAccessNavigatePreviousInEditorPickerId, false), when: editorPickerContext, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Tab } diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 371455df9d0..4841eb5c4ca 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { GroupIdentifier, IWorkbenchEditorConfiguration, EditorOptions, TextEditorOptions, IEditorInput, IEditorIdentifier, IEditorCloseEvent, IEditor, IEditorPartOptions, IEditorPartOptionsChangeEvent, EditorInput } from 'vs/workbench/common/editor'; +import { GroupIdentifier, IWorkbenchEditorConfiguration, EditorOptions, TextEditorOptions, IEditorInput, IEditorIdentifier, IEditorCloseEvent, IEditorPane, IEditorPartOptions, IEditorPartOptionsChangeEvent, EditorInput } from 'vs/workbench/common/editor'; import { EditorGroup } from 'vs/workbench/common/editor/editorGroup'; import { IEditorGroup, GroupDirection, IAddGroupOptions, IMergeGroupOptions, GroupsOrder, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -14,7 +14,7 @@ import { IConfigurationChangeEvent } from 'vs/platform/configuration/common/conf import { ISerializableView } from 'vs/base/browser/ui/grid/grid'; import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; -import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService'; export const EDITOR_TITLE_HEIGHT = 35; @@ -30,6 +30,7 @@ export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = { highlightModifiedTabs: false, tabCloseButton: 'right', tabSizing: 'fit', + titleScrollbarSizing: 'default', focusRecentEditorAfterClose: true, showIcons: true, enablePreview: true, @@ -64,6 +65,10 @@ export function getEditorPartOptions(config: IWorkbenchEditorConfiguration): IEd } export interface IEditorOpeningEvent extends IEditorIdentifier { + + /** + * The options used when opening the editor. + */ options?: IEditorOptions; /** @@ -73,7 +78,7 @@ export interface IEditorOpeningEvent extends IEditorIdentifier { * to return a promise that resolves to `undefined` to prevent the opening * alltogether. */ - prevent(callback: () => undefined | Promise): void; + prevent(callback: () => undefined | Promise): void; } export interface IEditorGroupsAccessor { @@ -126,7 +131,7 @@ export interface IEditorGroupView extends IDisposable, ISerializableView, IEdito } export function getActiveTextEditorOptions(group: IEditorGroup, expectedActiveEditor?: IEditorInput, presetOptions?: EditorOptions): EditorOptions { - const activeGroupCodeEditor = group.activeControl ? getCodeEditor(group.activeControl.getControl()) : undefined; + const activeGroupCodeEditor = group.activeEditorPane ? getCodeEditor(group.activeEditorPane.getControl()) : undefined; if (activeGroupCodeEditor) { if (!expectedActiveEditor || expectedActiveEditor.matches(group.activeEditor)) { return TextEditorOptions.fromEditor(activeGroupCodeEditor, presetOptions); @@ -160,5 +165,5 @@ export interface EditorServiceImpl extends IEditorService { /** * Override to return a typed `EditorInput`. */ - createInput(input: IResourceEditor): EditorInput; + createEditorInput(input: IResourceEditorInputType): EditorInput; } diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index c145f1ca68d..1658e7a8e8c 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -5,25 +5,21 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; -import { mixin } from 'vs/base/common/objects'; -import { IEditorInput, EditorInput, IEditorIdentifier, IEditorCommandsContext, CloseDirection, SaveReason, EditorsOrder, SideBySideEditorInput } from 'vs/workbench/common/editor'; -import { QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { EditorQuickOpenEntry, EditorQuickOpenEntryGroup, IEditorQuickOpenEntry, QuickOpenAction } from 'vs/workbench/browser/quickopen'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; +import { IEditorInput, IEditorIdentifier, IEditorCommandsContext, CloseDirection, SaveReason, EditorsOrder, SideBySideEditorInput } from 'vs/workbench/common/editor'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { CLOSE_EDITOR_COMMAND_ID, NAVIGATE_ALL_EDITORS_BY_APPEARANCE_PREFIX, MOVE_ACTIVE_EDITOR_COMMAND_ID, NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX, ActiveEditorMoveArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, mergeAllGroups, NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, ActiveEditorMoveArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, mergeAllGroups } from 'vs/workbench/browser/parts/editor/editorCommands'; import { IEditorGroupsService, IEditorGroup, GroupsArrangement, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { values } from 'vs/base/common/map'; +import { ItemActivation, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { AllEditorsByMostRecentlyUsedQuickAccess, ActiveGroupEditorsByMostRecentlyUsedQuickAccess, AllEditorsByAppearanceQuickAccess } from 'vs/workbench/browser/parts/editor/editorQuickAccess'; export class ExecuteCommandAction extends Action { @@ -389,63 +385,6 @@ export class FocusBelowGroup extends BaseFocusGroupAction { } } -export class OpenToSideFromQuickOpenAction extends Action { - - static readonly OPEN_TO_SIDE_ID = 'workbench.action.openToSide'; - static readonly OPEN_TO_SIDE_LABEL = nls.localize('openToSide', "Open to the Side"); - - constructor( - @IEditorService private readonly editorService: IEditorService, - @IConfigurationService private readonly configurationService: IConfigurationService - ) { - super(OpenToSideFromQuickOpenAction.OPEN_TO_SIDE_ID, OpenToSideFromQuickOpenAction.OPEN_TO_SIDE_LABEL); - - this.updateClass(); - } - - updateClass(): void { - const preferredDirection = preferredSideBySideGroupDirection(this.configurationService); - - this.class = (preferredDirection === GroupDirection.RIGHT) ? 'codicon-split-horizontal' : 'codicon-split-vertical'; - } - - async run(context: unknown): Promise { - const entry = toEditorQuickOpenEntry(context); - if (entry) { - const input = entry.getInput(); - if (input) { - if (input instanceof EditorInput) { - await this.editorService.openEditor(input, entry.getOptions(), SIDE_GROUP); - return; - } - - const resourceInput = input as IResourceInput; - resourceInput.options = mixin(resourceInput.options, entry.getOptions()); - - await this.editorService.openEditor(resourceInput, SIDE_GROUP); - } - } - } -} - -export function toEditorQuickOpenEntry(element: unknown): IEditorQuickOpenEntry | null { - - // QuickOpenEntryGroup - if (element instanceof QuickOpenEntryGroup) { - const group = element; - if (group.getEntry()) { - element = group.getEntry(); - } - } - - // EditorQuickOpenEntry or EditorQuickOpenEntryGroup both implement IEditorQuickOpenEntry - if (element instanceof EditorQuickOpenEntry || element instanceof EditorQuickOpenEntryGroup) { - return element; - } - - return null; -} - export class CloseEditorAction extends Action { static readonly ID = 'workbench.action.closeActiveEditor'; @@ -521,10 +460,10 @@ export class RevertAndCloseEditorAction extends Action { } async run(): Promise { - const activeControl = this.editorService.activeControl; - if (activeControl) { - const editor = activeControl.input; - const group = activeControl.group; + const activeEditorPane = this.editorService.activeEditorPane; + if (activeEditorPane) { + const editor = activeEditorPane.input; + const group = activeEditorPane.group; // first try a normal revert where the contents of the editor are restored try { @@ -638,7 +577,7 @@ export abstract class BaseCloseAllAction extends Action { dirtyEditorsToConfirm.add(name); } - const confirm = await this.fileDialogService.showSaveConfirm(values(dirtyEditorsToConfirm)); + const confirm = await this.fileDialogService.showSaveConfirm(Array.from(dirtyEditorsToConfirm.values())); if (confirm === ConfirmResult.CANCEL) { return; } @@ -1213,55 +1152,68 @@ export class ClearRecentFilesAction extends Action { } } -export class ShowEditorsInActiveGroupByMostRecentlyUsedAction extends QuickOpenAction { +export class ShowEditorsInActiveGroupByMostRecentlyUsedAction extends Action { static readonly ID = 'workbench.action.showEditorsInActiveGroup'; static readonly LABEL = nls.localize('showEditorsInActiveGroup', "Show Editors in Active Group By Most Recently Used"); constructor( - actionId: string, - actionLabel: string, - @IQuickOpenService quickOpenService: IQuickOpenService + id: string, + label: string, + @IQuickInputService private readonly quickInputService: IQuickInputService ) { - super(actionId, actionLabel, NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX, quickOpenService); + super(id, label); + } + + async run(): Promise { + this.quickInputService.quickAccess.show(ActiveGroupEditorsByMostRecentlyUsedQuickAccess.PREFIX); } } -export class ShowAllEditorsByAppearanceAction extends QuickOpenAction { +export class ShowAllEditorsByAppearanceAction extends Action { static readonly ID = 'workbench.action.showAllEditors'; static readonly LABEL = nls.localize('showAllEditors', "Show All Editors By Appearance"); constructor( - actionId: string, - actionLabel: string, - @IQuickOpenService quickOpenService: IQuickOpenService + id: string, + label: string, + @IQuickInputService private readonly quickInputService: IQuickInputService ) { - super(actionId, actionLabel, NAVIGATE_ALL_EDITORS_BY_APPEARANCE_PREFIX, quickOpenService); + super(id, label); + } + + async run(): Promise { + this.quickInputService.quickAccess.show(AllEditorsByAppearanceQuickAccess.PREFIX); } } -export class ShowAllEditorsByMostRecentlyUsedAction extends QuickOpenAction { +export class ShowAllEditorsByMostRecentlyUsedAction extends Action { static readonly ID = 'workbench.action.showAllEditorsByMostRecentlyUsed'; static readonly LABEL = nls.localize('showAllEditorsByMostRecentlyUsed', "Show All Editors By Most Recently Used"); constructor( - actionId: string, - actionLabel: string, - @IQuickOpenService quickOpenService: IQuickOpenService + id: string, + label: string, + @IQuickInputService private readonly quickInputService: IQuickInputService ) { - super(actionId, actionLabel, NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX, quickOpenService); + super(id, label); + } + + async run(): Promise { + this.quickInputService.quickAccess.show(AllEditorsByMostRecentlyUsedQuickAccess.PREFIX); } } -export class BaseQuickOpenEditorAction extends Action { +export class BaseQuickAccessEditorAction extends Action { constructor( id: string, label: string, private prefix: string, - @IQuickOpenService private readonly quickOpenService: IQuickOpenService, + private itemActivation: ItemActivation | undefined, + @IQuickInputService private readonly quickInputService: IQuickInputService, @IKeybindingService private readonly keybindingService: IKeybindingService ) { super(id, label); @@ -1270,11 +1222,14 @@ export class BaseQuickOpenEditorAction extends Action { async run(): Promise { const keybindings = this.keybindingService.lookupKeybindings(this.id); - this.quickOpenService.show(this.prefix, { quickNavigateConfiguration: { keybindings } }); + this.quickInputService.quickAccess.show(this.prefix, { + quickNavigateConfiguration: { keybindings }, + itemActivation: this.itemActivation + }); } } -export class QuickOpenPreviousRecentlyUsedEditorAction extends BaseQuickOpenEditorAction { +export class QuickAccessPreviousRecentlyUsedEditorAction extends BaseQuickAccessEditorAction { static readonly ID = 'workbench.action.quickOpenPreviousRecentlyUsedEditor'; static readonly LABEL = nls.localize('quickOpenPreviousRecentlyUsedEditor', "Quick Open Previous Recently Used Editor"); @@ -1282,14 +1237,14 @@ export class QuickOpenPreviousRecentlyUsedEditorAction extends BaseQuickOpenEdit constructor( id: string, label: string, - @IQuickOpenService quickOpenService: IQuickOpenService, + @IQuickInputService quickInputService: IQuickInputService, @IKeybindingService keybindingService: IKeybindingService ) { - super(id, label, NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX, quickOpenService, keybindingService); + super(id, label, AllEditorsByMostRecentlyUsedQuickAccess.PREFIX, undefined, quickInputService, keybindingService); } } -export class QuickOpenNextRecentlyUsedEditorAction extends BaseQuickOpenEditorAction { +export class QuickAccessLeastRecentlyUsedEditorAction extends BaseQuickAccessEditorAction { static readonly ID = 'workbench.action.quickOpenLeastRecentlyUsedEditor'; static readonly LABEL = nls.localize('quickOpenLeastRecentlyUsedEditor', "Quick Open Least Recently Used Editor"); @@ -1297,14 +1252,14 @@ export class QuickOpenNextRecentlyUsedEditorAction extends BaseQuickOpenEditorAc constructor( id: string, label: string, - @IQuickOpenService quickOpenService: IQuickOpenService, + @IQuickInputService quickInputService: IQuickInputService, @IKeybindingService keybindingService: IKeybindingService ) { - super(id, label, NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX, quickOpenService, keybindingService); + super(id, label, AllEditorsByMostRecentlyUsedQuickAccess.PREFIX, undefined, quickInputService, keybindingService); } } -export class QuickOpenPreviousRecentlyUsedEditorInGroupAction extends BaseQuickOpenEditorAction { +export class QuickAccessPreviousRecentlyUsedEditorInGroupAction extends BaseQuickAccessEditorAction { static readonly ID = 'workbench.action.quickOpenPreviousRecentlyUsedEditorInGroup'; static readonly LABEL = nls.localize('quickOpenPreviousRecentlyUsedEditorInGroup', "Quick Open Previous Recently Used Editor in Group"); @@ -1312,14 +1267,14 @@ export class QuickOpenPreviousRecentlyUsedEditorInGroupAction extends BaseQuickO constructor( id: string, label: string, - @IQuickOpenService quickOpenService: IQuickOpenService, + @IQuickInputService quickInputService: IQuickInputService, @IKeybindingService keybindingService: IKeybindingService ) { - super(id, label, NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX, quickOpenService, keybindingService); + super(id, label, ActiveGroupEditorsByMostRecentlyUsedQuickAccess.PREFIX, undefined, quickInputService, keybindingService); } } -export class QuickOpenLeastRecentlyUsedEditorInGroupAction extends BaseQuickOpenEditorAction { +export class QuickAccessLeastRecentlyUsedEditorInGroupAction extends BaseQuickAccessEditorAction { static readonly ID = 'workbench.action.quickOpenLeastRecentlyUsedEditorInGroup'; static readonly LABEL = nls.localize('quickOpenLeastRecentlyUsedEditorInGroup', "Quick Open Least Recently Used Editor in Group"); @@ -1327,14 +1282,14 @@ export class QuickOpenLeastRecentlyUsedEditorInGroupAction extends BaseQuickOpen constructor( id: string, label: string, - @IQuickOpenService quickOpenService: IQuickOpenService, + @IQuickInputService quickInputService: IQuickInputService, @IKeybindingService keybindingService: IKeybindingService ) { - super(id, label, NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX, quickOpenService, keybindingService); + super(id, label, ActiveGroupEditorsByMostRecentlyUsedQuickAccess.PREFIX, ItemActivation.LAST, quickInputService, keybindingService); } } -export class QuickOpenPreviousEditorFromHistoryAction extends Action { +export class QuickAccessPreviousEditorFromHistoryAction extends Action { static readonly ID = 'workbench.action.openPreviousEditorFromHistory'; static readonly LABEL = nls.localize('navigateEditorHistoryByInput', "Quick Open Previous Editor from History"); @@ -1342,7 +1297,7 @@ export class QuickOpenPreviousEditorFromHistoryAction extends Action { constructor( id: string, label: string, - @IQuickOpenService private readonly quickOpenService: IQuickOpenService, + @IQuickInputService private readonly quickInputService: IQuickInputService, @IKeybindingService private readonly keybindingService: IKeybindingService ) { super(id, label); @@ -1351,7 +1306,7 @@ export class QuickOpenPreviousEditorFromHistoryAction extends Action { async run(): Promise { const keybindings = this.keybindingService.lookupKeybindings(this.id); - this.quickOpenService.show(undefined, { quickNavigateConfiguration: { keybindings } }); + this.quickInputService.quickAccess.show('', { quickNavigateConfiguration: { keybindings } }); } } diff --git a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts index ddd07b51647..cc9d969be70 100644 --- a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts +++ b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts @@ -78,9 +78,9 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution this.lastActiveEditorControlDisposable.clear(); // Listen to focus changes on control for auto save - const activeEditorControl = this.editorService.activeControl; - if (activeEditor && activeEditorControl) { - this.lastActiveEditorControlDisposable.add(activeEditorControl.onDidBlur(() => { + const activeEditorPane = this.editorService.activeEditorPane; + if (activeEditor && activeEditorPane) { + this.lastActiveEditorControlDisposable.add(activeEditorPane.onDidBlur(() => { this.maybeTriggerAutoSave(SaveReason.FOCUS_CHANGE, { groupId: activeGroup.id, editor: activeEditor }); })); } diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index a6c94bdaa38..08748f19996 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -7,13 +7,13 @@ import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { TextCompareEditorVisibleContext, EditorInput, IEditorIdentifier, IEditorCommandsContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, CloseDirection, IEditor, IEditorInput } from 'vs/workbench/common/editor'; -import { IEditorService, IVisibleEditor } from 'vs/workbench/services/editor/common/editorService'; +import { TextCompareEditorVisibleContext, EditorInput, IEditorIdentifier, IEditorCommandsContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, CloseDirection, IEditorInput, IVisibleEditorPane } from 'vs/workbench/common/editor'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor'; import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; import { URI } from 'vs/base/common/uri'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IListService } from 'vs/platform/list/browser/listService'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { distinct, coalesce } from 'vs/base/common/arrays'; @@ -22,6 +22,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { ActiveGroupEditorsByMostRecentlyUsedQuickAccess } from 'vs/workbench/browser/parts/editor/editorQuickAccess'; export const CLOSE_SAVED_EDITORS_COMMAND_ID = 'workbench.action.closeUnmodifiedEditors'; export const CLOSE_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeEditorsInGroup'; @@ -46,10 +47,6 @@ export const SPLIT_EDITOR_DOWN = 'workbench.action.splitEditorDown'; export const SPLIT_EDITOR_LEFT = 'workbench.action.splitEditorLeft'; export const SPLIT_EDITOR_RIGHT = 'workbench.action.splitEditorRight'; -export const NAVIGATE_ALL_EDITORS_BY_APPEARANCE_PREFIX = 'edt '; -export const NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX = 'edt mru '; -export const NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX = 'edt active '; - export const OPEN_EDITOR_AT_INDEX_COMMAND_ID = 'workbench.action.openEditorAtIndex'; export interface ActiveEditorMoveArguments { @@ -120,18 +117,18 @@ function moveActiveEditor(args: ActiveEditorMoveArguments = Object.create(null), args.by = args.by || 'tab'; args.value = typeof args.value === 'number' ? args.value : 1; - const activeControl = accessor.get(IEditorService).activeControl; - if (activeControl) { + const activeEditorPane = accessor.get(IEditorService).activeEditorPane; + if (activeEditorPane) { switch (args.by) { case 'tab': - return moveActiveTab(args, activeControl, accessor); + return moveActiveTab(args, activeEditorPane, accessor); case 'group': - return moveActiveEditorToGroup(args, activeControl, accessor); + return moveActiveEditorToGroup(args, activeEditorPane, accessor); } } } -function moveActiveTab(args: ActiveEditorMoveArguments, control: IVisibleEditor, accessor: ServicesAccessor): void { +function moveActiveTab(args: ActiveEditorMoveArguments, control: IVisibleEditorPane, accessor: ServicesAccessor): void { const group = control.group; let index = group.getIndexOfEditor(control.input); switch (args.to) { @@ -159,7 +156,7 @@ function moveActiveTab(args: ActiveEditorMoveArguments, control: IVisibleEditor, group.moveEditor(control.input, group, { index }); } -function moveActiveEditorToGroup(args: ActiveEditorMoveArguments, control: IVisibleEditor, accessor: ServicesAccessor): void { +function moveActiveEditorToGroup(args: ActiveEditorMoveArguments, control: IVisibleEditorPane, accessor: ServicesAccessor): void { const editorGroupService = accessor.get(IEditorGroupsService); const configurationService = accessor.get(IConfigurationService); @@ -261,7 +258,7 @@ function registerDiffEditorCommands(): void { function navigateInDiffEditor(accessor: ServicesAccessor, next: boolean): void { const editorService = accessor.get(IEditorService); - const candidates = [editorService.activeControl, ...editorService.visibleControls].filter(e => e instanceof TextDiffEditor); + const candidates = [editorService.activeEditorPane, ...editorService.visibleEditorPanes].filter(e => e instanceof TextDiffEditor); if (candidates.length > 0) { const navigator = (candidates[0]).getDiffNavigator(); @@ -317,9 +314,9 @@ function registerDiffEditorCommands(): void { function registerOpenEditorAtIndexCommands(): void { const openEditorAtIndex: ICommandHandler = (accessor: ServicesAccessor, editorIndex: number): void => { const editorService = accessor.get(IEditorService); - const activeControl = editorService.activeControl; - if (activeControl) { - const editor = activeControl.group.getEditorByIndex(editorIndex); + const activeEditorPane = editorService.activeEditorPane; + if (activeEditorPane) { + const editor = activeEditorPane.group.getEditorByIndex(editorIndex); if (editor) { editorService.openEditor(editor); } @@ -491,13 +488,11 @@ function registerCloseEditorCommands() { contexts.push({ groupId: activeGroup.id }); // active group as fallback } - return Promise.all(distinct(contexts.map(c => c.groupId)).map(groupId => { + return Promise.all(distinct(contexts.map(c => c.groupId)).map(async groupId => { const group = editorGroupService.getGroup(groupId); if (group) { return group.closeEditors({ savedOnly: true }); } - - return Promise.resolve(); })); } }); @@ -516,13 +511,11 @@ function registerCloseEditorCommands() { distinctGroupIds.push(editorGroupService.activeGroup.id); } - return Promise.all(distinctGroupIds.map(groupId => { + return Promise.all(distinctGroupIds.map(async groupId => { const group = editorGroupService.getGroup(groupId); if (group) { return group.closeAllEditors(); } - - return Promise.resolve(); })); } }); @@ -544,7 +537,7 @@ function registerCloseEditorCommands() { const groupIds = distinct(contexts.map(context => context.groupId)); - return Promise.all(groupIds.map(groupId => { + return Promise.all(groupIds.map(async groupId => { const group = editorGroupService.getGroup(groupId); if (group) { const editors = coalesce(contexts @@ -553,8 +546,6 @@ function registerCloseEditorCommands() { return group.closeEditors(editors); } - - return Promise.resolve(); })); } }); @@ -599,7 +590,7 @@ function registerCloseEditorCommands() { const groupIds = distinct(contexts.map(context => context.groupId)); - return Promise.all(groupIds.map(groupId => { + return Promise.all(groupIds.map(async groupId => { const group = editorGroupService.getGroup(groupId); if (group) { const editors = contexts @@ -613,8 +604,6 @@ function registerCloseEditorCommands() { return group.closeEditors(editorsToClose); } - - return Promise.resolve(); })); } }); @@ -624,7 +613,7 @@ function registerCloseEditorCommands() { weight: KeybindingWeight.WorkbenchContrib, when: undefined, primary: undefined, - handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { + handler: async (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { const editorGroupService = accessor.get(IEditorGroupsService); const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context)); @@ -635,8 +624,6 @@ function registerCloseEditorCommands() { return group.closeEditors({ direction: CloseDirection.RIGHT, except: editor }); } - - return Promise.resolve(false); } }); @@ -645,15 +632,13 @@ function registerCloseEditorCommands() { weight: KeybindingWeight.WorkbenchContrib, when: undefined, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.Enter), - handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { + handler: async (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { const editorGroupService = accessor.get(IEditorGroupsService); const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context)); if (group && editor) { return group.pinEditor(editor); } - - return Promise.resolve(false); } }); @@ -664,7 +649,7 @@ function registerCloseEditorCommands() { primary: undefined, handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { const editorGroupService = accessor.get(IEditorGroupsService); - const quickOpenService = accessor.get(IQuickOpenService); + const quickInputService = accessor.get(IQuickInputService); const commandsContext = getCommandsContext(resourceOrContext, context); if (commandsContext && typeof commandsContext.groupId === 'number') { @@ -674,7 +659,7 @@ function registerCloseEditorCommands() { } } - return quickOpenService.show(NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX); + return quickInputService.quickAccess.show(ActiveGroupEditorsByMostRecentlyUsedQuickAccess.PREFIX); } }); @@ -708,21 +693,19 @@ function getCommandsContext(resourceOrContext: URI | IEditorCommandsContext, con return undefined; } -function resolveCommandsContext(editorGroupService: IEditorGroupsService, context?: IEditorCommandsContext): { group: IEditorGroup, editor?: IEditorInput, control?: IEditor } { +function resolveCommandsContext(editorGroupService: IEditorGroupsService, context?: IEditorCommandsContext): { group: IEditorGroup, editor?: IEditorInput } { // Resolve from context let group = context && typeof context.groupId === 'number' ? editorGroupService.getGroup(context.groupId) : undefined; let editor = group && context && typeof context.editorIndex === 'number' ? types.withNullAsUndefined(group.getEditorByIndex(context.editorIndex)) : undefined; - let control = group ? group.activeControl : undefined; // Fallback to active group as needed if (!group) { group = editorGroupService.activeGroup; editor = group.activeEditor; - control = group.activeControl; } - return { group, editor, control }; + return { group, editor }; } export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsContext | undefined, listService: IListService, editorGroupService: IEditorGroupsService): IEditorCommandsContext[] { diff --git a/src/vs/workbench/browser/parts/editor/editorControl.ts b/src/vs/workbench/browser/parts/editor/editorControl.ts index 1cbe3fbcd53..c7acfb330d2 100644 --- a/src/vs/workbench/browser/parts/editor/editorControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorControl.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, IVisibleEditorPane } from 'vs/workbench/common/editor'; import { Dimension, show, hide, addClass } from 'vs/base/browser/dom'; import { Registry } from 'vs/platform/registry/common/platform'; import { IEditorRegistry, Extensions as EditorExtensions, IEditorDescriptor } from 'vs/workbench/browser/editor'; @@ -14,20 +14,19 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IEditorProgressService, LongRunningOperation } from 'vs/platform/progress/common/progress'; import { IEditorGroupView, DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor'; import { Emitter } from 'vs/base/common/event'; -import { IVisibleEditor } from 'vs/workbench/services/editor/common/editorService'; import { assertIsDefined } from 'vs/base/common/types'; export interface IOpenEditorResult { - readonly control: BaseEditor; + readonly editorPane: BaseEditor; readonly editorChanged: boolean; } export class EditorControl extends Disposable { - get minimumWidth() { return this._activeControl ? this._activeControl.minimumWidth : DEFAULT_EDITOR_MIN_DIMENSIONS.width; } - get minimumHeight() { return this._activeControl ? this._activeControl.minimumHeight : DEFAULT_EDITOR_MIN_DIMENSIONS.height; } - get maximumWidth() { return this._activeControl ? this._activeControl.maximumWidth : DEFAULT_EDITOR_MAX_DIMENSIONS.width; } - get maximumHeight() { return this._activeControl ? this._activeControl.maximumHeight : DEFAULT_EDITOR_MAX_DIMENSIONS.height; } + get minimumWidth() { return this._activeEditorPane?.minimumWidth ?? DEFAULT_EDITOR_MIN_DIMENSIONS.width; } + get minimumHeight() { return this._activeEditorPane?.minimumHeight ?? DEFAULT_EDITOR_MIN_DIMENSIONS.height; } + get maximumWidth() { return this._activeEditorPane?.maximumWidth ?? DEFAULT_EDITOR_MAX_DIMENSIONS.width; } + get maximumHeight() { return this._activeEditorPane?.maximumHeight ?? DEFAULT_EDITOR_MAX_DIMENSIONS.height; } private readonly _onDidFocus = this._register(new Emitter()); readonly onDidFocus = this._onDidFocus.event; @@ -35,10 +34,10 @@ export class EditorControl extends Disposable { private _onDidSizeConstraintsChange = this._register(new Emitter<{ width: number; height: number; } | undefined>()); readonly onDidSizeConstraintsChange = this._onDidSizeConstraintsChange.event; - private _activeControl: BaseEditor | null = null; - private controls: BaseEditor[] = []; + private _activeEditorPane: BaseEditor | null = null; + private readonly editorPanes: BaseEditor[] = []; - private readonly activeControlDisposables = this._register(new DisposableStore()); + private readonly activeEditorPaneDisposables = this._register(new DisposableStore()); private dimension: Dimension | undefined; private editorOperation: LongRunningOperation; @@ -54,119 +53,119 @@ export class EditorControl extends Disposable { this.editorOperation = this._register(new LongRunningOperation(editorProgressService)); } - get activeControl(): IVisibleEditor | null { - return this._activeControl as IVisibleEditor | null; + get activeEditorPane(): IVisibleEditorPane | null { + return this._activeEditorPane as IVisibleEditorPane | null; } async openEditor(editor: EditorInput, options?: EditorOptions): Promise { - // Editor control + // Editor pane const descriptor = Registry.as(EditorExtensions.Editors).getEditor(editor); if (!descriptor) { - throw new Error('No editor descriptor found'); + throw new Error(`No editor descriptor found for input id ${editor.getTypeId()}`); } - const control = this.doShowEditorControl(descriptor); + const editorPane = this.doShowEditorPane(descriptor); // Set input - const editorChanged = await this.doSetInput(control, editor, options); - return { control, editorChanged }; + const editorChanged = await this.doSetInput(editorPane, editor, options); + return { editorPane, editorChanged }; } - private doShowEditorControl(descriptor: IEditorDescriptor): BaseEditor { + private doShowEditorPane(descriptor: IEditorDescriptor): BaseEditor { - // Return early if the currently active editor control can handle the input - if (this._activeControl && descriptor.describes(this._activeControl)) { - return this._activeControl; + // Return early if the currently active editor pane can handle the input + if (this._activeEditorPane && descriptor.describes(this._activeEditorPane)) { + return this._activeEditorPane; } // Hide active one first - this.doHideActiveEditorControl(); + this.doHideActiveEditorPane(); - // Create editor - const control = this.doCreateEditorControl(descriptor); + // Create editor pane + const editorPane = this.doCreateEditorPane(descriptor); // Set editor as active - this.doSetActiveControl(control); + this.doSetActiveEditorPane(editorPane); // Show editor - const container = assertIsDefined(control.getContainer()); + const container = assertIsDefined(editorPane.getContainer()); this.parent.appendChild(container); show(container); // Indicate to editor that it is now visible - control.setVisible(true, this.groupView); + editorPane.setVisible(true, this.groupView); // Layout if (this.dimension) { - control.layout(this.dimension); + editorPane.layout(this.dimension); } - return control; + return editorPane; } - private doCreateEditorControl(descriptor: IEditorDescriptor): BaseEditor { + private doCreateEditorPane(descriptor: IEditorDescriptor): BaseEditor { // Instantiate editor - const control = this.doInstantiateEditorControl(descriptor); + const editorPane = this.doInstantiateEditorPane(descriptor); // Create editor container as needed - if (!control.getContainer()) { - const controlInstanceContainer = document.createElement('div'); - addClass(controlInstanceContainer, 'editor-instance'); - controlInstanceContainer.setAttribute('data-editor-id', descriptor.getId()); + if (!editorPane.getContainer()) { + const editorPaneContainer = document.createElement('div'); + addClass(editorPaneContainer, 'editor-instance'); + editorPaneContainer.setAttribute('data-editor-id', descriptor.getId()); - control.create(controlInstanceContainer); + editorPane.create(editorPaneContainer); } - return control; + return editorPane; } - private doInstantiateEditorControl(descriptor: IEditorDescriptor): BaseEditor { + private doInstantiateEditorPane(descriptor: IEditorDescriptor): BaseEditor { // Return early if already instantiated - const existingControl = this.controls.filter(control => descriptor.describes(control))[0]; - if (existingControl) { - return existingControl; + const existingEditorPane = this.editorPanes.filter(editorPane => descriptor.describes(editorPane))[0]; + if (existingEditorPane) { + return existingEditorPane; } // Otherwise instantiate new - const control = this._register(descriptor.instantiate(this.instantiationService)); - this.controls.push(control); + const editorPane = this._register(descriptor.instantiate(this.instantiationService)); + this.editorPanes.push(editorPane); - return control; + return editorPane; } - private doSetActiveControl(control: BaseEditor | null) { - this._activeControl = control; + private doSetActiveEditorPane(editorPane: BaseEditor | null) { + this._activeEditorPane = editorPane; - // Clear out previous active control listeners - this.activeControlDisposables.clear(); + // Clear out previous active editor pane listeners + this.activeEditorPaneDisposables.clear(); - // Listen to control changes - if (control) { - this.activeControlDisposables.add(control.onDidSizeConstraintsChange(e => this._onDidSizeConstraintsChange.fire(e))); - this.activeControlDisposables.add(control.onDidFocus(() => this._onDidFocus.fire())); + // Listen to editor pane changes + if (editorPane) { + this.activeEditorPaneDisposables.add(editorPane.onDidSizeConstraintsChange(e => this._onDidSizeConstraintsChange.fire(e))); + this.activeEditorPaneDisposables.add(editorPane.onDidFocus(() => this._onDidFocus.fire())); } // Indicate that size constraints could have changed due to new editor this._onDidSizeConstraintsChange.fire(undefined); } - private async doSetInput(control: BaseEditor, editor: EditorInput, options: EditorOptions | undefined): Promise { + private async doSetInput(editorPane: BaseEditor, editor: EditorInput, options: EditorOptions | undefined): Promise { // If the input did not change, return early and only apply the options // unless the options instruct us to force open it even if it is the same const forceReload = options?.forceReload; - const inputMatches = control.input && control.input.matches(editor); + const inputMatches = editorPane.input && editorPane.input.matches(editor); if (inputMatches && !forceReload) { // Forward options - control.setOptions(options); + editorPane.setOptions(options); // Still focus as needed const focus = !options || !options.preserveFocus; if (focus) { - control.focus(); + editorPane.focus(); } return false; @@ -176,16 +175,16 @@ export class EditorControl extends Disposable { // be more relaxed about progress showing by increasing the delay a little bit to reduce flicker. const operation = this.editorOperation.start(this.layoutService.isRestored() ? 800 : 3200); - // Call into editor control + // Call into editor pane const editorWillChange = !inputMatches; try { - await control.setInput(editor, options, operation.token); + await editorPane.setInput(editor, options, operation.token); // Focus (unless prevented or another operation is running) if (operation.isCurrent()) { const focus = !options || !options.preserveFocus; if (focus) { - control.focus(); + editorPane.focus(); } } @@ -195,47 +194,47 @@ export class EditorControl extends Disposable { } } - private doHideActiveEditorControl(): void { - if (!this._activeControl) { + private doHideActiveEditorPane(): void { + if (!this._activeEditorPane) { return; } // Stop any running operation this.editorOperation.stop(); - // Remove control from parent and hide - const controlInstanceContainer = this._activeControl.getContainer(); - if (controlInstanceContainer) { - this.parent.removeChild(controlInstanceContainer); - hide(controlInstanceContainer); - this._activeControl.onHide(); + // Remove editor pane from parent and hide + const editorPaneContainer = this._activeEditorPane.getContainer(); + if (editorPaneContainer) { + this.parent.removeChild(editorPaneContainer); + hide(editorPaneContainer); + this._activeEditorPane.onHide(); } - // Indicate to editor control - this._activeControl.clearInput(); - this._activeControl.setVisible(false, this.groupView); + // Indicate to editor pane + this._activeEditorPane.clearInput(); + this._activeEditorPane.setVisible(false, this.groupView); - // Clear active control - this.doSetActiveControl(null); + // Clear active editor pane + this.doSetActiveEditorPane(null); } closeEditor(editor: EditorInput): void { - if (this._activeControl && editor.matches(this._activeControl.input)) { - this.doHideActiveEditorControl(); + if (this._activeEditorPane && editor.matches(this._activeEditorPane.input)) { + this.doHideActiveEditorPane(); } } setVisible(visible: boolean): void { - if (this._activeControl) { - this._activeControl.setVisible(visible, this.groupView); + if (this._activeEditorPane) { + this._activeEditorPane.setVisible(visible, this.groupView); } } layout(dimension: Dimension): void { this.dimension = dimension; - if (this._activeControl && this.dimension) { - this._activeControl.layout(this.dimension); + if (this._activeEditorPane && this.dimension) { + this._activeEditorPane.layout(this.dimension); } } } diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index bb51c1e0206..61c85cd0871 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -7,8 +7,8 @@ import 'vs/css!./media/editordroptarget'; import { LocalSelectionTransfer, DraggedEditorIdentifier, ResourcesDropHandler, DraggedEditorGroupIdentifier, DragAndDropObserver, containsDragType } from 'vs/workbench/browser/dnd'; import { addDisposableListener, EventType, EventHelper, isAncestor, toggleClass, addClass, removeClass } from 'vs/base/browser/dom'; import { IEditorGroupsAccessor, EDITOR_TITLE_HEIGHT, IEditorGroupView, getActiveTextEditorOptions } from 'vs/workbench/browser/parts/editor/editor'; -import { EDITOR_DRAG_AND_DROP_BACKGROUND, Themable } from 'vs/workbench/common/theme'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { EDITOR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme'; +import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { IEditorIdentifier, EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { isMacintosh, isWeb } from 'vs/base/common/platform'; @@ -308,7 +308,7 @@ class DropOverlay extends Themable { } // Open as untitled file with the provided contents - const untitledEditor = this.editorService.createInput({ + const untitledEditor = this.editorService.createEditorInput({ resource: proposedFilePath, forceUntitled: true, contents: VSBuffer.wrap(new Uint8Array(event.target.result)).toString() diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 39ab5198646..519e091fee3 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/editorgroupview'; import { EditorGroup, IEditorOpenOptions, EditorCloseEvent, ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup'; -import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor, EditorGroupEditorsCountContext, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditorPane, EditorGroupEditorsCountContext, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane } from 'vs/workbench/common/editor'; import { Event, Emitter, Relay } from 'vs/base/common/event'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { addClass, addClasses, Dimension, trackFocus, toggleClass, removeClass, addDisposableListener, EventType, EventHelper, findParentWithClass, clearNode, isAncestor } from 'vs/base/browser/dom'; @@ -14,9 +14,9 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { attachProgressBarStyler } from 'vs/platform/theme/common/styler'; -import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService'; import { editorBackground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; -import { Themable, EDITOR_GROUP_HEADER_TABS_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_EMPTY_BACKGROUND, EDITOR_GROUP_FOCUSED_EMPTY_BORDER } from 'vs/workbench/common/theme'; +import { EDITOR_GROUP_HEADER_TABS_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_EMPTY_BACKGROUND, EDITOR_GROUP_FOCUSED_EMPTY_BORDER } from 'vs/workbench/common/theme'; import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, GroupsOrder, ICloseEditorOptions } from 'vs/workbench/services/editor/common/editorGroupsService'; import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl'; import { EditorControl } from 'vs/workbench/browser/parts/editor/editorControl'; @@ -25,7 +25,7 @@ import { EditorProgressIndicator } from 'vs/workbench/services/progress/browser/ import { localize } from 'vs/nls'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { dispose, MutableDisposable } from 'vs/base/common/lifecycle'; -import { Severity, INotificationService, INotificationActions } from 'vs/platform/notification/common/notification'; +import { Severity, INotificationService } from 'vs/platform/notification/common/notification'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { RunOnceWorker } from 'vs/base/common/async'; @@ -42,7 +42,7 @@ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { isErrorWithActions, IErrorWithActions } from 'vs/base/common/errorsWithActions'; -import { IVisibleEditor, IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; import { hash } from 'vs/base/common/hash'; import { guessMimeTypes } from 'vs/base/common/mime'; @@ -255,7 +255,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { if (this.isEmpty) { EventHelper.stop(e); - this.openEditor(this.editorService.createInput({ forceUntitled: true }), EditorOptions.create({ pinned: true })); + this.openEditor(this.editorService.createEditorInput({ forceUntitled: true }), EditorOptions.create({ pinned: true })); } })); @@ -292,11 +292,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { localize('closeGroupAction', "Close"), 'codicon-close', true, - () => { - this.accessor.removeGroup(this); - - return Promise.resolve(true); - })); + async () => this.accessor.removeGroup(this))); const keybinding = this.keybindingService.lookupKeybinding(removeGroupAction.id); containerToolbar.push(removeGroupAction, { icon: true, label: false, keybinding: keybinding ? keybinding.getLabel() : undefined }); @@ -733,8 +729,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this._group.count; } - get activeControl(): IVisibleEditor | undefined { - return this.editorControl ? withNullAsUndefined(this.editorControl.activeControl) : undefined; + get activeEditorPane(): IVisibleEditorPane | undefined { + return this.editorControl ? withNullAsUndefined(this.editorControl.activeEditorPane) : undefined; } get activeEditor(): EditorInput | null { @@ -771,9 +767,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { focus(): void { - // Pass focus to widgets - if (this.activeControl) { - this.activeControl.focus(); + // Pass focus to editor panes + if (this.activeEditorPane) { + this.activeEditorPane.focus(); } else { this.element.focus(); } @@ -803,11 +799,11 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#region openEditor() - async openEditor(editor: EditorInput, options?: EditorOptions): Promise { + async openEditor(editor: EditorInput, options?: EditorOptions): Promise { // Guard against invalid inputs if (!editor) { - return Promise.resolve(null); + return null; } // Editor opening event allows for prevention @@ -822,13 +818,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return withUndefinedAsNull(await this.doOpenEditor(editor, options)); } - private doOpenEditor(editor: EditorInput, options?: EditorOptions): Promise { + private async doOpenEditor(editor: EditorInput, options?: EditorOptions): Promise { // Guard against invalid inputs. Disposed inputs // should never open because they emit no events // e.g. to indicate dirty changes. if (editor.isDisposed()) { - return Promise.resolve(undefined); + return; } // Determine options @@ -895,10 +891,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return showEditorResult; } - private async doShowEditor(editor: EditorInput, active: boolean, options?: EditorOptions): Promise { + private async doShowEditor(editor: EditorInput, active: boolean, options?: EditorOptions): Promise { // Show in editor control if the active editor changed - let openEditorPromise: Promise; + let openEditorPromise: Promise | undefined; if (active) { openEditorPromise = (async () => { try { @@ -909,7 +905,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_ACTIVE, editor }); } - return result.control; + return result.editorPane; } catch (error) { // Handle errors but do not bubble them up @@ -919,7 +915,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } })(); } else { - openEditorPromise = Promise.resolve(undefined); // inactive: return undefined as result to signal this + openEditorPromise = undefined; // inactive: return undefined as result to signal this } // Show in title control after editor control because some actions depend on it @@ -980,7 +976,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Otherwise, show a background notification. else { - const actions: INotificationActions = { primary: [] }; + const actions = { primary: [] as readonly IAction[] }; if (Array.isArray(errorActions)) { actions.primary = errorActions; } @@ -1015,7 +1011,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#region openEditors() - async openEditors(editors: { editor: EditorInput, options?: EditorOptions }[]): Promise { + async openEditors(editors: { editor: EditorInput, options?: EditorOptions }[]): Promise { if (!editors.length) { return null; } @@ -1041,7 +1037,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Opening many editors at once can put any editor to be // the active one depending on options. As such, we simply // return the active control after this operation. - return this.editorControl.activeControl; + return this.editorControl.activeEditorPane; } //#endregion @@ -1075,7 +1071,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Update model and make sure to continue to use the editor we get from // the model. It is possible that the editor was already opened and we // want to ensure that we use the existing instance in that case. - const editor = this.group.getEditorByIndex(currentIndex)!; + const editor = this._group.getEditorByIndex(currentIndex); + if (!editor) { + return; + } // Update model this._group.moveEditor(editor, moveToIndex); @@ -1278,7 +1277,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return false; // editor must be dirty and not saving } - if (editor instanceof SideBySideEditorInput && this.isOpened(editor.master)) { + if (editor instanceof SideBySideEditorInput && this._group.contains(editor.master)) { return false; // master-side of editor is still opened somewhere else } @@ -1329,25 +1328,24 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Otherwise, handle accordingly switch (res) { case ConfirmResult.SAVE: - const result = await editor.save(this.id, { reason: SaveReason.EXPLICIT }); + await editor.save(this.id, { reason: SaveReason.EXPLICIT }); - return !result; + return editor.isDirty(); // veto if still dirty case ConfirmResult.DONT_SAVE: - try { // first try a normal revert where the contents of the editor are restored - const result = await editor.revert(this.id); + await editor.revert(this.id); - return !result; + return editor.isDirty(); // veto if still dirty } catch (error) { // if that fails, since we are about to close the editor, we accept that // the editor cannot be reverted and instead do a soft revert that just // enables us to close the editor. With this, a user can always close a // dirty editor even when reverting fails. - const result = await editor.revert(this.id, { soft: true }); + await editor.revert(this.id, { soft: true }); - return !result; + return editor.isDirty(); // veto if still dirty } case ConfirmResult.CANCEL: return true; // veto @@ -1623,7 +1621,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } class EditorOpeningEvent implements IEditorOpeningEvent { - private override: (() => Promise) | undefined = undefined; + private override: (() => Promise) | undefined = undefined; constructor( private _group: GroupIdentifier, @@ -1644,11 +1642,11 @@ class EditorOpeningEvent implements IEditorOpeningEvent { return this._options; } - prevent(callback: () => Promise): void { + prevent(callback: () => Promise): void { this.override = callback; } - isPrevented(): (() => Promise) | undefined { + isPrevented(): (() => Promise) | undefined { return this.override; } } diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index a3b7654dd45..e3b3dc65fe1 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -13,7 +13,6 @@ import { GroupDirection, IAddGroupOptions, GroupsArrangement, GroupOrientation, import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IView, orthogonal, LayoutPriority, IViewSize, Direction, SerializableGrid, Sizing, ISerializedGrid, Orientation, GridBranchNode, isGridBranchNode, GridNode, createSerializedGrid, Grid } from 'vs/base/browser/ui/grid/grid'; import { GroupIdentifier, IWorkbenchEditorConfiguration, IEditorPartOptions, IEditorPartOptionsChangeEvent } from 'vs/workbench/common/editor'; -import { values } from 'vs/base/common/map'; import { EDITOR_GROUP_BORDER, EDITOR_PANE_BACKGROUND } from 'vs/workbench/common/theme'; import { distinct, coalesce } from 'vs/base/common/arrays'; import { IEditorGroupsAccessor, IEditorGroupView, getEditorPartOptions, impactsEditorPartOptions, IEditorPartCreationOptions } from 'vs/workbench/browser/parts/editor/editor'; @@ -32,6 +31,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { MementoObject } from 'vs/workbench/common/memento'; import { assertIsDefined } from 'vs/base/common/types'; import { IBoundarySashes } from 'vs/base/browser/ui/grid/gridview'; +import { CompositeDragAndDropObserver } from 'vs/workbench/browser/dnd'; interface IEditorPartUIState { serializedGrid: ISerializedGrid; @@ -210,7 +210,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro } get groups(): IEditorGroupView[] { - return values(this.groupViews); + return Array.from(this.groupViews.values()); } get count(): number { @@ -826,6 +826,20 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro // Drop support this._register(this.createEditorDropTarget(this.container, {})); + // No drop in the editor + const overlay = document.createElement('div'); + addClass(overlay, 'drop-block-overlay'); + parent.appendChild(overlay); + + CompositeDragAndDropObserver.INSTANCE.registerTarget(this.element, { + onDragStart: e => { + toggleClass(overlay, 'visible', true); + }, + onDragEnd: e => { + toggleClass(overlay, 'visible', false); + } + }); + return this.container; } diff --git a/src/vs/workbench/browser/parts/editor/editorPicker.ts b/src/vs/workbench/browser/parts/editor/editorPicker.ts deleted file mode 100644 index 427a7bce430..00000000000 --- a/src/vs/workbench/browser/parts/editor/editorPicker.ts +++ /dev/null @@ -1,265 +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 'vs/css!./media/editorpicker'; -import * as nls from 'vs/nls'; -import { IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; -import { IAutoFocus, Mode, IEntryRunContext, IQuickNavigateConfiguration, IModel } from 'vs/base/parts/quickopen/common/quickOpen'; -import { QuickOpenModel, QuickOpenEntry, QuickOpenEntryGroup, QuickOpenItemAccessor } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { QuickOpenHandler } from 'vs/workbench/browser/quickopen'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService, IEditorGroup, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { toResource, SideBySideEditor, IEditorInput, EditorsOrder } from 'vs/workbench/common/editor'; -import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; -import { CancellationToken } from 'vs/base/common/cancellation'; - -export class EditorPickerEntry extends QuickOpenEntryGroup { - - constructor( - private editor: IEditorInput, - public readonly group: IEditorGroup, - @IModeService private readonly modeService: IModeService, - @IModelService private readonly modelService: IModelService - ) { - super(); - } - - getLabelOptions(): IIconLabelValueOptions { - return { - extraClasses: getIconClasses(this.modelService, this.modeService, this.getResource()), - italic: !this.group.isPinned(this.editor) - }; - } - - getLabel(): string { - return this.editor.getName(); - } - - getIcon(): string { - return this.editor.isDirty() && !this.editor.isSaving() ? 'codicon codicon-circle-filled' : ''; - } - - getResource() { - return toResource(this.editor, { supportSideBySide: SideBySideEditor.MASTER }); - } - - getAriaLabel(): string { - return nls.localize('entryAriaLabel', "{0}, editor group picker", this.getLabel()); - } - - getDescription() { - return this.editor.getDescription(); - } - - run(mode: Mode, context: IEntryRunContext): boolean { - if (mode === Mode.OPEN) { - return this.runOpen(context); - } - - return super.run(mode, context); - } - - private runOpen(context: IEntryRunContext): boolean { - this.group.openEditor(this.editor); - - return true; - } -} - -export abstract class BaseEditorPicker extends QuickOpenHandler { - private scorerCache: ScorerCache; - - constructor( - @IInstantiationService protected instantiationService: IInstantiationService, - @IEditorService protected editorService: IEditorService, - @IEditorGroupsService protected editorGroupService: IEditorGroupsService - ) { - super(); - - this.scorerCache = Object.create(null); - } - - getResults(searchValue: string, token: CancellationToken): Promise { - const editorEntries = this.getEditorEntries(); - if (!editorEntries.length) { - return Promise.resolve(null); - } - - // Prepare search for scoring - const query = prepareQuery(searchValue); - - const entries = editorEntries.filter(e => { - if (!query.value) { - return true; - } - - const itemScore = scoreItem(e, query, true, QuickOpenItemAccessor, this.scorerCache); - if (!itemScore.score) { - return false; - } - - e.setHighlights(itemScore.labelMatch || [], itemScore.descriptionMatch); - - return true; - }); - - // Sorting - if (query.value) { - const groups = this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE); - entries.sort((e1, e2) => { - if (e1.group !== e2.group) { - return groups.indexOf(e1.group) - groups.indexOf(e2.group); // older groups first - } - - return compareItemsByScore(e1, e2, query, true, QuickOpenItemAccessor, this.scorerCache); - }); - } - - // Grouping (for more than one group) - if (this.editorGroupService.count > 1) { - let lastGroup: IEditorGroup; - entries.forEach(e => { - if (!lastGroup || lastGroup !== e.group) { - e.setGroupLabel(e.group.label); - e.setShowBorder(!!lastGroup); - lastGroup = e.group; - } - }); - } - - return Promise.resolve(new QuickOpenModel(entries)); - } - - onClose(canceled: boolean): void { - this.scorerCache = Object.create(null); - } - - protected abstract count(): number; - - protected abstract getEditorEntries(): EditorPickerEntry[]; - - getAutoFocus(searchValue: string, context: { model: IModel, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus { - if (searchValue || !context.quickNavigateConfiguration) { - return { - autoFocusFirstEntry: true - }; - } - - const isShiftNavigate = (context.quickNavigateConfiguration && context.quickNavigateConfiguration.keybindings.some(k => { - const [firstPart, chordPart] = k.getParts(); - if (chordPart) { - return false; - } - - return firstPart.shiftKey; - })); - - if (isShiftNavigate) { - return { - autoFocusLastEntry: true - }; - } - - const editors = this.count(); - return { - autoFocusFirstEntry: editors === 1, - autoFocusSecondEntry: editors > 1 - }; - } -} - -export class ActiveGroupEditorsByMostRecentlyUsedPicker extends BaseEditorPicker { - - static readonly ID = 'workbench.picker.activeGroupEditorsByMostRecentlyUsed'; - - protected count(): number { - return this.group.count; - } - - protected getEditorEntries(): EditorPickerEntry[] { - return this.group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).map(editor => this.instantiationService.createInstance(EditorPickerEntry, editor, this.group)); - } - - private get group(): IEditorGroup { - return this.editorGroupService.activeGroup; - } - - getEmptyLabel(searchString: string): string { - if (searchString) { - return nls.localize('noResultsFoundInGroup', "No matching opened editor found in active editor group"); - } - - return nls.localize('noOpenedEditors', "List of opened editors is currently empty in active editor group"); - } -} - -export abstract class BaseAllEditorsPicker extends BaseEditorPicker { - - constructor( - @IInstantiationService instantiationService: IInstantiationService, - @IEditorService editorService: IEditorService, - @IEditorGroupsService editorGroupService: IEditorGroupsService - ) { - super(instantiationService, editorService, editorGroupService); - } - - protected count(): number { - return this.editorService.count; - } - - getEmptyLabel(searchString: string): string { - if (searchString) { - return nls.localize('noResultsFound', "No matching opened editor found"); - } - - return nls.localize('noOpenedEditorsAllGroups', "List of opened editors is currently empty"); - } - - getAutoFocus(searchValue: string, context: { model: IModel, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus { - if (searchValue) { - return { - autoFocusFirstEntry: true - }; - } - - return super.getAutoFocus(searchValue, context); - } -} - -export class AllEditorsByAppearancePicker extends BaseAllEditorsPicker { - - static readonly ID = 'workbench.picker.editorsByAppearance'; - - protected getEditorEntries(): EditorPickerEntry[] { - const entries: EditorPickerEntry[] = []; - - for (const group of this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE)) { - for (const editor of group.getEditors(EditorsOrder.SEQUENTIAL)) { - entries.push(this.instantiationService.createInstance(EditorPickerEntry, editor, group)); - } - } - - return entries; - } -} - -export class AllEditorsByMostRecentlyUsedPicker extends BaseAllEditorsPicker { - - static readonly ID = 'workbench.picker.editorsByMostRecentlyUsed'; - - protected getEditorEntries(): EditorPickerEntry[] { - const entries: EditorPickerEntry[] = []; - - for (const { editor, groupId } of this.editorService.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)) { - entries.push(this.instantiationService.createInstance(EditorPickerEntry, editor, this.editorGroupService.getGroup(groupId)!)); - } - - return entries; - } -} diff --git a/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts b/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts new file mode 100644 index 00000000000..a1071cd45ee --- /dev/null +++ b/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts @@ -0,0 +1,269 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./media/editorquickaccess'; +import { localize } from 'vs/nls'; +import { IQuickPickSeparator, quickPickItemScorerAccessor, IQuickPickItemWithResource, IQuickPick } from 'vs/platform/quickinput/common/quickInput'; +import { PickerQuickAccessProvider, IPickerQuickAccessItem, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess'; +import { IEditorGroupsService, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { EditorsOrder, IEditorIdentifier, toResource, SideBySideEditor, GroupIdentifier } from 'vs/workbench/common/editor'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; +import { prepareQuery, scoreItemFuzzy, compareItemsByFuzzyScore, FuzzyScorerCache } from 'vs/base/common/fuzzyScorer'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IDisposable } from 'vs/base/common/lifecycle'; + +interface IEditorQuickPickItem extends IQuickPickItemWithResource, IPickerQuickAccessItem { + groupId: GroupIdentifier; +} + +export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessProvider { + + private readonly pickState = new class { + + scorerCache: FuzzyScorerCache = Object.create(null); + isQuickNavigating: boolean | undefined = undefined; + + reset(isQuickNavigating: boolean): void { + + // Caches + if (!isQuickNavigating) { + this.scorerCache = Object.create(null); + } + + // Other + this.isQuickNavigating = isQuickNavigating; + } + }; + + constructor( + prefix: string, + @IEditorGroupsService protected readonly editorGroupService: IEditorGroupsService, + @IEditorService protected readonly editorService: IEditorService, + @IModelService private readonly modelService: IModelService, + @IModeService private readonly modeService: IModeService + ) { + super(prefix, + { + canAcceptInBackground: true, + noResultsPick: { + label: localize('noViewResults', "No matching editors"), + groupId: -1 + } + } + ); + } + + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + + // Reset the pick state for this run + this.pickState.reset(!!picker.quickNavigate); + + // Start picker + return super.provide(picker, token); + } + + protected getPicks(filter: string): Array { + const query = prepareQuery(filter); + + // Filtering + const filteredEditorEntries = this.doGetEditorPickItems().filter(entry => { + if (!query.normalized) { + return true; + } + + // Score on label and description + const itemScore = scoreItemFuzzy(entry, query, true, quickPickItemScorerAccessor, this.pickState.scorerCache); + if (!itemScore.score) { + return false; + } + + // Apply highlights + entry.highlights = { label: itemScore.labelMatch, description: itemScore.descriptionMatch }; + + return true; + }); + + // Sorting + if (query.normalized) { + const groups = this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE).map(group => group.id); + filteredEditorEntries.sort((entryA, entryB) => { + if (entryA.groupId !== entryB.groupId) { + return groups.indexOf(entryA.groupId) - groups.indexOf(entryB.groupId); // older groups first + } + + return compareItemsByFuzzyScore(entryA, entryB, query, true, quickPickItemScorerAccessor, this.pickState.scorerCache); + }); + } + + // Grouping (for more than one group) + const filteredEditorEntriesWithSeparators: Array = []; + if (this.editorGroupService.count > 1) { + let lastGroupId: number | undefined = undefined; + for (const entry of filteredEditorEntries) { + if (typeof lastGroupId !== 'number' || lastGroupId !== entry.groupId) { + const group = this.editorGroupService.getGroup(entry.groupId); + if (group) { + filteredEditorEntriesWithSeparators.push({ type: 'separator', label: group.label }); + } + lastGroupId = entry.groupId; + } + + filteredEditorEntriesWithSeparators.push(entry); + } + } else { + filteredEditorEntriesWithSeparators.push(...filteredEditorEntries); + } + + return filteredEditorEntriesWithSeparators; + } + + private doGetEditorPickItems(): Array { + const editors = this.doGetEditors(); + + const mapGroupIdToGroupAriaLabel = new Map(); + for (const { groupId } of editors) { + if (!mapGroupIdToGroupAriaLabel.has(groupId)) { + const group = this.editorGroupService.getGroup(groupId); + if (group) { + mapGroupIdToGroupAriaLabel.set(groupId, group.ariaLabel); + } + } + } + + return this.doGetEditors().map(({ editor, groupId }): IEditorQuickPickItem => { + const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); + const isDirty = editor.isDirty() && !editor.isSaving(); + + return { + groupId, + resource, + label: editor.getName(), + ariaLabel: (() => { + if (mapGroupIdToGroupAriaLabel.size > 1) { + return isDirty ? + localize('entryAriaLabelWithGroupDirty', "{0}, dirty, {1}", editor.getName(), mapGroupIdToGroupAriaLabel.get(groupId)) : + localize('entryAriaLabelWithGroup', "{0}, {1}", editor.getName(), mapGroupIdToGroupAriaLabel.get(groupId)); + } + + return isDirty ? localize('entryAriaLabelDirty', "{0}, dirty", editor.getName()) : editor.getName(); + })(), + description: editor.getDescription(), + iconClasses: getIconClasses(this.modelService, this.modeService, resource), + italic: !this.editorGroupService.getGroup(groupId)?.isPinned(editor), + buttons: (() => { + return [ + { + iconClass: isDirty ? 'dirty-editor codicon-circle-filled' : 'codicon-close', + tooltip: localize('closeEditor', "Close Editor"), + alwaysVisible: isDirty + } + ]; + })(), + trigger: async () => { + const group = this.editorGroupService.getGroup(groupId); + if (group) { + await group.closeEditor(editor, { preserveFocus: true }); + + if (!group.isOpened(editor)) { + return TriggerAction.REMOVE_ITEM; + } + } + + return TriggerAction.NO_ACTION; + }, + accept: (keyMods, event) => this.editorGroupService.getGroup(groupId)?.openEditor(editor, { preserveFocus: event.inBackground }), + }; + }); + } + + protected abstract doGetEditors(): IEditorIdentifier[]; +} + +//#region Active Editor Group Editors by Most Recently Used + +export class ActiveGroupEditorsByMostRecentlyUsedQuickAccess extends BaseEditorQuickAccessProvider { + + static PREFIX = 'edt active '; + + constructor( + @IEditorGroupsService editorGroupService: IEditorGroupsService, + @IEditorService editorService: IEditorService, + @IModelService modelService: IModelService, + @IModeService modeService: IModeService + ) { + super(ActiveGroupEditorsByMostRecentlyUsedQuickAccess.PREFIX, editorGroupService, editorService, modelService, modeService); + } + + protected doGetEditors(): IEditorIdentifier[] { + const group = this.editorGroupService.activeGroup; + + return group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).map(editor => ({ editor, groupId: group.id })); + } +} + +//#endregion + + +//#region All Editors by Appearance + +export class AllEditorsByAppearanceQuickAccess extends BaseEditorQuickAccessProvider { + + static PREFIX = 'edt '; + + constructor( + @IEditorGroupsService editorGroupService: IEditorGroupsService, + @IEditorService editorService: IEditorService, + @IModelService modelService: IModelService, + @IModeService modeService: IModeService + ) { + super(AllEditorsByAppearanceQuickAccess.PREFIX, editorGroupService, editorService, modelService, modeService); + } + + protected doGetEditors(): IEditorIdentifier[] { + const entries: IEditorIdentifier[] = []; + + for (const group of this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE)) { + for (const editor of group.getEditors(EditorsOrder.SEQUENTIAL)) { + entries.push({ editor, groupId: group.id }); + } + } + + return entries; + } +} + +//#endregion + + +//#region All Editors by Most Recently Used + +export class AllEditorsByMostRecentlyUsedQuickAccess extends BaseEditorQuickAccessProvider { + + static PREFIX = 'edt mru '; + + constructor( + @IEditorGroupsService editorGroupService: IEditorGroupsService, + @IEditorService editorService: IEditorService, + @IModelService modelService: IModelService, + @IModeService modeService: IModeService + ) { + super(AllEditorsByMostRecentlyUsedQuickAccess.PREFIX, editorGroupService, editorService, modelService, modeService); + } + + protected doGetEditors(): IEditorIdentifier[] { + const entries: IEditorIdentifier[] = []; + + for (const editor of this.editorService.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)) { + entries.push(editor); + } + + return entries; + } +} + +//#endregion diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index aaf1ae0f35a..8cc4435ce50 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri'; import { Action } from 'vs/base/common/actions'; import { Language } from 'vs/base/common/platform'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; -import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput, IEditor as IBaseEditor, IEditorInput, SideBySideEditor, IModeSupport } from 'vs/workbench/common/editor'; +import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput, IEditorPane, IEditorInput, SideBySideEditor, IModeSupport } from 'vs/workbench/common/editor'; import { Disposable, MutableDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IEditorAction } from 'vs/editor/common/editorCommon'; import { EndOfLineSequence } from 'vs/editor/common/model'; @@ -363,8 +363,8 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } private async showIndentationPicker(): Promise { - const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget); - if (!activeTextEditorWidget) { + const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl); + if (!activeTextEditorControl) { return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); } @@ -373,19 +373,19 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } const picks: QuickPickInput[] = [ - activeTextEditorWidget.getAction(IndentUsingSpaces.ID), - activeTextEditorWidget.getAction(IndentUsingTabs.ID), - activeTextEditorWidget.getAction(DetectIndentation.ID), - activeTextEditorWidget.getAction(IndentationToSpacesAction.ID), - activeTextEditorWidget.getAction(IndentationToTabsAction.ID), - activeTextEditorWidget.getAction(TrimTrailingWhitespaceAction.ID) + activeTextEditorControl.getAction(IndentUsingSpaces.ID), + activeTextEditorControl.getAction(IndentUsingTabs.ID), + activeTextEditorControl.getAction(DetectIndentation.ID), + activeTextEditorControl.getAction(IndentationToSpacesAction.ID), + activeTextEditorControl.getAction(IndentationToTabsAction.ID), + activeTextEditorControl.getAction(TrimTrailingWhitespaceAction.ID) ].map((a: IEditorAction) => { return { id: a.id, label: a.label, detail: (Language.isDefaultVariant() || a.label === a.alias) ? undefined : a.alias, run: () => { - activeTextEditorWidget.focus(); + activeTextEditorControl.focus(); a.run(); } }; @@ -454,7 +454,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { const props: IStatusbarEntry = { text, - tooltip: nls.localize('gotoLine', "Go to Line"), + tooltip: nls.localize('gotoLine', "Go to Line/Column"), command: 'workbench.action.gotoLine' }; @@ -608,8 +608,8 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { private updateStatusBar(): void { const activeInput = this.editorService.activeEditor; - const activeControl = this.editorService.activeControl; - const activeCodeEditor = activeControl ? withNullAsUndefined(getCodeEditor(activeControl.getControl())) : undefined; + const activeEditorPane = this.editorService.activeEditorPane; + const activeCodeEditor = activeEditorPane ? withNullAsUndefined(getCodeEditor(activeEditorPane.getControl())) : undefined; // Update all states this.onColumnSelectionModeChange(activeCodeEditor); @@ -617,9 +617,9 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { this.onSelectionChange(activeCodeEditor); this.onModeChange(activeCodeEditor, activeInput); this.onEOLChange(activeCodeEditor); - this.onEncodingChange(activeControl, activeCodeEditor); + this.onEncodingChange(activeEditorPane, activeCodeEditor); this.onIndentationChange(activeCodeEditor); - this.onMetadataChange(activeControl); + this.onMetadataChange(activeEditorPane); this.currentProblemStatus.update(activeCodeEditor); // Dispose old active editor listeners @@ -672,25 +672,25 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } // Handle binary editors - else if (activeControl instanceof BaseBinaryResourceEditor || activeControl instanceof BinaryResourceDiffEditor) { + else if (activeEditorPane instanceof BaseBinaryResourceEditor || activeEditorPane instanceof BinaryResourceDiffEditor) { const binaryEditors: BaseBinaryResourceEditor[] = []; - if (activeControl instanceof BinaryResourceDiffEditor) { - const details = activeControl.getDetailsEditor(); + if (activeEditorPane instanceof BinaryResourceDiffEditor) { + const details = activeEditorPane.getDetailsEditorPane(); if (details instanceof BaseBinaryResourceEditor) { binaryEditors.push(details); } - const master = activeControl.getMasterEditor(); + const master = activeEditorPane.getMasterEditorPane(); if (master instanceof BaseBinaryResourceEditor) { binaryEditors.push(master); } } else { - binaryEditors.push(activeControl); + binaryEditors.push(activeEditorPane); } binaryEditors.forEach(editor => { this.activeEditorListeners.add(editor.onMetadataChanged(metadata => { - this.onMetadataChange(activeControl); + this.onMetadataChange(activeEditorPane); })); this.activeEditorListeners.add(editor.onDidOpenInPlace(() => { @@ -733,7 +733,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { this.updateState(update); } - private onMetadataChange(editor: IBaseEditor | undefined): void { + private onMetadataChange(editor: IEditorPane | undefined): void { const update: StateDelta = { type: 'metadata', metadata: undefined }; if (editor instanceof BaseBinaryResourceEditor || editor instanceof BinaryResourceDiffEditor) { @@ -832,7 +832,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { this.updateState(info); } - private onEncodingChange(editor: IBaseEditor | undefined, editorWidget: ICodeEditor | undefined): void { + private onEncodingChange(editor: IEditorPane | undefined, editorWidget: ICodeEditor | undefined): void { if (editor && !this.isActiveEditor(editor)) { return; } @@ -859,13 +859,13 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } private onResourceEncodingChange(resource: URI): void { - const activeControl = this.editorService.activeControl; - if (activeControl) { - const activeResource = toResource(activeControl.input, { supportSideBySide: SideBySideEditor.MASTER }); + const activeEditorPane = this.editorService.activeEditorPane; + if (activeEditorPane) { + const activeResource = toResource(activeEditorPane.input, { supportSideBySide: SideBySideEditor.MASTER }); if (activeResource && isEqual(activeResource, resource)) { - const activeCodeEditor = withNullAsUndefined(getCodeEditor(activeControl.getControl())); + const activeCodeEditor = withNullAsUndefined(getCodeEditor(activeEditorPane.getControl())); - return this.onEncodingChange(activeControl, activeCodeEditor); // only update if the encoding changed for the active resource + return this.onEncodingChange(activeEditorPane, activeCodeEditor); // only update if the encoding changed for the active resource } } } @@ -876,10 +876,10 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { this.updateState(info); } - private isActiveEditor(control: IBaseEditor): boolean { - const activeControl = this.editorService.activeControl; + private isActiveEditor(control: IEditorPane): boolean { + const activeEditorPane = this.editorService.activeEditorPane; - return !!activeControl && activeControl === control; + return !!activeEditorPane && activeEditorPane === control; } } @@ -1047,13 +1047,13 @@ export class ChangeModeAction extends Action { } async run(): Promise { - const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget); - if (!activeTextEditorWidget) { + const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl); + if (!activeTextEditorControl) { await this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); return; } - const textModel = activeTextEditorWidget.getModel(); + const textModel = activeTextEditorControl.getModel(); const resource = this.editorService.activeEditor ? toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }) : null; let hasLanguageSupport = !!resource; @@ -1137,7 +1137,7 @@ export class ChangeModeAction extends Action { // User decided to configure settings for current language if (pick === configureModeSettings) { - this.preferencesService.configureSettingsForLanguage(withUndefinedAsNull(modeId)); + this.preferencesService.openGlobalSettings(true, { editSetting: `[${withUndefinedAsNull(modeId)}]` }); return; } @@ -1209,7 +1209,7 @@ export class ChangeModeAction extends Action { this.configurationService.updateValue(FILES_ASSOCIATIONS_CONFIG, currentAssociations, target); } - }, 50 /* quick open is sensitive to being opened so soon after another */); + }, 50 /* quick input is sensitive to being opened so soon after another */); } private getFakeResource(lang: string): URI | undefined { @@ -1248,8 +1248,8 @@ export class ChangeEOLAction extends Action { } async run(): Promise { - const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget); - if (!activeTextEditorWidget) { + const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl); + if (!activeTextEditorControl) { await this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); return; } @@ -1259,7 +1259,7 @@ export class ChangeEOLAction extends Action { return; } - let textModel = activeTextEditorWidget.getModel(); + let textModel = activeTextEditorControl.getModel(); const EOLOptions: IChangeEOLEntry[] = [ { label: nlsEOLLF, eol: EndOfLineSequence.LF }, @@ -1270,7 +1270,7 @@ export class ChangeEOLAction extends Action { const eol = await this.quickInputService.pick(EOLOptions, { placeHolder: nls.localize('pickEndOfLine', "Select End of Line Sequence"), activeItem: EOLOptions[selectedIndex] }); if (eol) { - const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorWidget); + const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorControl); if (activeCodeEditor?.hasModel() && !this.editorService.activeEditor?.isReadonly()) { textModel = activeCodeEditor.getModel(); textModel.pushStackElement(); @@ -1299,18 +1299,18 @@ export class ChangeEncodingAction extends Action { } async run(): Promise { - if (!getCodeEditor(this.editorService.activeTextEditorWidget)) { + if (!getCodeEditor(this.editorService.activeTextEditorControl)) { await this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); return; } - const activeControl = this.editorService.activeControl; - if (!activeControl) { + const activeEditorPane = this.editorService.activeEditorPane; + if (!activeEditorPane) { await this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); return; } - const encodingSupport: IEncodingSupport | null = toEditorWithEncodingSupport(activeControl.input); + const encodingSupport: IEncodingSupport | null = toEditorWithEncodingSupport(activeEditorPane.input); if (!encodingSupport) { await this.quickInputService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]); return; @@ -1334,7 +1334,7 @@ export class ChangeEncodingAction extends Action { let action: IQuickPickItem; if (encodingSupport instanceof UntitledTextEditorInput) { action = saveWithEncodingPick; - } else if (activeControl.input.isReadonly()) { + } else if (activeEditorPane.input.isReadonly()) { action = reopenWithEncodingPick; } else { action = await this.quickInputService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true }); @@ -1344,9 +1344,9 @@ export class ChangeEncodingAction extends Action { return; } - await timeout(50); // quick open is sensitive to being opened so soon after another + await timeout(50); // quick input is sensitive to being opened so soon after another - const resource = toResource(activeControl.input, { supportSideBySide: SideBySideEditor.MASTER }); + const resource = toResource(activeEditorPane.input, { supportSideBySide: SideBySideEditor.MASTER }); if (!resource || (!this.fileService.canHandleResource(resource) && resource.scheme !== Schemas.untitled)) { return; // encoding detection only possible for resources the file service can handle or that are untitled } @@ -1409,11 +1409,11 @@ export class ChangeEncodingAction extends Action { return; } - if (!this.editorService.activeControl) { + if (!this.editorService.activeEditorPane) { return; } - const activeEncodingSupport = toEditorWithEncodingSupport(this.editorService.activeControl.input); + const activeEncodingSupport = toEditorWithEncodingSupport(this.editorService.activeEditorPane.input); if (typeof encoding.id !== 'undefined' && activeEncodingSupport && activeEncodingSupport.getEncoding() !== encoding.id) { activeEncodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding } diff --git a/src/vs/workbench/browser/parts/editor/editorsObserver.ts b/src/vs/workbench/browser/parts/editor/editorsObserver.ts index b370de392c8..47d62dc9351 100644 --- a/src/vs/workbench/browser/parts/editor/editorsObserver.ts +++ b/src/vs/workbench/browser/parts/editor/editorsObserver.ts @@ -3,15 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IEditorInput, IEditorInputFactoryRegistry, IEditorIdentifier, GroupIdentifier, Extensions, IEditorPartOptionsChangeEvent, EditorsOrder } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditorInputFactoryRegistry, IEditorIdentifier, GroupIdentifier, Extensions, IEditorPartOptionsChangeEvent, EditorsOrder, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { Registry } from 'vs/platform/registry/common/platform'; import { Event, Emitter } from 'vs/base/common/event'; import { IEditorGroupsService, IEditorGroup, GroupChangeKind, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { coalesce } from 'vs/base/common/arrays'; -import { LinkedMap, Touch } from 'vs/base/common/map'; +import { LinkedMap, Touch, ResourceMap } from 'vs/base/common/map'; import { equals } from 'vs/base/common/objects'; +import { URI } from 'vs/base/common/uri'; interface ISerializedEditorsList { entries: ISerializedEditorIdentifier[]; @@ -37,9 +38,10 @@ export class EditorsObserver extends Disposable { private readonly keyMap = new Map>(); private readonly mostRecentEditorsMap = new LinkedMap(); + private readonly editorResourcesMap = new ResourceMap(); - private readonly _onDidChange = this._register(new Emitter()); - readonly onDidChange = this._onDidChange.event; + private readonly _onDidMostRecentlyActiveEditorsChange = this._register(new Emitter()); + readonly onDidMostRecentlyActiveEditorsChange = this._onDidMostRecentlyActiveEditorsChange.event; get count(): number { return this.mostRecentEditorsMap.size; @@ -49,6 +51,10 @@ export class EditorsObserver extends Disposable { return this.mostRecentEditorsMap.values(); } + hasEditor(resource: URI): boolean { + return this.editorResourcesMap.has(resource); + } + constructor( @IEditorGroupsService private editorGroupsService: IEditorGroupsService, @IStorageService private readonly storageService: IStorageService @@ -72,12 +78,12 @@ export class EditorsObserver extends Disposable { // of the new group into our list in LRU order const groupEditorsMru = group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE); for (let i = groupEditorsMru.length - 1; i >= 0; i--) { - this.addMostRecentEditor(group, groupEditorsMru[i], false /* is not active */); + this.addMostRecentEditor(group, groupEditorsMru[i], false /* is not active */, true /* is new */); } // Make sure that active editor is put as first if group is active if (this.editorGroupsService.activeGroup === group && group.activeEditor) { - this.addMostRecentEditor(group, group.activeEditor, true /* is active */); + this.addMostRecentEditor(group, group.activeEditor, true /* is active */, false /* already added before */); } // Group Listeners @@ -92,7 +98,7 @@ export class EditorsObserver extends Disposable { // Group gets active: put active editor as most recent case GroupChangeKind.GROUP_ACTIVE: { if (this.editorGroupsService.activeGroup === group && group.activeEditor) { - this.addMostRecentEditor(group, group.activeEditor, true /* is active */); + this.addMostRecentEditor(group, group.activeEditor, true /* is active */, false /* editor already opened */); } break; @@ -102,7 +108,7 @@ export class EditorsObserver extends Disposable { // if group is active, otherwise second most recent case GroupChangeKind.EDITOR_ACTIVE: { if (e.editor) { - this.addMostRecentEditor(group, e.editor, this.editorGroupsService.activeGroup === group); + this.addMostRecentEditor(group, e.editor, this.editorGroupsService.activeGroup === group, false /* editor already opened */); } break; @@ -114,7 +120,7 @@ export class EditorsObserver extends Disposable { // start to close oldest ones if needed. case GroupChangeKind.EDITOR_OPEN: { if (e.editor) { - this.addMostRecentEditor(group, e.editor, false /* is not active */); + this.addMostRecentEditor(group, e.editor, false /* is not active */, true /* is new */); this.ensureOpenedEditorsLimit({ groupId: group.id, editor: e.editor }, group.id); } @@ -148,7 +154,7 @@ export class EditorsObserver extends Disposable { } } - private addMostRecentEditor(group: IEditorGroup, editor: IEditorInput, isActive: boolean): void { + private addMostRecentEditor(group: IEditorGroup, editor: IEditorInput, isActive: boolean, isNew: boolean): void { const key = this.ensureKey(group, editor); const mostRecentEditor = this.mostRecentEditorsMap.first; @@ -169,11 +175,39 @@ export class EditorsObserver extends Disposable { this.mostRecentEditorsMap.set(mostRecentEditor, mostRecentEditor, Touch.AsOld /* make first */); } + // Update in resource map if this is a new editor + if (isNew) { + this.updateEditorResourcesMap(editor, true); + } + // Event - this._onDidChange.fire(); + this._onDidMostRecentlyActiveEditorsChange.fire(); + } + + private updateEditorResourcesMap(editor: IEditorInput, add: boolean): void { + const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); + if (!resource) { + return; // require a resource + } + + if (add) { + this.editorResourcesMap.set(resource, (this.editorResourcesMap.get(resource) ?? 0) + 1); + } else { + const counter = this.editorResourcesMap.get(resource) ?? 0; + if (counter > 1) { + this.editorResourcesMap.set(resource, counter - 1); + } else { + this.editorResourcesMap.delete(resource); + } + } } private removeMostRecentEditor(group: IEditorGroup, editor: IEditorInput): void { + + // Update in resource map + this.updateEditorResourcesMap(editor, false); + + // Update in MRU list const key = this.findKey(group, editor); if (key) { @@ -187,7 +221,7 @@ export class EditorsObserver extends Disposable { } // Event - this._onDidChange.fire(); + this._onDidMostRecentlyActiveEditorsChange.fire(); } } @@ -361,7 +395,7 @@ export class EditorsObserver extends Disposable { const group = groups[i]; const groupEditorsMru = group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE); for (let i = groupEditorsMru.length - 1; i >= 0; i--) { - this.addMostRecentEditor(group, groupEditorsMru[i], true /* enforce as active to preserve order */); + this.addMostRecentEditor(group, groupEditorsMru[i], true /* enforce as active to preserve order */, true /* is new */); } } } @@ -392,6 +426,9 @@ export class EditorsObserver extends Disposable { // Make sure key is registered as well const editorIdentifier = this.ensureKey(group, editor); mapValues.push([editorIdentifier, editorIdentifier]); + + // Update in resource map + this.updateEditorResourcesMap(editor, true); } // Fill map with deserialized values diff --git a/src/vs/workbench/browser/parts/editor/media/editordroptarget.css b/src/vs/workbench/browser/parts/editor/media/editordroptarget.css index b961f0defd7..1c11b159030 100644 --- a/src/vs/workbench/browser/parts/editor/media/editordroptarget.css +++ b/src/vs/workbench/browser/parts/editor/media/editordroptarget.css @@ -22,8 +22,9 @@ opacity: 0; /* hidden initially */ transition: opacity 150ms ease-out; + /* color: red; */ } #monaco-workbench-editor-drop-overlay > .editor-group-overlay-indicator.overlay-move-transition { transition: top 70ms ease-out, left 70ms ease-out, width 70ms ease-out, height 70ms ease-out, opacity 150ms ease-out; -} \ No newline at end of file +} diff --git a/src/vs/editor/standalone/browser/quickOpen/gotoLine.css b/src/vs/workbench/browser/parts/editor/media/editorquickaccess.css similarity index 61% rename from src/vs/editor/standalone/browser/quickOpen/gotoLine.css rename to src/vs/workbench/browser/parts/editor/media/editorquickaccess.css index e71a5e1dd76..89390b0c7e5 100644 --- a/src/vs/editor/standalone/browser/quickOpen/gotoLine.css +++ b/src/vs/workbench/browser/parts/editor/media/editorquickaccess.css @@ -3,6 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-quick-open-widget { - font-size: 13px; -} \ No newline at end of file +.quick-input-list .quick-input-list-entry.has-actions:hover .quick-input-list-entry-action-bar .action-label.dirty-editor::before { + content: "\ea76"; /* Close icon flips between black dot and "X" for dirty open editors */ +} diff --git a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts index 211fdec8ce1..e595a8e0bce 100644 --- a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts @@ -88,8 +88,8 @@ export class NoTabsTitleControl extends TitleControl { private onTitleLabelClick(e: MouseEvent): void { EventHelper.stop(e, false); - // delayed to let the onTitleClick() come first which can cause a focus change which can close quick open - setTimeout(() => this.quickOpenService.show()); + // delayed to let the onTitleClick() come first which can cause a focus change which can close quick access + setTimeout(() => this.quickInputService.quickAccess.show()); } private onTitleDoubleClick(e: MouseEvent): void { @@ -110,11 +110,11 @@ export class NoTabsTitleControl extends TitleControl { } } } else { - // @rebornix - // gesture tap should open the quick open + // TODO@rebornix + // gesture tap should open the quick access // editorGroupView will focus on the editor again when there are mouse/pointer/touch down events // we need to wait a bit as `GesureEvent.Tap` is generated from `touchstart` and then `touchend` evnets, which are not an atom event. - setTimeout(() => this.quickOpenService.show(), 50); + setTimeout(() => this.quickInputService.quickAccess.show(), 50); } } @@ -260,9 +260,6 @@ export class NoTabsTitleControl extends TitleControl { this.updateEditorDirty(editor); // Editor Label - const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); - const name = editor.getName(); - const { labelFormat } = this.accessor.partOptions; let description: string; if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) { @@ -278,7 +275,19 @@ export class NoTabsTitleControl extends TitleControl { title = ''; // dont repeat what is already shown } - editorLabel.setResource({ name, description, resource }, { title: typeof title === 'string' ? title : undefined, italic: !isEditorPinned, extraClasses: ['no-tabs', 'title-label'] }); + editorLabel.setResource( + { + resource: toResource(editor, { supportSideBySide: SideBySideEditor.BOTH }), + name: editor.getName(), + description + }, + { + title, + italic: !isEditorPinned, + extraClasses: ['no-tabs', 'title-label'] + } + ); + if (isGroupActive) { editorLabel.element.style.color = this.getColor(TAB_ACTIVE_FOREGROUND) || ''; } else { diff --git a/src/vs/workbench/browser/parts/editor/rangeDecorations.ts b/src/vs/workbench/browser/parts/editor/rangeDecorations.ts index 6a97ae80407..98304f2366b 100644 --- a/src/vs/workbench/browser/parts/editor/rangeDecorations.ts +++ b/src/vs/workbench/browser/parts/editor/rangeDecorations.ts @@ -10,7 +10,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IRange } from 'vs/editor/common/core/range'; import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { TrackedRangeStickiness, IModelDecorationsChangeAccessor } from 'vs/editor/common/model'; export interface IRangeHighlightDecoration { @@ -41,9 +41,9 @@ export class RangeHighlightDecorations extends Disposable { this.rangeHighlightDecorationId = null; } - highlightRange(range: IRangeHighlightDecoration, editor?: ICodeEditor) { + highlightRange(range: IRangeHighlightDecoration, editor?: any) { editor = editor ? editor : this.getEditor(range); - if (editor) { + if (isCodeEditor(editor)) { this.doHighlightRange(editor, range); } } @@ -63,7 +63,7 @@ export class RangeHighlightDecorations extends Disposable { const resource = activeEditor && activeEditor.resource; if (resource) { if (resource.toString() === resourceRange.resource.toString()) { - return this.editorService.activeTextEditorWidget as ICodeEditor; + return this.editorService.activeTextEditorControl as ICodeEditor; } } diff --git a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts index 2ea2a495bab..fd57b26ca33 100644 --- a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts +++ b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts @@ -5,7 +5,7 @@ import * as DOM from 'vs/base/browser/dom'; import { Registry } from 'vs/platform/registry/common/platform'; -import { EditorInput, EditorOptions, SideBySideEditorInput, IEditorControl, IEditor } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, SideBySideEditorInput, IEditorControl, IEditorPane } from 'vs/workbench/common/editor'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -24,15 +24,15 @@ export class SideBySideEditor extends BaseEditor { static readonly ID: string = 'workbench.editor.sidebysideEditor'; static MASTER: SideBySideEditor | undefined; - get minimumMasterWidth() { return this.masterEditor ? this.masterEditor.minimumWidth : 0; } - get maximumMasterWidth() { return this.masterEditor ? this.masterEditor.maximumWidth : Number.POSITIVE_INFINITY; } - get minimumMasterHeight() { return this.masterEditor ? this.masterEditor.minimumHeight : 0; } - get maximumMasterHeight() { return this.masterEditor ? this.masterEditor.maximumHeight : Number.POSITIVE_INFINITY; } + get minimumMasterWidth() { return this.masterEditorPane ? this.masterEditorPane.minimumWidth : 0; } + get maximumMasterWidth() { return this.masterEditorPane ? this.masterEditorPane.maximumWidth : Number.POSITIVE_INFINITY; } + get minimumMasterHeight() { return this.masterEditorPane ? this.masterEditorPane.minimumHeight : 0; } + get maximumMasterHeight() { return this.masterEditorPane ? this.masterEditorPane.maximumHeight : Number.POSITIVE_INFINITY; } - get minimumDetailsWidth() { return this.detailsEditor ? this.detailsEditor.minimumWidth : 0; } - get maximumDetailsWidth() { return this.detailsEditor ? this.detailsEditor.maximumWidth : Number.POSITIVE_INFINITY; } - get minimumDetailsHeight() { return this.detailsEditor ? this.detailsEditor.minimumHeight : 0; } - get maximumDetailsHeight() { return this.detailsEditor ? this.detailsEditor.maximumHeight : Number.POSITIVE_INFINITY; } + get minimumDetailsWidth() { return this.detailsEditorPane ? this.detailsEditorPane.minimumWidth : 0; } + get maximumDetailsWidth() { return this.detailsEditorPane ? this.detailsEditorPane.maximumWidth : Number.POSITIVE_INFINITY; } + get minimumDetailsHeight() { return this.detailsEditorPane ? this.detailsEditorPane.minimumHeight : 0; } + get maximumDetailsHeight() { return this.detailsEditorPane ? this.detailsEditorPane.maximumHeight : Number.POSITIVE_INFINITY; } // these setters need to exist because this extends from BaseEditor set minimumWidth(value: number) { /* noop */ } @@ -45,8 +45,8 @@ export class SideBySideEditor extends BaseEditor { get minimumHeight() { return this.minimumMasterHeight + this.minimumDetailsHeight; } get maximumHeight() { return this.maximumMasterHeight + this.maximumDetailsHeight; } - protected masterEditor?: BaseEditor; - protected detailsEditor?: BaseEditor; + protected masterEditorPane?: BaseEditor; + protected detailsEditorPane?: BaseEditor; private masterEditorContainer: HTMLElement | undefined; private detailsEditorContainer: HTMLElement | undefined; @@ -77,7 +77,7 @@ export class SideBySideEditor extends BaseEditor { this.detailsEditorContainer = DOM.$('.details-editor-container'); this.splitview.addView({ element: this.detailsEditorContainer, - layout: size => this.detailsEditor && this.detailsEditor.layout(new DOM.Dimension(size, this.dimension.height)), + layout: size => this.detailsEditorPane && this.detailsEditorPane.layout(new DOM.Dimension(size, this.dimension.height)), minimumSize: 220, maximumSize: Number.POSITIVE_INFINITY, onDidChange: Event.None @@ -86,7 +86,7 @@ export class SideBySideEditor extends BaseEditor { this.masterEditorContainer = DOM.$('.master-editor-container'); this.splitview.addView({ element: this.masterEditorContainer, - layout: size => this.masterEditor && this.masterEditor.layout(new DOM.Dimension(size, this.dimension.height)), + layout: size => this.masterEditorPane && this.masterEditorPane.layout(new DOM.Dimension(size, this.dimension.height)), minimumSize: 220, maximumSize: Number.POSITIVE_INFINITY, onDidChange: Event.None @@ -103,30 +103,30 @@ export class SideBySideEditor extends BaseEditor { } setOptions(options: EditorOptions | undefined): void { - if (this.masterEditor) { - this.masterEditor.setOptions(options); + if (this.masterEditorPane) { + this.masterEditorPane.setOptions(options); } } protected setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { - if (this.masterEditor) { - this.masterEditor.setVisible(visible, group); + if (this.masterEditorPane) { + this.masterEditorPane.setVisible(visible, group); } - if (this.detailsEditor) { - this.detailsEditor.setVisible(visible, group); + if (this.detailsEditorPane) { + this.detailsEditorPane.setVisible(visible, group); } super.setEditorVisible(visible, group); } clearInput(): void { - if (this.masterEditor) { - this.masterEditor.clearInput(); + if (this.masterEditorPane) { + this.masterEditorPane.clearInput(); } - if (this.detailsEditor) { - this.detailsEditor.clearInput(); + if (this.detailsEditorPane) { + this.detailsEditorPane.clearInput(); } this.disposeEditors(); @@ -135,8 +135,8 @@ export class SideBySideEditor extends BaseEditor { } focus(): void { - if (this.masterEditor) { - this.masterEditor.focus(); + if (this.masterEditorPane) { + this.masterEditorPane.focus(); } } @@ -148,19 +148,19 @@ export class SideBySideEditor extends BaseEditor { } getControl(): IEditorControl | undefined { - if (this.masterEditor) { - return this.masterEditor.getControl(); + if (this.masterEditorPane) { + return this.masterEditorPane.getControl(); } return undefined; } - getMasterEditor(): IEditor | undefined { - return this.masterEditor; + getMasterEditorPane(): IEditorPane | undefined { + return this.masterEditorPane; } - getDetailsEditor(): IEditor | undefined { - return this.detailsEditor; + getDetailsEditorPane(): IEditorPane | undefined { + return this.detailsEditorPane; } private async updateInput(oldInput: SideBySideEditorInput, newInput: SideBySideEditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { @@ -172,13 +172,13 @@ export class SideBySideEditor extends BaseEditor { return this.setNewInput(newInput, options, token); } - if (!this.detailsEditor || !this.masterEditor) { + if (!this.detailsEditorPane || !this.masterEditorPane) { return; } await Promise.all([ - this.detailsEditor.setInput(newInput.details, undefined, token), - this.masterEditor.setInput(newInput.master, options, token) + this.detailsEditorPane.setInput(newInput.details, undefined, token), + this.masterEditorPane.setInput(newInput.master, options, token) ]); } @@ -203,8 +203,8 @@ export class SideBySideEditor extends BaseEditor { } private async onEditorsCreated(details: BaseEditor, master: BaseEditor, detailsInput: EditorInput, masterInput: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { - this.detailsEditor = details; - this.masterEditor = master; + this.detailsEditorPane = details; + this.masterEditorPane = master; this._onDidSizeConstraintsChange.input = Event.any( Event.map(details.onDidSizeConstraintsChange, () => undefined), @@ -214,8 +214,8 @@ export class SideBySideEditor extends BaseEditor { this.onDidCreateEditors.fire(undefined); await Promise.all([ - this.detailsEditor.setInput(detailsInput, undefined, token), - this.masterEditor.setInput(masterInput, options, token)] + this.detailsEditorPane.setInput(detailsInput, undefined, token), + this.masterEditorPane.setInput(masterInput, options, token)] ); } @@ -228,14 +228,14 @@ export class SideBySideEditor extends BaseEditor { } private disposeEditors(): void { - if (this.detailsEditor) { - this.detailsEditor.dispose(); - this.detailsEditor = undefined; + if (this.detailsEditorPane) { + this.detailsEditorPane.dispose(); + this.detailsEditorPane = undefined; } - if (this.masterEditor) { - this.masterEditor.dispose(); - this.masterEditor = undefined; + if (this.masterEditorPane) { + this.masterEditorPane.dispose(); + this.masterEditorPane = undefined; } if (this.detailsEditorContainer) { diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 3509fe604e0..75729287c9a 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/tabstitlecontrol'; -import { isMacintosh } from 'vs/base/common/platform'; +import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { shorten } from 'vs/base/common/labels'; import { toResource, GroupIdentifier, IEditorInput, Verbosity, EditorCommandsContextActionRunner, IEditorPartOptions, SideBySideEditor } from 'vs/workbench/common/editor'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -19,12 +19,12 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IMenuService } from 'vs/platform/actions/common/actions'; import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IDisposable, dispose, DisposableStore, combinedDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { getOrSet } from 'vs/base/common/map'; -import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BACKGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP, TAB_ACTIVE_MODIFIED_BORDER, TAB_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER } from 'vs/workbench/common/theme'; import { activeContrastBorder, contrastBorder, editorBackground, breadcrumbsBackground } from 'vs/platform/theme/common/colorRegistry'; import { ResourcesDropHandler, fillResourceDataTransfers, DraggedEditorIdentifier, DraggedEditorGroupIdentifier, DragAndDropObserver } from 'vs/workbench/browser/dnd'; @@ -40,9 +40,11 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { BreadcrumbsControl } from 'vs/workbench/browser/parts/editor/breadcrumbsControl'; import { IFileService } from 'vs/platform/files/common/files'; import { withNullAsUndefined, assertAllDefined, assertIsDefined } from 'vs/base/common/types'; -import { ILabelService } from 'vs/platform/label/common/label'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { basenameOrAuthority } from 'vs/base/common/resources'; +import { RunOnceScheduler } from 'vs/base/common/async'; +import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; +import { IPath, win32, posix } from 'vs/base/common/path'; interface IEditorInputLabel { name?: string; @@ -54,6 +56,11 @@ type AugmentedLabel = IEditorInputLabel & { editor: IEditorInput }; export class TabsTitleControl extends TitleControl { + private static readonly SCROLLBAR_SIZES = { + default: 3, + large: 10 + }; + private titleContainer: HTMLElement | undefined; private tabsContainer: HTMLElement | undefined; private editorToolbarContainer: HTMLElement | undefined; @@ -69,6 +76,8 @@ export class TabsTitleControl extends TitleControl { private readonly layoutScheduled = this._register(new MutableDisposable()); private blockRevealActiveTab: boolean | undefined; + private path: IPath = isWindows ? win32 : posix; + constructor( parent: HTMLElement, accessor: IEditorGroupsAccessor, @@ -80,18 +89,33 @@ export class TabsTitleControl extends TitleControl { @ITelemetryService telemetryService: ITelemetryService, @INotificationService notificationService: INotificationService, @IMenuService menuService: IMenuService, - @IQuickOpenService quickOpenService: IQuickOpenService, + @IQuickInputService quickInputService: IQuickInputService, @IThemeService themeService: IThemeService, @IExtensionService extensionService: IExtensionService, @IConfigurationService configurationService: IConfigurationService, @IFileService fileService: IFileService, - @ILabelService labelService: ILabelService, - @IEditorService private readonly editorService: EditorServiceImpl + @IEditorService private readonly editorService: EditorServiceImpl, + @IRemotePathService private readonly remotePathService: IRemotePathService ) { - super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickOpenService, themeService, extensionService, configurationService, fileService, labelService); + super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickInputService, themeService, extensionService, configurationService, fileService); this.tabResourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER)); this.closeOneEditorAction = this._register(this.instantiationService.createInstance(CloseOneEditorAction, CloseOneEditorAction.ID, CloseOneEditorAction.LABEL)); + + // Resolve the correct path library for the OS we are on + // If we are connected to remote, this accounts for the + // remote OS. + (async () => this.path = await this.remotePathService.path)(); + } + + protected registerListeners(): void { + super.registerListeners(); + + this._register(this.accessor.onDidEditorPartOptionsChange(e => { + if (e.oldPartOptions.titleScrollbarSizing !== e.newPartOptions.titleScrollbarSizing) { + this.updateTabsScrollbarSizing(); + } + })); } protected create(parent: HTMLElement): void { @@ -134,10 +158,10 @@ export class TabsTitleControl extends TitleControl { private createTabsScrollbar(scrollable: HTMLElement): ScrollableElement { const tabsScrollbar = new ScrollableElement(scrollable, { horizontal: ScrollbarVisibility.Auto, + horizontalScrollbarSize: this.getTabsScrollbarSizing(), vertical: ScrollbarVisibility.Hidden, scrollYToX: true, - useShadows: false, - horizontalScrollbarSize: 3 + useShadows: false }); tabsScrollbar.onScroll(e => { @@ -147,6 +171,20 @@ export class TabsTitleControl extends TitleControl { return tabsScrollbar; } + private updateTabsScrollbarSizing(): void { + this.tabsScrollbar?.updateOptions({ + horizontalScrollbarSize: this.getTabsScrollbarSizing() + }); + } + + private getTabsScrollbarSizing(): number { + if (this.accessor.partOptions.titleScrollbarSizing !== 'large') { + return TabsTitleControl.SCROLLBAR_SIZES.default; + } + + return TabsTitleControl.SCROLLBAR_SIZES.large; + } + private updateBreadcrumbsControl(): void { if (this.breadcrumbsControl && this.breadcrumbsControl.update()) { // relayout when we have a breadcrumbs and when update changed @@ -193,13 +231,13 @@ export class TabsTitleControl extends TitleControl { EventHelper.stop(e); - this.group.openEditor(this.editorService.createInput({ - forceUntitled: true, - options: { + this.group.openEditor( + this.editorService.createEditorInput({ forceUntitled: true }), + { pinned: true, // untitled is always pinned index: this.group.count // always at the end } - })); + ); })); }); @@ -392,10 +430,16 @@ export class TabsTitleControl extends TitleControl { this.layout(this.dimension); } + private updateEditorLabelAggregator = this._register(new RunOnceScheduler(() => this.updateEditorLabels(), 0)); + updateEditorLabel(editor: IEditorInput): void { // Update all labels to account for changes to tab labels - this.updateEditorLabels(); + // Since this method may be called a lot of times from + // individual editors, we collect all those requests and + // then run the update once because we have to update + // all opened tabs in the group at once. + this.updateEditorLabelAggregator.schedule(); } updateEditorLabels(): void { @@ -869,7 +913,7 @@ export class TabsTitleControl extends TitleControl { } // Shorten descriptions - const shortenedDescriptions = shorten(descriptions); + const shortenedDescriptions = shorten(descriptions, this.path.sep); descriptions.forEach((description, i) => { for (const label of mapDescriptionToDuplicates.get(description) || []) { label.description = shortenedDescriptions[i]; @@ -948,10 +992,13 @@ export class TabsTitleControl extends TitleControl { tabContainer.title = title; // Label - const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); - tabLabelWidget.setResource({ name, description, resource }, { title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor) }); + tabLabelWidget.setResource( + { name, description, resource: toResource(editor, { supportSideBySide: SideBySideEditor.BOTH }) }, + { title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor) } + ); // Tests helper + const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); if (resource) { tabContainer.setAttribute('data-resource-name', basenameOrAuthority(resource)); } else { @@ -1236,7 +1283,7 @@ export class TabsTitleControl extends TitleControl { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { // Add border between tabs and breadcrumbs in high contrast mode. if (theme.type === HIGH_CONTRAST) { const borderColor = (theme.getColor(TAB_BORDER) || theme.getColor(contrastBorder)); diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index ec42a8b48cb..23a1cecf42c 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -9,7 +9,7 @@ import { isFunction, isObject, isArray, assertIsDefined } from 'vs/base/common/t import { IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditorOptions, IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BaseTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor'; -import { TextEditorOptions, EditorInput, EditorOptions, TEXT_DIFF_EDITOR_ID, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, ITextDiffEditor, IEditorMemento } from 'vs/workbench/common/editor'; +import { TextEditorOptions, EditorInput, EditorOptions, TEXT_DIFF_EDITOR_ID, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, ITextDiffEditorPane, IEditorMemento } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { DiffNavigator } from 'vs/editor/browser/widget/diffNavigator'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; @@ -34,7 +34,7 @@ import { EditorActivation, IEditorOptions } from 'vs/platform/editor/common/edit /** * The text editor that leverages the diff text editor for the editing experience. */ -export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { +export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPane { static readonly ID = TEXT_DIFF_EDITOR_ID; @@ -160,12 +160,12 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { const binaryDiffInput = new DiffEditorInput(input.getName(), input.getDescription(), originalInput, modifiedInput, true); // Forward binary flag to input if supported - const fileInputFactory = Registry.as(EditorInputExtensions.EditorInputFactories).getFileInputFactory(); - if (fileInputFactory.isFileInput(originalInput)) { + const fileEditorInputFactory = Registry.as(EditorInputExtensions.EditorInputFactories).getFileEditorInputFactory(); + if (fileEditorInputFactory.isFileEditorInput(originalInput)) { originalInput.setForceOpenAsBinary(); } - if (fileInputFactory.isFileInput(modifiedInput)) { + if (fileEditorInputFactory.isFileEditorInput(modifiedInput)) { modifiedInput.setForceOpenAsBinary(); } diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index bb60338a50c..5d9e4a14899 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -10,7 +10,7 @@ import { Event } from 'vs/base/common/event'; import { isObject, assertIsDefined, withNullAsUndefined, isFunction } from 'vs/base/common/types'; import { Dimension } from 'vs/base/browser/dom'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { EditorInput, EditorOptions, IEditorMemento, ITextEditor, TextEditorOptions } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, IEditorMemento, ITextEditorPane, TextEditorOptions } from 'vs/workbench/common/editor'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { IEditorViewState, IEditor, ScrollType } from 'vs/editor/common/editorCommon'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -33,7 +33,7 @@ export interface IEditorConfiguration { * The base class of editors that leverage the text editor for the editing experience. This class is only intended to * be subclassed and not instantiated. */ -export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { +export abstract class BaseTextEditor extends BaseEditor implements ITextEditorPane { static readonly TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'textEditorViewState'; diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index 1077097edde..b7a18e813ad 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -6,7 +6,7 @@ import { applyDragImage, DataTransfers } from 'vs/base/browser/dnd'; import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { ActionsOrientation, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionsOrientation, IActionViewItem, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IAction, IRunEvent, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import * as arrays from 'vs/base/common/arrays'; @@ -23,11 +23,10 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { listActiveSelectionBackground, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry'; -import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { prepareActions } from 'vs/workbench/browser/actions'; +import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService'; import { DraggedEditorGroupIdentifier, DraggedEditorIdentifier, fillResourceDataTransfers, LocalSelectionTransfer } from 'vs/workbench/browser/dnd'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs'; @@ -35,12 +34,10 @@ import { BreadcrumbsControl, IBreadcrumbsControlOptions } from 'vs/workbench/bro import { EDITOR_TITLE_HEIGHT, IEditorGroupsAccessor, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { EditorCommandsContextActionRunner, IEditorCommandsContext, IEditorInput, toResource, IEditorPartOptions, SideBySideEditor, EditorPinnedContext } from 'vs/workbench/common/editor'; import { ResourceContextKey } from 'vs/workbench/common/resources'; -import { Themable } from 'vs/workbench/common/theme'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IFileService } from 'vs/platform/files/common/files'; import { withNullAsUndefined, withUndefinedAsNull, assertIsDefined } from 'vs/base/common/types'; -import { ILabelService } from 'vs/platform/label/common/label'; import { isFirefox } from 'vs/base/browser/browser'; export interface IToolbarActions { @@ -78,12 +75,11 @@ export abstract class TitleControl extends Themable { @ITelemetryService private readonly telemetryService: ITelemetryService, @INotificationService private readonly notificationService: INotificationService, @IMenuService private readonly menuService: IMenuService, - @IQuickOpenService protected quickOpenService: IQuickOpenService, + @IQuickInputService protected quickInputService: IQuickInputService, @IThemeService themeService: IThemeService, @IExtensionService private readonly extensionService: IExtensionService, @IConfigurationService protected configurationService: IConfigurationService, - @IFileService private readonly fileService: IFileService, - @ILabelService private readonly labelService: ILabelService + @IFileService private readonly fileService: IFileService ) { super(themeService); @@ -96,9 +92,10 @@ export abstract class TitleControl extends Themable { this.registerListeners(); } - private registerListeners(): void { + protected registerListeners(): void { + + // Update actions toolbar when extension register that may contribute them this._register(this.extensionService.onDidRegisterExtensions(() => this.updateEditorActionsToolbar())); - this._register(this.labelService.onDidChangeFormatters(() => this.updateEditorLabels())); } protected abstract create(parent: HTMLElement): void; @@ -159,12 +156,12 @@ export abstract class TitleControl extends Themable { } private actionViewItemProvider(action: IAction): IActionViewItem | undefined { - const activeControl = this.group.activeControl; + const activeEditorPane = this.group.activeEditorPane; // Check Active Editor let actionViewItem: IActionViewItem | undefined = undefined; - if (activeControl instanceof BaseEditor) { - actionViewItem = activeControl.getActionViewItem(action); + if (activeEditorPane instanceof BaseEditor) { + actionViewItem = activeEditorPane.getActionViewItem(action); } // Check extensions @@ -226,9 +223,9 @@ export abstract class TitleControl extends Themable { this.editorPinnedContext.set(this.group.activeEditor ? this.group.isPinned(this.group.activeEditor) : false); // Editor actions require the editor control to be there, so we retrieve it via service - const activeControl = this.group.activeControl; - if (activeControl instanceof BaseEditor) { - const codeEditor = getCodeEditor(activeControl.getControl()); + const activeEditorPane = this.group.activeEditorPane; + if (activeEditorPane instanceof BaseEditor) { + const codeEditor = getCodeEditor(activeEditorPane.getControl()); const scopedContextKeyService = codeEditor?.invokeWithinContext(accessor => accessor.get(IContextKeyService)) || this.contextKeyService; const titleBarMenu = this.menuService.createMenu(MenuId.EditorTitle, scopedContextKeyService); this.editorToolBarMenuDisposables.add(titleBarMenu); @@ -389,7 +386,7 @@ export abstract class TitleControl extends Themable { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { // Drag Feedback const dragImageBackground = theme.getColor(listActiveSelectionBackground); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsAlerts.ts b/src/vs/workbench/browser/parts/notifications/notificationsAlerts.ts index 22b683d8c68..86bf82ce942 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsAlerts.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsAlerts.ts @@ -5,7 +5,7 @@ import { alert } from 'vs/base/browser/ui/aria/aria'; import { localize } from 'vs/nls'; -import { INotificationViewItem, INotificationsModel, NotificationChangeType, INotificationChangeEvent, NotificationViewItemLabelKind } from 'vs/workbench/common/notifications'; +import { INotificationViewItem, INotificationsModel, NotificationChangeType, INotificationChangeEvent, NotificationViewItemContentChangeKind } from 'vs/workbench/common/notifications'; import { Disposable } from 'vs/base/common/lifecycle'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Severity } from 'vs/platform/notification/common/notification'; @@ -45,9 +45,9 @@ export class NotificationsAlerts extends Disposable { private triggerAriaAlert(notifiation: INotificationViewItem): void { - // Trigger the alert again whenever the label changes - const listener = notifiation.onDidChangeLabel(e => { - if (e.kind === NotificationViewItemLabelKind.MESSAGE) { + // Trigger the alert again whenever the message changes + const listener = notifiation.onDidChangeContent(e => { + if (e.kind === NotificationViewItemContentChangeKind.MESSAGE) { this.doTriggerAriaAlert(notifiation); } }); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts index c37af327a36..8d245dce1cf 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts @@ -5,9 +5,9 @@ import 'vs/css!./media/notificationsCenter'; import 'vs/css!./media/notificationsActions'; -import { Themable, NOTIFICATIONS_BORDER, NOTIFICATIONS_CENTER_HEADER_FOREGROUND, NOTIFICATIONS_CENTER_HEADER_BACKGROUND, NOTIFICATIONS_CENTER_BORDER } from 'vs/workbench/common/theme'; -import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; -import { INotificationsModel, INotificationChangeEvent, NotificationChangeType } from 'vs/workbench/common/notifications'; +import { NOTIFICATIONS_BORDER, NOTIFICATIONS_CENTER_HEADER_FOREGROUND, NOTIFICATIONS_CENTER_HEADER_BACKGROUND, NOTIFICATIONS_CENTER_BORDER } from 'vs/workbench/common/theme'; +import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector, Themable } from 'vs/platform/theme/common/themeService'; +import { INotificationsModel, INotificationChangeEvent, NotificationChangeType, NotificationViewItemContentChangeKind } from 'vs/workbench/common/notifications'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { Emitter } from 'vs/base/common/event'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -177,7 +177,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente let focusEditor = false; - // Update notifications list based on event + // Update notifications list based on event kind const [notificationsList, notificationsCenterContainer] = assertAllDefined(this.notificationsList, this.notificationsCenterContainer); switch (e.kind) { case NotificationChangeType.ADD: @@ -185,6 +185,22 @@ export class NotificationsCenter extends Themable implements INotificationsCente e.item.updateVisibility(true); break; case NotificationChangeType.CHANGE: + // Handle content changes + // - actions: re-draw to properly show them + // - message: update notification height unless collapsed + switch (e.detail) { + case NotificationViewItemContentChangeKind.ACTIONS: + notificationsList.updateNotificationsList(e.index, 1, [e.item]); + break; + case NotificationViewItemContentChangeKind.MESSAGE: + if (e.item.expanded) { + notificationsList.updateNotificationHeight(e.item); + } + break; + } + break; + case NotificationChangeType.EXPAND_COLLAPSE: + // Re-draw entire item when expansion changes to reveal or hide details notificationsList.updateNotificationsList(e.index, 1, [e.item]); break; case NotificationChangeType.REMOVE: @@ -300,7 +316,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const notificationBorderColor = theme.getColor(NOTIFICATIONS_BORDER); if (notificationBorderColor) { collector.addRule(`.monaco-workbench > .notifications-center .notifications-list-container .monaco-list-row[data-last-element="false"] > .notification-list-item { border-bottom: 1px solid ${notificationBorderColor}; }`); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsList.ts b/src/vs/workbench/browser/parts/notifications/notificationsList.ts index 2d585698cb9..ec2e92ca08f 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsList.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsList.ts @@ -8,8 +8,8 @@ import { addClass, isAncestor, trackFocus } from 'vs/base/browser/dom'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IListOptions } from 'vs/base/browser/ui/list/listWidget'; -import { Themable, NOTIFICATIONS_LINKS, NOTIFICATIONS_BACKGROUND, NOTIFICATIONS_FOREGROUND, NOTIFICATIONS_ERROR_ICON_FOREGROUND, NOTIFICATIONS_WARNING_ICON_FOREGROUND, NOTIFICATIONS_INFO_ICON_FOREGROUND } from 'vs/workbench/common/theme'; -import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { NOTIFICATIONS_LINKS, NOTIFICATIONS_BACKGROUND, NOTIFICATIONS_FOREGROUND, NOTIFICATIONS_ERROR_ICON_FOREGROUND, NOTIFICATIONS_WARNING_ICON_FOREGROUND, NOTIFICATIONS_INFO_ICON_FOREGROUND } from 'vs/workbench/common/theme'; +import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector, Themable } from 'vs/platform/theme/common/themeService'; import { contrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; import { INotificationViewItem } from 'vs/workbench/common/notifications'; import { NotificationsListDelegate, NotificationRenderer } from 'vs/workbench/browser/parts/notifications/notificationsViewer'; @@ -21,6 +21,7 @@ import { assertIsDefined, assertAllDefined } from 'vs/base/common/types'; export class NotificationsList extends Themable { private listContainer: HTMLElement | undefined; private list: WorkbenchList | undefined; + private listDelegate: NotificationsListDelegate | undefined; private viewModel: INotificationViewItem[]; private isVisible: boolean | undefined; @@ -73,11 +74,12 @@ export class NotificationsList extends Themable { const renderer = this.instantiationService.createInstance(NotificationRenderer, actionRunner); // List + const listDelegate = this.listDelegate = new NotificationsListDelegate(this.listContainer); const list = this.list = >this._register(this.instantiationService.createInstance( WorkbenchList, 'NotificationsList', this.listContainer, - new NotificationsListDelegate(this.listContainer), + listDelegate, [renderer], { ...this.options, @@ -123,7 +125,7 @@ export class NotificationsList extends Themable { // Only allow for focus in notifications, as the // selection is too strong over the contents of // the notification - this._register(list.onSelectionChange(e => { + this._register(list.onDidChangeSelection(e => { if (e.indexes.length > 0) { list.setSelection([]); } @@ -186,6 +188,17 @@ export class NotificationsList extends Themable { } } + updateNotificationHeight(item: INotificationViewItem): void { + const index = this.viewModel.indexOf(item); + if (index === -1) { + return; + } + + const [list, listDelegate] = assertAllDefined(this.list, this.listDelegate); + list.updateElementHeight(index, listDelegate.getHeight(item)); + list.layout(); + } + hide(): void { if (!this.isVisible || !this.list) { return; // already hidden @@ -250,7 +263,7 @@ export class NotificationsList extends Themable { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const linkColor = theme.getColor(NOTIFICATIONS_LINKS); if (linkColor) { collector.addRule(`.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-message a { color: ${linkColor}; }`); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts index ecee706fcb9..3fd8e6d8e11 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts @@ -4,15 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/notificationsToasts'; -import { INotificationsModel, NotificationChangeType, INotificationChangeEvent, INotificationViewItem, NotificationViewItemLabelKind } from 'vs/workbench/common/notifications'; +import { INotificationsModel, NotificationChangeType, INotificationChangeEvent, INotificationViewItem, NotificationViewItemContentChangeKind } from 'vs/workbench/common/notifications'; import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { addClass, removeClass, isAncestor, addDisposableListener, EventType, Dimension } from 'vs/base/browser/dom'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { NotificationsList } from 'vs/workbench/browser/parts/notifications/notificationsList'; import { Event, Emitter } from 'vs/base/common/event'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; -import { Themable, NOTIFICATIONS_TOAST_BORDER, NOTIFICATIONS_BACKGROUND } from 'vs/workbench/common/theme'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { NOTIFICATIONS_TOAST_BORDER, NOTIFICATIONS_BACKGROUND } from 'vs/workbench/common/theme'; +import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { NotificationsToastsVisibleContext, INotificationsToastController } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; @@ -196,19 +196,24 @@ export class NotificationsToasts extends Themable implements INotificationsToast // the height computation takes the content of it into account! this.layoutContainer(maxDimensions.height); - // Update when item height changes due to expansion + // Re-draw entire item when expansion changes to reveal or hide details itemDisposables.add(item.onDidChangeExpansion(() => { notificationList.updateNotificationsList(0, 1, [item]); })); - // Update when item height potentially changes due to label changes - itemDisposables.add(item.onDidChangeLabel(e => { - if (!item.expanded) { - return; // dynamic height only applies to expanded notifications - } - - if (e.kind === NotificationViewItemLabelKind.ACTIONS || e.kind === NotificationViewItemLabelKind.MESSAGE) { - notificationList.updateNotificationsList(0, 1, [item]); + // Handle content changes + // - actions: re-draw to properly show them + // - message: update notification height unless collapsed + itemDisposables.add(item.onDidChangeContent(e => { + switch (e.kind) { + case NotificationViewItemContentChangeKind.ACTIONS: + notificationList.updateNotificationsList(0, 1, [item]); + break; + case NotificationViewItemContentChangeKind.MESSAGE: + if (item.expanded) { + notificationList.updateNotificationHeight(item); + } + break; } })); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index 41063188c35..873f43d437c 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -17,13 +17,12 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { dispose, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown'; -import { INotificationViewItem, NotificationViewItem, NotificationViewItemLabelKind, INotificationMessage, ChoiceAction } from 'vs/workbench/common/notifications'; +import { INotificationViewItem, NotificationViewItem, NotificationViewItemContentChangeKind, INotificationMessage, ChoiceAction } from 'vs/workbench/common/notifications'; import { ClearNotificationAction, ExpandNotificationAction, CollapseNotificationAction, ConfigureNotificationAction } from 'vs/workbench/browser/parts/notifications/notificationsActions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { Severity } from 'vs/platform/notification/common/notification'; import { isNonEmptyArray } from 'vs/base/common/arrays'; -import { startsWith } from 'vs/base/common/strings'; export class NotificationsListDelegate implements IListVirtualDelegate { @@ -46,14 +45,13 @@ export class NotificationsListDelegate implements IListVirtualDelegate actionHandler.callback(node.href))); + actionHandler.toDispose.add(addDisposableListener(anchor, EventType.CLICK, e => { + EventHelper.stop(e, true); + actionHandler.callback(node.href); + })); } messageContainer.appendChild(anchor); @@ -331,16 +332,19 @@ export class NotificationTemplateRenderer extends Disposable { // Progress this.renderProgress(notification); - // Label Change Events - this.inputDisposables.add(notification.onDidChangeLabel(event => { + // Label Change Events that we can handle directly + // (changes to actions require an entire redraw of + // the notification because it has an impact on + // epxansion state) + this.inputDisposables.add(notification.onDidChangeContent(event => { switch (event.kind) { - case NotificationViewItemLabelKind.SEVERITY: + case NotificationViewItemContentChangeKind.SEVERITY: this.renderSeverity(notification); break; - case NotificationViewItemLabelKind.PROGRESS: + case NotificationViewItemContentChangeKind.PROGRESS: this.renderProgress(notification); break; - case NotificationViewItemLabelKind.MESSAGE: + case NotificationViewItemContentChangeKind.MESSAGE: this.renderMessage(notification); break; } diff --git a/src/vs/workbench/browser/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index a5b9004ca72..3e56ac8a370 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -12,19 +12,19 @@ z-index: initial; } -.monaco-workbench .part.panel .title { +.monaco-workbench .part.panel .composite.title { height: 35px; display: flex; flex-direction: row; justify-content: space-between; } -.monaco-workbench .part.panel.bottom .title { +.monaco-workbench .part.panel.bottom .composite.title { border-top-width: 1px; border-top-style: solid; } -.monaco-workbench.noeditorarea .part.panel.bottom .title { +.monaco-workbench.noeditorarea .part.panel.bottom .composite.title { border-top-width: 0; /* no border when editor area is hiden */ } @@ -46,13 +46,13 @@ border-right-width: 0; /* no border when editor area is hiden */ } -.monaco-workbench .part.panel > .title > .title-actions .monaco-action-bar .action-item .action-label { +.monaco-workbench .part.panel > .composite.title > .title-actions .monaco-action-bar .action-item .action-label { outline-offset: -2px; } /** Panel Switcher */ -.monaco-workbench .part.panel > .title > .panel-switcher-container.composite-bar > .monaco-action-bar .action-label.codicon-more { +.monaco-workbench .part.panel > .composite.title > .panel-switcher-container.composite-bar > .monaco-action-bar .action-label.codicon-more { display: flex; align-items: center; justify-content: center; @@ -66,16 +66,16 @@ width: 100px; } -.monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar { +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar { line-height: 27px; /* matches panel titles in settings */ height: 35px; } -.monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:first-child { +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item:first-child { padding-left: 12px; } -.monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item { +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item { text-transform: uppercase; padding-left: 10px; padding-right: 10px; @@ -85,24 +85,64 @@ display: flex; } -.monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item .action-label{ +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item::before, +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item::after { + content: ''; + width: 2px; + display: block; + background-color: var(--insert-border-color); + opacity: 0; + transition-property: opacity; + transition-duration: 0ms; + transition-delay: 100ms; +} + +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item::before { + margin-left: -11px; + margin-right: 9px; +} + +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item::after { + margin-right: -11px; + margin-left: 9px; +} + +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item:last-of-type::after { + margin-right: -10px; + margin-left: 8px; +} + +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item.right::before, +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item.left::after, +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item.left::before, +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item.right::after { + transition-delay: 0s; +} + +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item.left::before, +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item.right::after, +.monaco-workbench .part.panel > .composite.title.dragged-over > .panel-switcher-container > .monaco-action-bar .action-item:last-of-type::after { + opacity: 1; +} + +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item .action-label{ margin-right: 0; } -.monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:last-child { +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item:last-child { padding-right: 10px; } -.monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item.checked .action-label { +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item.checked .action-label { border-bottom: 1px solid; margin-right: 0; } -.monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar .badge { +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .badge { margin-left: 8px; } -.monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar .badge .badge-content { +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .badge .badge-content { padding: 3px 5px; border-radius: 11px; font-size: 11px; diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index 7cb981e9583..1501c87bcce 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -74,7 +74,6 @@ class FocusPanelAction extends Action { // Show panel if (!this.layoutService.isVisible(Parts.PANEL_PART)) { this.layoutService.setPanelHidden(false); - return; } // Focus into active panel diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index a73f42b53f0..666c59c1855 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -19,7 +19,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ClosePanelAction, PanelActivityAction, ToggleMaximizedPanelAction, TogglePanelAction, PlaceHolderPanelActivityAction, PlaceHolderToggleCompositePinnedAction, PositionPanelActionConfigs, SetPanelPositionAction } from 'vs/workbench/browser/parts/panel/panelActions'; -import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_DRAG_AND_DROP_BACKGROUND, PANEL_INPUT_BORDER } from 'vs/workbench/common/theme'; import { activeContrastBorder, focusBorder, contrastBorder, editorBackground, badgeBackground, badgeForeground } from 'vs/platform/theme/common/colorRegistry'; import { CompositeBar, ICompositeBarItem, CompositeDragAndDrop } from 'vs/workbench/browser/parts/compositeBar'; @@ -37,6 +37,8 @@ import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExte import { MenuId } from 'vs/platform/actions/common/actions'; import { ViewMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; +import { Before2D } from 'vs/workbench/browser/dnd'; interface ICachedPanel { id: string; @@ -104,6 +106,7 @@ export class PanelPart extends CompositePart implements IPanelService { @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IExtensionService private readonly extensionService: IExtensionService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService ) { super( notificationService, @@ -125,6 +128,7 @@ export class PanelPart extends CompositePart implements IPanelService { ); this.panelRegistry = Registry.as(PanelExtensions.Panels); + storageKeysSyncRegistryService.registerStorageKey({ key: PanelPart.PINNED_PANELS, version: 1 }); this.compositeBar = this._register(this.instantiationService.createInstance(CompositeBar, this.getCachedPanels(), { icon: false, @@ -145,12 +149,11 @@ export class PanelPart extends CompositePart implements IPanelService { hidePart: () => this.layoutService.setPanelHidden(true), dndHandler: new CompositeDragAndDrop(this.viewDescriptorService, ViewContainerLocation.Panel, (id: string, focus?: boolean) => this.openPanel(id, focus) as Promise, - (from: string, to: string) => this.compositeBar.move(from, to), - () => this.getPinnedPanels().map(p => p.id) + (from: string, to: string, before?: Before2D) => this.compositeBar.move(from, to, before?.horizontallyBefore) ), compositeSize: 0, overflowActionSize: 44, - colors: (theme: ITheme) => ({ + colors: (theme: IColorTheme) => ({ activeBackgroundColor: theme.getColor(PANEL_BACKGROUND), // Background color for overflow action inactiveBackgroundColor: theme.getColor(PANEL_BACKGROUND), // Background color for overflow action activeBorderBottomColor: theme.getColor(PANEL_ACTIVE_TITLE_BORDER), @@ -633,7 +636,7 @@ export class PanelPart extends CompositePart implements IPanelService { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { // Panel Background: since panels can host editors, we apply a background rule if the panel background // color is different from the editor background color. This is a bit of a hack though. The better way diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts deleted file mode 100644 index 63c2009ec83..00000000000 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ /dev/null @@ -1,265 +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 { Component } from 'vs/workbench/common/component'; -import { IQuickInputService, IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInputButton, IInputBox, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { inputBackground, inputForeground, inputBorder, inputValidationInfoBackground, inputValidationInfoForeground, inputValidationInfoBorder, inputValidationWarningBackground, inputValidationWarningForeground, inputValidationWarningBorder, inputValidationErrorBackground, inputValidationErrorForeground, inputValidationErrorBorder, badgeBackground, badgeForeground, contrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, progressBarBackground, widgetShadow, listFocusForeground, listFocusBackground, activeContrastBorder, pickerGroupBorder, pickerGroupForeground } from 'vs/platform/theme/common/colorRegistry'; -import { QUICK_INPUT_BACKGROUND, QUICK_INPUT_FOREGROUND } from 'vs/workbench/common/theme'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { CLOSE_ON_FOCUS_LOST_CONFIG } from 'vs/workbench/browser/quickopen'; -import { computeStyles } from 'vs/platform/theme/common/styler'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { ICommandAndKeybindingRule, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { inQuickOpenContext, InQuickOpenContextKey } from 'vs/workbench/browser/parts/quickopen/quickopen'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { QuickInputController, IQuickInputStyles } from 'vs/base/parts/quickinput/browser/quickInput'; -import { WorkbenchList } from 'vs/platform/list/browser/listService'; -import { List, IListOptions } from 'vs/base/browser/ui/list/listWidget'; -import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; - -export class QuickInputService extends Component implements IQuickInputService { - - public _serviceBrand: undefined; - - public backButton: IQuickInputButton; - - private static readonly ID = 'workbench.component.quickinput'; - - - private inQuickOpenWidgets: Record = {}; - private inQuickOpenContext: IContextKey; - private contexts: Map> = new Map(); - private controller: QuickInputController; - - constructor( - @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IQuickOpenService private readonly quickOpenService: IQuickOpenService, - @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, - @IKeybindingService private readonly keybindingService: IKeybindingService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IThemeService themeService: IThemeService, - @IStorageService storageService: IStorageService, - @IAccessibilityService private readonly accessibilityService: IAccessibilityService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService - ) { - super(QuickInputService.ID, themeService, storageService); - this.inQuickOpenContext = InQuickOpenContextKey.bindTo(contextKeyService); - this._register(this.quickOpenService.onShow(() => this.inQuickOpen('quickOpen', true))); - this._register(this.quickOpenService.onHide(() => this.inQuickOpen('quickOpen', false))); - this.controller = new QuickInputController({ - idPrefix: 'quickInput_', // Constant since there is still only one. - container: this.layoutService.getWorkbenchElement(), - ignoreFocusOut: () => this.environmentService.args['sticky-quickopen'] || !this.configurationService.getValue(CLOSE_ON_FOCUS_LOST_CONFIG), - isScreenReaderOptimized: () => this.accessibilityService.isScreenReaderOptimized(), - backKeybindingLabel: () => this.keybindingService.lookupKeybinding(QuickPickBack.id)?.getLabel() || undefined, - setContextKey: (id?: string) => this.setContextKey(id), - returnFocus: () => this.editorGroupService.activeGroup.focus(), - createList: ( - user: string, - container: HTMLElement, - delegate: IListVirtualDelegate, - renderers: IListRenderer[], - options: IListOptions, - ) => this.instantiationService.createInstance(WorkbenchList, user, container, delegate, renderers, options) as List, - styles: this.computeStyles(), - }); - this.backButton = this.controller.backButton; - this._register(this.layoutService.onLayout(dimension => this.controller.layout(dimension, this.layoutService.getTitleBarOffset()))); - this.controller.layout(this.layoutService.dimension, this.layoutService.getTitleBarOffset()); - this._register(this.quickOpenService.onShow(() => this.controller.hide(true))); - this._register(this.controller.onShow(() => { - this.quickOpenService.close(); - this.inQuickOpen('quickInput', true); - this.resetContextKeys(); - })); - this._register(this.controller.onHide(() => { - this.inQuickOpen('quickInput', false); - this.resetContextKeys(); - })); - } - - private inQuickOpen(widget: 'quickInput' | 'quickOpen', open: boolean) { - if (open) { - this.inQuickOpenWidgets[widget] = true; - } else { - delete this.inQuickOpenWidgets[widget]; - } - if (Object.keys(this.inQuickOpenWidgets).length) { - if (!this.inQuickOpenContext.get()) { - this.inQuickOpenContext.set(true); - } - } else { - if (this.inQuickOpenContext.get()) { - this.inQuickOpenContext.reset(); - } - } - } - - private setContextKey(id?: string) { - let key: IContextKey | undefined; - if (id) { - key = this.contexts.get(id); - if (!key) { - key = new RawContextKey(id, false) - .bindTo(this.contextKeyService); - this.contexts.set(id, key); - } - } - - if (key && key.get()) { - return; // already active context - } - - this.resetContextKeys(); - - if (key) { - key.set(true); - } - } - - private resetContextKeys() { - this.contexts.forEach(context => { - if (context.get()) { - context.reset(); - } - }); - } - - pick>(picks: Promise[]> | QuickPickInput[], options: O = {}, token: CancellationToken = CancellationToken.None): Promise { - return this.controller.pick(picks, options, token); - } - - input(options: IInputOptions = {}, token: CancellationToken = CancellationToken.None): Promise { - return this.controller.input(options, token); - } - - createQuickPick(): IQuickPick { - return this.controller.createQuickPick(); - } - - createInputBox(): IInputBox { - return this.controller.createInputBox(); - } - - focus() { - this.controller.focus(); - } - - toggle() { - this.controller.toggle(); - } - - navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration) { - this.controller.navigate(next, quickNavigate); - } - - accept() { - return this.controller.accept(); - } - - back() { - return this.controller.back(); - } - - cancel() { - return this.controller.cancel(); - } - - protected updateStyles() { - this.controller.applyStyles(this.computeStyles()); - } - - private computeStyles(): IQuickInputStyles { - return { - widget: { - titleColor: { dark: 'rgba(255, 255, 255, 0.105)', light: 'rgba(0,0,0,.06)', hc: 'black' }[this.theme.type], // TODO - ...computeStyles(this.theme, { - quickInputBackground: QUICK_INPUT_BACKGROUND, - quickInputForeground: QUICK_INPUT_FOREGROUND, - contrastBorder, - widgetShadow, - }), - }, - inputBox: computeStyles(this.theme, { - inputForeground, - inputBackground, - inputBorder, - inputValidationInfoBackground, - inputValidationInfoForeground, - inputValidationInfoBorder, - inputValidationWarningBackground, - inputValidationWarningForeground, - inputValidationWarningBorder, - inputValidationErrorBackground, - inputValidationErrorForeground, - inputValidationErrorBorder, - }), - countBadge: computeStyles(this.theme, { - badgeBackground, - badgeForeground, - badgeBorder: contrastBorder - }), - button: computeStyles(this.theme, { - buttonForeground, - buttonBackground, - buttonHoverBackground, - buttonBorder: contrastBorder - }), - progressBar: computeStyles(this.theme, { - progressBarBackground - }), - list: computeStyles(this.theme, { - listBackground: QUICK_INPUT_BACKGROUND, - // Look like focused when inactive. - listInactiveFocusForeground: listFocusForeground, - listInactiveFocusBackground: listFocusBackground, - listFocusOutline: activeContrastBorder, - listInactiveFocusOutline: activeContrastBorder, - pickerGroupBorder, - pickerGroupForeground, - }), - }; - } -} - -export const QuickPickManyToggle: ICommandAndKeybindingRule = { - id: 'workbench.action.quickPickManyToggle', - weight: KeybindingWeight.WorkbenchContrib, - when: inQuickOpenContext, - primary: 0, - handler: accessor => { - const quickInputService = accessor.get(IQuickInputService); - quickInputService.toggle(); - } -}; - -export const QuickPickBack: ICommandAndKeybindingRule = { - id: 'workbench.action.quickInputBack', - weight: KeybindingWeight.WorkbenchContrib + 50, - when: inQuickOpenContext, - primary: 0, - win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, - mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS }, - linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS }, - handler: accessor => { - const quickInputService = accessor.get(IQuickInputService); - quickInputService.back(); - } -}; - -registerSingleton(IQuickInputService, QuickInputService, true); diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputActions.ts b/src/vs/workbench/browser/parts/quickinput/quickInputActions.ts deleted file mode 100644 index a41fff2101b..00000000000 --- a/src/vs/workbench/browser/parts/quickinput/quickInputActions.ts +++ /dev/null @@ -1,10 +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 { QuickPickManyToggle, QuickPickBack } from 'vs/workbench/browser/parts/quickinput/quickInput'; -import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; - -KeybindingsRegistry.registerCommandAndKeybindingRule(QuickPickManyToggle); -KeybindingsRegistry.registerCommandAndKeybindingRule(QuickPickBack); diff --git a/src/vs/workbench/browser/parts/quickopen/media/quickopen.css b/src/vs/workbench/browser/parts/quickopen/media/quickopen.css deleted file mode 100644 index edfbcd3a213..00000000000 --- a/src/vs/workbench/browser/parts/quickopen/media/quickopen.css +++ /dev/null @@ -1,15 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.vs .monaco-workbench .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.none, -.vs-dark .monaco-workbench .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.none { - width: 16px; - background: none; -} - -.monaco-workbench .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.dirty { - width: 14px; - height: 18px; -} diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenActions.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenActions.ts deleted file mode 100644 index 0a8187a8759..00000000000 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenActions.ts +++ /dev/null @@ -1,101 +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 { Registry } from 'vs/platform/registry/common/platform'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; -import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; -import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; -import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; -import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { RemoveFromEditorHistoryAction } from 'vs/workbench/browser/parts/quickopen/quickOpenController'; -import { QuickOpenSelectNextAction, QuickOpenSelectPreviousAction, inQuickOpenContext, getQuickNavigateHandler, QuickOpenNavigateNextAction, QuickOpenNavigatePreviousAction, defaultQuickOpenContext, QUICKOPEN_ACTION_ID, QUICKOPEN_ACION_LABEL } from 'vs/workbench/browser/parts/quickopen/quickopen'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'workbench.action.closeQuickOpen', - weight: KeybindingWeight.WorkbenchContrib, - when: inQuickOpenContext, - primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], - handler: accessor => { - const quickOpenService = accessor.get(IQuickOpenService); - quickOpenService.close(); - const quickInputService = accessor.get(IQuickInputService); - return quickInputService.cancel(); - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'workbench.action.acceptSelectedQuickOpenItem', - weight: KeybindingWeight.WorkbenchContrib, - when: inQuickOpenContext, - primary: 0, - handler: accessor => { - const quickOpenService = accessor.get(IQuickOpenService); - quickOpenService.accept(); - const quickInputService = accessor.get(IQuickInputService); - return quickInputService.accept(); - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'workbench.action.focusQuickOpen', - weight: KeybindingWeight.WorkbenchContrib, - when: inQuickOpenContext, - primary: 0, - handler: accessor => { - const quickOpenService = accessor.get(IQuickOpenService); - quickOpenService.focus(); - const quickInputService = accessor.get(IQuickInputService); - quickInputService.focus(); - } -}); - -const registry = Registry.as(ActionExtensions.WorkbenchActions); - -const globalQuickOpenKeybinding = { primary: KeyMod.CtrlCmd | KeyCode.KEY_P, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_E], mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_P, secondary: undefined } }; - -KeybindingsRegistry.registerKeybindingRule({ - id: QUICKOPEN_ACTION_ID, - weight: KeybindingWeight.WorkbenchContrib, - when: undefined, - primary: globalQuickOpenKeybinding.primary, - secondary: globalQuickOpenKeybinding.secondary, - mac: globalQuickOpenKeybinding.mac -}); - -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { id: QUICKOPEN_ACTION_ID, title: { value: QUICKOPEN_ACION_LABEL, original: 'Go to File...' } } -}); - -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenSelectNextAction, QuickOpenSelectNextAction.ID, QuickOpenSelectNextAction.LABEL, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_N } }, inQuickOpenContext, KeybindingWeight.WorkbenchContrib + 50), 'Select Next in Quick Open'); -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenSelectPreviousAction, QuickOpenSelectPreviousAction.ID, QuickOpenSelectPreviousAction.LABEL, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_P } }, inQuickOpenContext, KeybindingWeight.WorkbenchContrib + 50), 'Select Previous in Quick Open'); -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenNavigateNextAction, QuickOpenNavigateNextAction.ID, QuickOpenNavigateNextAction.LABEL), 'Navigate Next in Quick Open'); -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenNavigatePreviousAction, QuickOpenNavigatePreviousAction.ID, QuickOpenNavigatePreviousAction.LABEL), 'Navigate Previous in Quick Open'); -registry.registerWorkbenchAction(SyncActionDescriptor.create(RemoveFromEditorHistoryAction, RemoveFromEditorHistoryAction.ID, RemoveFromEditorHistoryAction.LABEL), 'Remove From History'); - -const quickOpenNavigateNextInFilePickerId = 'workbench.action.quickOpenNavigateNextInFilePicker'; -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: quickOpenNavigateNextInFilePickerId, - weight: KeybindingWeight.WorkbenchContrib + 50, - handler: getQuickNavigateHandler(quickOpenNavigateNextInFilePickerId, true), - when: defaultQuickOpenContext, - primary: globalQuickOpenKeybinding.primary, - secondary: globalQuickOpenKeybinding.secondary, - mac: globalQuickOpenKeybinding.mac -}); - -const quickOpenNavigatePreviousInFilePickerId = 'workbench.action.quickOpenNavigatePreviousInFilePicker'; -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: quickOpenNavigatePreviousInFilePickerId, - weight: KeybindingWeight.WorkbenchContrib + 50, - handler: getQuickNavigateHandler(quickOpenNavigatePreviousInFilePickerId, false), - when: defaultQuickOpenContext, - primary: globalQuickOpenKeybinding.primary | KeyMod.Shift, - secondary: [globalQuickOpenKeybinding.secondary[0] | KeyMod.Shift], - mac: { - primary: globalQuickOpenKeybinding.mac.primary | KeyMod.Shift, - secondary: undefined - } -}); diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts deleted file mode 100644 index 9c506768193..00000000000 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ /dev/null @@ -1,872 +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 'vs/css!./media/quickopen'; -import * as nls from 'vs/nls'; -import * as browser from 'vs/base/browser/browser'; -import * as strings from 'vs/base/common/strings'; -import { URI } from 'vs/base/common/uri'; -import * as resources from 'vs/base/common/resources'; -import * as types from 'vs/base/common/types'; -import { Action } from 'vs/base/common/actions'; -import { IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; -import { Mode, IEntryRunContext, IAutoFocus, IQuickNavigateConfiguration, IModel } from 'vs/base/parts/quickopen/common/quickOpen'; -import { QuickOpenEntry, QuickOpenModel, QuickOpenEntryGroup, QuickOpenItemAccessorClass } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { QuickOpenWidget, HideReason } from 'vs/base/parts/quickopen/browser/quickOpenWidget'; -import { ContributableActionProvider } from 'vs/workbench/browser/actions'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { EditorInput, IWorkbenchEditorConfiguration, IEditorInput } from 'vs/workbench/common/editor'; -import { Component } from 'vs/workbench/common/component'; -import { Event, Emitter } from 'vs/base/common/event'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { QuickOpenHandler, QuickOpenHandlerDescriptor, IQuickOpenRegistry, Extensions, EditorQuickOpenEntry, CLOSE_ON_FOCUS_LOST_CONFIG, SEARCH_EDITOR_HISTORY, PRESERVE_INPUT_CONFIG } from 'vs/workbench/browser/quickopen'; -import * as errors from 'vs/base/common/errors'; -import { IQuickOpenService, IShowOptions } from 'vs/platform/quickOpen/common/quickOpen'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { QUICK_INPUT_BACKGROUND, QUICK_INPUT_FOREGROUND } from 'vs/workbench/common/theme'; -import { attachQuickOpenStyler } from 'vs/platform/theme/common/styler'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IFileService } from 'vs/platform/files/common/files'; -import { scoreItem, ScorerCache, compareItemsByScore, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; -import { WorkbenchTree } from 'vs/platform/list/browser/listService'; -import { Schemas } from 'vs/base/common/network'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { Dimension, addClass } from 'vs/base/browser/dom'; -import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { ILabelService } from 'vs/platform/label/common/label'; -import { timeout } from 'vs/base/common/async'; -import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; -import { IStorageService } from 'vs/platform/storage/common/storage'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; - -const HELP_PREFIX = '?'; - -type ValueCallback = (value: T | Promise) => void; - -export class QuickOpenController extends Component implements IQuickOpenService { - - private static readonly MAX_SHORT_RESPONSE_TIME = 500; - private static readonly ID = 'workbench.component.quickopen'; - - _serviceBrand: undefined; - - private readonly _onShow: Emitter = this._register(new Emitter()); - readonly onShow: Event = this._onShow.event; - - private readonly _onHide: Emitter = this._register(new Emitter()); - readonly onHide: Event = this._onHide.event; - - private preserveInput: boolean | undefined; - private isQuickOpen: boolean | undefined; - private lastInputValue: string | undefined; - private lastSubmittedInputValue: string | undefined; - private quickOpenWidget: QuickOpenWidget | undefined; - private mapResolvedHandlersToPrefix: Map> = new Map(); - private mapContextKeyToContext: Map> = new Map(); - private handlerOnOpenCalled: Set = new Set(); - private promisesToCompleteOnHide: ValueCallback[] = []; - private previousActiveHandlerDescriptor: QuickOpenHandlerDescriptor | null | undefined; - private actionProvider = new ContributableActionProvider(); - private closeOnFocusLost: boolean | undefined; - private searchInEditorHistory: boolean | undefined; - private editorHistoryHandler: EditorHistoryHandler; - private pendingGetResultsInvocation: CancellationTokenSource | null = null; - - constructor( - @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, - @INotificationService private readonly notificationService: INotificationService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IThemeService themeService: IThemeService, - @IStorageService storageService: IStorageService - ) { - super(QuickOpenController.ID, themeService, storageService); - - this.editorHistoryHandler = this.instantiationService.createInstance(EditorHistoryHandler); - - this.updateConfiguration(); - - this.registerListeners(); - } - - private registerListeners(): void { - this._register(this.configurationService.onDidChangeConfiguration(() => this.updateConfiguration())); - this._register(this.layoutService.onPartVisibilityChange(() => this.positionQuickOpenWidget())); - this._register(browser.onDidChangeZoomLevel(() => this.positionQuickOpenWidget())); - this._register(this.layoutService.onLayout(dimension => this.layout(dimension))); - } - - private updateConfiguration(): void { - if (this.environmentService.args['sticky-quickopen']) { - this.closeOnFocusLost = false; - } else { - this.closeOnFocusLost = this.configurationService.getValue(CLOSE_ON_FOCUS_LOST_CONFIG); - } - this.preserveInput = this.configurationService.getValue(PRESERVE_INPUT_CONFIG); - - this.searchInEditorHistory = this.configurationService.getValue(SEARCH_EDITOR_HISTORY); - } - - navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration): void { - if (this.quickOpenWidget) { - this.quickOpenWidget.navigate(next, quickNavigate); - } - } - - accept(): void { - if (this.quickOpenWidget && this.quickOpenWidget.isVisible()) { - this.quickOpenWidget.accept(); - } - } - - focus(): void { - if (this.quickOpenWidget && this.quickOpenWidget.isVisible()) { - this.quickOpenWidget.focus(); - } - } - - close(): void { - if (this.quickOpenWidget && this.quickOpenWidget.isVisible()) { - this.quickOpenWidget.hide(HideReason.CANCELED); - } - } - - private emitQuickOpenVisibilityChange(isVisible: boolean): void { - if (isVisible) { - this._onShow.fire(); - } else { - this._onHide.fire(); - } - } - - show(prefix?: string, options?: IShowOptions): Promise { - let quickNavigateConfiguration = options ? options.quickNavigateConfiguration : undefined; - let inputSelection = options ? options.inputSelection : undefined; - let autoFocus = options ? options.autoFocus : undefined; - - const promiseCompletedOnHide = new Promise(c => { - this.promisesToCompleteOnHide.push(c); - }); - - // Telemetry: log that quick open is shown and log the mode - const registry = Registry.as(Extensions.Quickopen); - const handlerDescriptor = (prefix ? registry.getQuickOpenHandler(prefix) : undefined) || registry.getDefaultQuickOpenHandler(); - - // Trigger onOpen - this.resolveHandler(handlerDescriptor); - - // Create upon first open - if (!this.quickOpenWidget) { - const quickOpenWidget: QuickOpenWidget = this.quickOpenWidget = this._register(new QuickOpenWidget( - this.layoutService.getWorkbenchElement(), - { - onOk: () => this.onOk(), - onCancel: () => { /* ignore */ }, - onType: (value: string) => this.onType(quickOpenWidget, value || ''), - onShow: () => this.handleOnShow(), - onHide: (reason) => this.handleOnHide(reason), - onFocusLost: () => !this.closeOnFocusLost - }, { - inputPlaceHolder: this.hasHandler(HELP_PREFIX) ? nls.localize('quickOpenInput', "Type '?' to get help on the actions you can take from here") : '', - keyboardSupport: false, - treeCreator: (container, config, opts) => this.instantiationService.createInstance(WorkbenchTree, container, config, opts) - })); - this._register(attachQuickOpenStyler(this.quickOpenWidget, this.themeService, { background: QUICK_INPUT_BACKGROUND, foreground: QUICK_INPUT_FOREGROUND })); - - const quickOpenContainer = this.quickOpenWidget.create(); - addClass(quickOpenContainer, 'show-file-icons'); - this.positionQuickOpenWidget(); - } - - // Layout - this.quickOpenWidget.layout(this.layoutService.dimension); - - // Show quick open with prefix or editor history - if (!this.quickOpenWidget.isVisible() || quickNavigateConfiguration) { - if (prefix) { - this.quickOpenWidget.show(prefix, { quickNavigateConfiguration, inputSelection, autoFocus }); - } else { - const editorHistory = this.getEditorHistoryWithGroupLabel(); - if (editorHistory.getEntries().length < 2) { - quickNavigateConfiguration = undefined; // If no entries can be shown, default to normal quick open mode - } - - // Compute auto focus - if (!autoFocus) { - if (!quickNavigateConfiguration) { - autoFocus = { autoFocusFirstEntry: true }; - } else { - const autoFocusFirstEntry = this.editorGroupService.activeGroup.count === 0; - autoFocus = { autoFocusFirstEntry, autoFocusSecondEntry: !autoFocusFirstEntry }; - } - } - - // Update context - const registry = Registry.as(Extensions.Quickopen); - this.setQuickOpenContextKey(registry.getDefaultQuickOpenHandler().contextKey); - if (this.preserveInput) { - this.quickOpenWidget.show(editorHistory, { value: this.lastSubmittedInputValue, quickNavigateConfiguration, autoFocus, inputSelection }); - } else { - this.quickOpenWidget.show(editorHistory, { quickNavigateConfiguration, autoFocus, inputSelection }); - } - } - } - - // Otherwise reset the widget to the prefix that is passed in - else { - this.quickOpenWidget.show(prefix || '', { inputSelection }); - } - - return promiseCompletedOnHide; - } - - private positionQuickOpenWidget(): void { - const titlebarOffset = this.layoutService.getTitleBarOffset(); - - if (this.quickOpenWidget) { - this.quickOpenWidget.getElement().style.top = `${titlebarOffset}px`; - } - } - - private handleOnShow(): void { - this.emitQuickOpenVisibilityChange(true); - } - - private handleOnHide(reason: HideReason): void { - - // Clear state - this.previousActiveHandlerDescriptor = null; - - // Cancel pending results calls - this.cancelPendingGetResultsInvocation(); - - // Pass to handlers - this.mapResolvedHandlersToPrefix.forEach((promise, prefix) => { - promise.then(handler => { - this.handlerOnOpenCalled.delete(prefix); - - handler.onClose(reason === HideReason.CANCELED); // Don't check if onOpen was called to preserve old behaviour for now - }); - }); - - // Complete promises that are waiting - while (this.promisesToCompleteOnHide.length) { - const callback = this.promisesToCompleteOnHide.pop(); - if (callback) { - callback(true); - } - } - - if (reason !== HideReason.FOCUS_LOST) { - this.editorGroupService.activeGroup.focus(); // focus back to editor group unless user clicked somewhere else - } - - // Reset context keys - this.resetQuickOpenContextKeys(); - - // Events - this.emitQuickOpenVisibilityChange(false); - } - - private cancelPendingGetResultsInvocation(): void { - if (this.pendingGetResultsInvocation) { - this.pendingGetResultsInvocation.cancel(); - this.pendingGetResultsInvocation.dispose(); - this.pendingGetResultsInvocation = null; - } - } - - private resetQuickOpenContextKeys(): void { - this.mapContextKeyToContext.forEach(context => context.reset()); - } - - private setQuickOpenContextKey(id?: string): void { - let key: IContextKey | undefined; - if (id) { - key = this.mapContextKeyToContext.get(id); - if (!key) { - key = new RawContextKey(id, false).bindTo(this.contextKeyService); - this.mapContextKeyToContext.set(id, key); - } - } - - if (key?.get()) { - return; // already active context - } - - this.resetQuickOpenContextKeys(); - - if (key) { - key.set(true); - } - } - - private hasHandler(prefix: string): boolean { - return !!Registry.as(Extensions.Quickopen).getQuickOpenHandler(prefix); - } - - private getEditorHistoryWithGroupLabel(): QuickOpenModel { - const entries: QuickOpenEntry[] = this.editorHistoryHandler.getResults(); - - // Apply label to first entry - if (entries.length > 0) { - entries[0] = new EditorHistoryEntryGroup(entries[0], nls.localize('historyMatches', "recently opened"), false); - } - - return new QuickOpenModel(entries, this.actionProvider); - } - - private onOk(): void { - if (this.isQuickOpen) { - this.lastSubmittedInputValue = this.lastInputValue; - } - } - - private onType(quickOpenWidget: QuickOpenWidget, value: string): void { - - // cancel any pending get results invocation and create new - this.cancelPendingGetResultsInvocation(); - const pendingResultsInvocationTokenSource = new CancellationTokenSource(); - const pendingResultsInvocationToken = pendingResultsInvocationTokenSource.token; - this.pendingGetResultsInvocation = pendingResultsInvocationTokenSource; - - // look for a handler - const registry = Registry.as(Extensions.Quickopen); - const handlerDescriptor = registry.getQuickOpenHandler(value); - const defaultHandlerDescriptor = registry.getDefaultQuickOpenHandler(); - const instantProgress = handlerDescriptor?.instantProgress; - const contextKey = handlerDescriptor ? handlerDescriptor.contextKey : defaultHandlerDescriptor.contextKey; - - // Reset Progress - if (!instantProgress) { - quickOpenWidget.getProgressBar().stop().hide(); - } - - // Reset Extra Class - quickOpenWidget.setExtraClass(null); - - // Update context - this.setQuickOpenContextKey(contextKey); - - // Remove leading and trailing whitespace - const trimmedValue = strings.trim(value); - - // If no value provided, default to editor history - if (!trimmedValue) { - - // Trigger onOpen - this.resolveHandler(handlerDescriptor || defaultHandlerDescriptor); - - quickOpenWidget.setInput(this.getEditorHistoryWithGroupLabel(), { autoFocusFirstEntry: true }); - - // If quickOpen entered empty we have to clear the prefill-cache - this.lastInputValue = ''; - this.isQuickOpen = true; - - return; - } - - let resultPromise: Promise; - let resultPromiseDone = false; - - if (handlerDescriptor) { - this.isQuickOpen = false; - resultPromise = this.handleSpecificHandler(quickOpenWidget, handlerDescriptor, value, pendingResultsInvocationToken); - } - - // Otherwise handle default handlers if no specific handler present - else { - this.isQuickOpen = true; - // Cache the value for prefilling the quickOpen next time is opened - this.lastInputValue = trimmedValue; - resultPromise = this.handleDefaultHandler(quickOpenWidget, defaultHandlerDescriptor, value, pendingResultsInvocationToken); - } - - // Remember as the active one - this.previousActiveHandlerDescriptor = handlerDescriptor; - - // Progress if task takes a long time - setTimeout(() => { - if (!resultPromiseDone && !pendingResultsInvocationToken.isCancellationRequested) { - quickOpenWidget.getProgressBar().infinite().show(); - } - }, instantProgress ? 0 : 800); - - // Promise done handling - resultPromise.then(() => { - resultPromiseDone = true; - - if (!pendingResultsInvocationToken.isCancellationRequested) { - quickOpenWidget.getProgressBar().hide(); - } - - pendingResultsInvocationTokenSource.dispose(); - }, (error: any) => { - resultPromiseDone = true; - - pendingResultsInvocationTokenSource.dispose(); - - errors.onUnexpectedError(error); - this.notificationService.error(types.isString(error) ? new Error(error) : error); - }); - } - - private async handleDefaultHandler(quickOpenWidget: QuickOpenWidget, handler: QuickOpenHandlerDescriptor, value: string, token: CancellationToken): Promise { - - // Fill in history results if matching and we are configured to search in history - let matchingHistoryEntries: QuickOpenEntry[]; - if (value && !this.searchInEditorHistory) { - matchingHistoryEntries = []; - } else { - matchingHistoryEntries = this.editorHistoryHandler.getResults(value, token); - } - - if (matchingHistoryEntries.length > 0) { - matchingHistoryEntries[0] = new EditorHistoryEntryGroup(matchingHistoryEntries[0], nls.localize('historyMatches', "recently opened"), false); - } - - // Resolve - const resolvedHandler = await this.resolveHandler(handler); - - const quickOpenModel = new QuickOpenModel(matchingHistoryEntries, this.actionProvider); - - let inputSet = false; - - // If we have matching entries from history we want to show them directly and not wait for the other results to come in - // This also applies when we used to have entries from a previous run and now there are no more history results matching - const previousInput = quickOpenWidget.getInput(); - const wasShowingHistory = previousInput?.entries?.some(e => e instanceof EditorHistoryEntry || e instanceof EditorHistoryEntryGroup); - if (wasShowingHistory || matchingHistoryEntries.length > 0) { - (async () => { - if (resolvedHandler.hasShortResponseTime()) { - await timeout(QuickOpenController.MAX_SHORT_RESPONSE_TIME); - } - - if (!token.isCancellationRequested && !inputSet) { - quickOpenWidget.setInput(quickOpenModel, { autoFocusFirstEntry: true }); - inputSet = true; - } - })(); - } - - // Get results - const result = await resolvedHandler.getResults(value, token); - if (!token.isCancellationRequested) { - - // now is the time to show the input if we did not have set it before - if (!inputSet) { - quickOpenWidget.setInput(quickOpenModel, { autoFocusFirstEntry: true }); - inputSet = true; - } - - // merge history and default handler results - const handlerResults = result?.entries || []; - this.mergeResults(quickOpenWidget, quickOpenModel, handlerResults, types.withNullAsUndefined(resolvedHandler.getGroupLabel())); - } - } - - private mergeResults(quickOpenWidget: QuickOpenWidget, quickOpenModel: QuickOpenModel, handlerResults: QuickOpenEntry[], groupLabel: string | undefined): void { - - // Remove results already showing by checking for a "resource" property - const mapEntryToResource = this.mapEntriesToResource(quickOpenModel); - const additionalHandlerResults: QuickOpenEntry[] = []; - for (const result of handlerResults) { - const resource = result.getResource(); - - if (!result.mergeWithEditorHistory() || !resource || !mapEntryToResource[resource.toString()]) { - additionalHandlerResults.push(result); - } - } - - // Show additional handler results below any existing results - if (additionalHandlerResults.length > 0) { - const autoFocusFirstEntry = (quickOpenModel.getEntries().length === 0); // the user might have selected another entry meanwhile in local history (see https://github.com/Microsoft/vscode/issues/20828) - const useTopBorder = quickOpenModel.getEntries().length > 0; - additionalHandlerResults[0] = new QuickOpenEntryGroup(additionalHandlerResults[0], groupLabel, useTopBorder); - quickOpenModel.addEntries(additionalHandlerResults); - quickOpenWidget.refresh(quickOpenModel, { autoFocusFirstEntry }); - } - - // Otherwise if no results are present (even from histoy) indicate this to the user - else if (quickOpenModel.getEntries().length === 0) { - quickOpenModel.addEntries([new PlaceholderQuickOpenEntry(nls.localize('noResultsFound1', "No results found"))]); - quickOpenWidget.refresh(quickOpenModel, { autoFocusFirstEntry: true }); - } - } - - private async handleSpecificHandler(quickOpenWidget: QuickOpenWidget, handlerDescriptor: QuickOpenHandlerDescriptor, value: string, token: CancellationToken): Promise { - const resolvedHandler = await this.resolveHandler(handlerDescriptor); - - // Remove handler prefix from search value - value = value.substr(handlerDescriptor.prefix.length); - - // Return early if the handler can not run in the current environment and inform the user - const canRun = resolvedHandler.canRun(); - if (types.isUndefinedOrNull(canRun) || (typeof canRun === 'boolean' && !canRun) || typeof canRun === 'string') { - const placeHolderLabel = (typeof canRun === 'string') ? canRun : nls.localize('canNotRunPlaceholder', "This quick open handler can not be used in the current context"); - - const model = new QuickOpenModel([new PlaceholderQuickOpenEntry(placeHolderLabel)], this.actionProvider); - this.showModel(quickOpenWidget, model, resolvedHandler.getAutoFocus(value, { model, quickNavigateConfiguration: quickOpenWidget.getQuickNavigateConfiguration() }), types.withNullAsUndefined(resolvedHandler.getAriaLabel())); - - return; - } - - // Support extra class from handler - const extraClass = resolvedHandler.getClass(); - if (extraClass) { - quickOpenWidget.setExtraClass(extraClass); - } - - // When handlers change, clear the result list first before loading the new results - if (this.previousActiveHandlerDescriptor !== handlerDescriptor) { - this.clearModel(quickOpenWidget); - } - - // Receive Results from Handler and apply - const result = await resolvedHandler.getResults(value, token); - if (!token.isCancellationRequested) { - if (!result || !result.entries.length) { - const model = new QuickOpenModel([new PlaceholderQuickOpenEntry(resolvedHandler.getEmptyLabel(value))]); - this.showModel(quickOpenWidget, model, resolvedHandler.getAutoFocus(value, { model, quickNavigateConfiguration: quickOpenWidget.getQuickNavigateConfiguration() }), types.withNullAsUndefined(resolvedHandler.getAriaLabel())); - } else { - this.showModel(quickOpenWidget, result, resolvedHandler.getAutoFocus(value, { model: result, quickNavigateConfiguration: quickOpenWidget.getQuickNavigateConfiguration() }), types.withNullAsUndefined(resolvedHandler.getAriaLabel())); - } - } - } - - private showModel(quickOpenWidget: QuickOpenWidget, model: IModel, autoFocus?: IAutoFocus, ariaLabel?: string): void { - - // If the given model is already set in the widget, refresh and return early - if (quickOpenWidget.getInput() === model) { - quickOpenWidget.refresh(model, autoFocus); - - return; - } - - // Otherwise just set it - quickOpenWidget.setInput(model, autoFocus, ariaLabel); - } - - private clearModel(quickOpenWidget: QuickOpenWidget): void { - this.showModel(quickOpenWidget, new QuickOpenModel(), undefined); - } - - private mapEntriesToResource(model: QuickOpenModel): { [resource: string]: QuickOpenEntry; } { - const entries = model.getEntries(); - const mapEntryToPath: { [path: string]: QuickOpenEntry; } = {}; - entries.forEach((entry: QuickOpenEntry) => { - const resource = entry.getResource(); - if (resource) { - mapEntryToPath[resource.toString()] = entry; - } - }); - - return mapEntryToPath; - } - - private async resolveHandler(handler: QuickOpenHandlerDescriptor): Promise { - let result = this.doResolveHandler(handler); - - const id = handler.getId(); - if (!this.handlerOnOpenCalled.has(id)) { - const original = result; - this.handlerOnOpenCalled.add(id); - result = original.then(resolved => { - this.mapResolvedHandlersToPrefix.set(id, original); - resolved.onOpen(); - - return resolved; - }); - - this.mapResolvedHandlersToPrefix.set(id, result); - } - - try { - return await result; - } catch (error) { - this.mapResolvedHandlersToPrefix.delete(id); - - throw new Error(`Unable to instantiate quick open handler ${handler.getId()}: ${JSON.stringify(error)}`); - } - } - - private doResolveHandler(handler: QuickOpenHandlerDescriptor): Promise { - const id = handler.getId(); - - // Return Cached - if (this.mapResolvedHandlersToPrefix.has(id)) { - return this.mapResolvedHandlersToPrefix.get(id)!; - } - - // Otherwise load and create - const result = Promise.resolve(handler.instantiate(this.instantiationService)); - this.mapResolvedHandlersToPrefix.set(id, result); - - return result; - } - - layout(dimension: Dimension): void { - if (this.quickOpenWidget) { - this.quickOpenWidget.layout(dimension); - } - } -} - -class PlaceholderQuickOpenEntry extends QuickOpenEntryGroup { - private placeHolderLabel: string; - - constructor(placeHolderLabel: string) { - super(); - - this.placeHolderLabel = placeHolderLabel; - } - - getLabel(): string { - return this.placeHolderLabel; - } -} - -class EditorHistoryHandler { - private scorerCache: ScorerCache; - - constructor( - @IHistoryService private readonly historyService: IHistoryService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IFileService private readonly fileService: IFileService - ) { - this.scorerCache = Object.create(null); - } - - getResults(searchValue?: string, token?: CancellationToken): QuickOpenEntry[] { - - // Massage search for scoring - const query = prepareQuery(searchValue || ''); - - // Just return all if we are not searching - const history = this.historyService.getHistory(); - if (!query.value) { - return history.map(input => this.instantiationService.createInstance(EditorHistoryEntry, input)); - } - - // Otherwise filter by search value and sort by score. Include matches on description - // in case the user is explicitly including path separators. - const accessor = query.containsPathSeparator ? MatchOnDescription : DoNotMatchOnDescription; - return history - - // For now, only support to match on inputs that provide resource information - .filter(input => { - let resource: URI | undefined; - if (input instanceof EditorInput) { - resource = resourceForEditorHistory(input, this.fileService); - } else { - resource = (input as IResourceInput).resource; - } - - return !!resource; - }) - - // Conver to quick open entries - .map(input => this.instantiationService.createInstance(EditorHistoryEntry, input)) - - // Make sure the search value is matching - .filter(e => { - const itemScore = scoreItem(e, query, false, accessor, this.scorerCache); - if (!itemScore.score) { - return false; - } - - e.setHighlights(itemScore.labelMatch || [], itemScore.descriptionMatch); - - return true; - }) - - // Sort by score and provide a fallback sorter that keeps the - // recency of items in case the score for items is the same - .sort((e1, e2) => compareItemsByScore(e1, e2, query, false, accessor, this.scorerCache, () => -1)); - } -} - -class EditorHistoryItemAccessorClass extends QuickOpenItemAccessorClass { - - constructor(private allowMatchOnDescription: boolean) { - super(); - } - - getItemDescription(entry: QuickOpenEntry): string | null { - return this.allowMatchOnDescription ? types.withUndefinedAsNull(entry.getDescription()) : null; - } -} - -const MatchOnDescription = new EditorHistoryItemAccessorClass(true); -const DoNotMatchOnDescription = new EditorHistoryItemAccessorClass(false); - -export class EditorHistoryEntryGroup extends QuickOpenEntryGroup { - // Marker class -} - -export class EditorHistoryEntry extends EditorQuickOpenEntry { - private input: IEditorInput | IResourceInput; - private resource: URI | undefined; - private label: string; - private description?: string; - private icon: string; - - constructor( - input: IEditorInput | IResourceInput, - @IEditorService editorService: IEditorService, - @IModeService private readonly modeService: IModeService, - @IModelService private readonly modelService: IModelService, - @ITextFileService private readonly textFileService: ITextFileService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @ILabelService labelService: ILabelService, - @IFileService fileService: IFileService, - @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService - ) { - super(editorService); - - this.input = input; - - if (input instanceof EditorInput) { - this.resource = resourceForEditorHistory(input, fileService); - this.label = input.getName(); - this.description = input.getDescription(); - this.icon = this.getDirtyIndicatorForEditor(input); - } else { - const resourceInput = input as IResourceInput; - this.resource = resourceInput.resource; - this.label = resources.basenameOrAuthority(resourceInput.resource); - this.description = labelService.getUriLabel(resources.dirname(this.resource), { relative: true }); - this.icon = this.getDirtyIndicatorForEditor(resourceInput); - } - } - - private getDirtyIndicatorForEditor(input: EditorInput | IResourceInput): string { - let signalDirty = false; - if (input instanceof EditorInput) { - signalDirty = input.isDirty() && !input.isSaving(); - } else { - signalDirty = this.textFileService.isDirty(input.resource) && this.filesConfigurationService.getAutoSaveMode() !== AutoSaveMode.AFTER_SHORT_DELAY; - } - - return signalDirty ? 'codicon codicon-circle-filled' : ''; - } - - getIcon(): string { - return this.icon; - } - - getLabel(): string { - return this.label; - } - - getLabelOptions(): IIconLabelValueOptions { - return { - extraClasses: getIconClasses(this.modelService, this.modeService, this.resource) - }; - } - - getAriaLabel(): string { - return nls.localize('entryAriaLabel', "{0}, recently opened", this.getLabel()); - } - - getDescription(): string | undefined { - return this.description; - } - - getResource(): URI | undefined { - return this.resource; - } - - getInput(): IEditorInput | IResourceInput { - return this.input; - } - - run(mode: Mode, context: IEntryRunContext): boolean { - if (mode === Mode.OPEN) { - const sideBySide = !context.quickNavigateConfiguration && (context.keymods.alt || context.keymods.ctrlCmd); - const pinned = !this.configurationService.getValue().workbench.editor.enablePreviewFromQuickOpen || context.keymods.alt; - - if (this.input instanceof EditorInput) { - this.editorService.openEditor(this.input, { pinned }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); - } else { - this.editorService.openEditor({ resource: (this.input as IResourceInput).resource, options: { pinned } }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); - } - - return true; - } - - return super.run(mode, context); - } -} - -function resourceForEditorHistory(input: EditorInput, fileService: IFileService): URI | undefined { - const resource = input ? input.resource : undefined; - - // For the editor history we only prefer resources that are either untitled or - // can be handled by the file service which indicates they are editable resources. - if (resource && (fileService.canHandleResource(resource) || resource.scheme === Schemas.untitled)) { - return resource; - } - - return undefined; -} - -export class RemoveFromEditorHistoryAction extends Action { - - static readonly ID = 'workbench.action.removeFromEditorHistory'; - static readonly LABEL = nls.localize('removeFromEditorHistory', "Remove From History"); - - constructor( - id: string, - label: string, - @IQuickInputService private readonly quickInputService: IQuickInputService, - @IModelService private readonly modelService: IModelService, - @IModeService private readonly modeService: IModeService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IHistoryService private readonly historyService: IHistoryService - ) { - super(id, label); - } - - async run(): Promise { - interface IHistoryPickEntry extends IQuickPickItem { - input: IEditorInput | IResourceInput; - } - - const history = this.historyService.getHistory(); - const picks: IHistoryPickEntry[] = history.map(h => { - const entry = this.instantiationService.createInstance(EditorHistoryEntry, h); - - return { - input: h, - iconClasses: getIconClasses(this.modelService, this.modeService, entry.getResource()), - label: entry.getLabel(), - description: entry.getDescription() - }; - }); - - const pick = await this.quickInputService.pick(picks, { placeHolder: nls.localize('pickHistory', "Select an editor entry to remove from history"), matchOnDescription: true }); - if (pick) { - this.historyService.remove(pick.input); - } - } -} - -registerSingleton(IQuickOpenService, QuickOpenController, true); diff --git a/src/vs/workbench/browser/parts/quickopen/quickopen.ts b/src/vs/workbench/browser/parts/quickopen/quickopen.ts deleted file mode 100644 index 2e79e602673..00000000000 --- a/src/vs/workbench/browser/parts/quickopen/quickopen.ts +++ /dev/null @@ -1,148 +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 nls from 'vs/nls'; -import { Action } from 'vs/base/common/actions'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; - -const inQuickOpenKey = 'inQuickOpen'; -export const InQuickOpenContextKey = new RawContextKey(inQuickOpenKey, false); -export const inQuickOpenContext = ContextKeyExpr.has(inQuickOpenKey); -export const defaultQuickOpenContextKey = 'inFilesPicker'; -export const defaultQuickOpenContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyExpr.has(defaultQuickOpenContextKey)); - -export const QUICKOPEN_ACTION_ID = 'workbench.action.quickOpen'; -export const QUICKOPEN_ACION_LABEL = nls.localize('quickOpen', "Go to File..."); - -CommandsRegistry.registerCommand({ - id: QUICKOPEN_ACTION_ID, - handler: async function (accessor: ServicesAccessor, prefix: string | null = null) { - const quickOpenService = accessor.get(IQuickOpenService); - - await quickOpenService.show(typeof prefix === 'string' ? prefix : undefined); - }, - description: { - description: `Quick open`, - args: [{ - name: 'prefix', - schema: { - 'type': 'string' - } - }] - } -}); - -export const QUICKOPEN_FOCUS_SECONDARY_ACTION_ID = 'workbench.action.quickOpenPreviousEditor'; -CommandsRegistry.registerCommand(QUICKOPEN_FOCUS_SECONDARY_ACTION_ID, async function (accessor: ServicesAccessor, prefix: string | null = null) { - const quickOpenService = accessor.get(IQuickOpenService); - - await quickOpenService.show(undefined, { autoFocus: { autoFocusSecondEntry: true } }); -}); - -export class BaseQuickOpenNavigateAction extends Action { - - constructor( - id: string, - label: string, - private next: boolean, - private quickNavigate: boolean, - @IQuickOpenService private readonly quickOpenService: IQuickOpenService, - @IQuickInputService private readonly quickInputService: IQuickInputService, - @IKeybindingService private readonly keybindingService: IKeybindingService - ) { - super(id, label); - } - - async run(): Promise { - const keys = this.keybindingService.lookupKeybindings(this.id); - const quickNavigate = this.quickNavigate ? { keybindings: keys } : undefined; - - this.quickOpenService.navigate(this.next, quickNavigate); - this.quickInputService.navigate(this.next, quickNavigate); - } -} - -export function getQuickNavigateHandler(id: string, next?: boolean): ICommandHandler { - return accessor => { - const keybindingService = accessor.get(IKeybindingService); - const quickOpenService = accessor.get(IQuickOpenService); - const quickInputService = accessor.get(IQuickInputService); - - const keys = keybindingService.lookupKeybindings(id); - const quickNavigate = { keybindings: keys }; - - quickOpenService.navigate(!!next, quickNavigate); - quickInputService.navigate(!!next, quickNavigate); - }; -} - -export class QuickOpenNavigateNextAction extends BaseQuickOpenNavigateAction { - - static readonly ID = 'workbench.action.quickOpenNavigateNext'; - static readonly LABEL = nls.localize('quickNavigateNext', "Navigate Next in Quick Open"); - - constructor( - id: string, - label: string, - @IQuickOpenService quickOpenService: IQuickOpenService, - @IQuickInputService quickInputService: IQuickInputService, - @IKeybindingService keybindingService: IKeybindingService - ) { - super(id, label, true, true, quickOpenService, quickInputService, keybindingService); - } -} - -export class QuickOpenNavigatePreviousAction extends BaseQuickOpenNavigateAction { - - static readonly ID = 'workbench.action.quickOpenNavigatePrevious'; - static readonly LABEL = nls.localize('quickNavigatePrevious', "Navigate Previous in Quick Open"); - - constructor( - id: string, - label: string, - @IQuickOpenService quickOpenService: IQuickOpenService, - @IQuickInputService quickInputService: IQuickInputService, - @IKeybindingService keybindingService: IKeybindingService - ) { - super(id, label, false, true, quickOpenService, quickInputService, keybindingService); - } -} - -export class QuickOpenSelectNextAction extends BaseQuickOpenNavigateAction { - - static readonly ID = 'workbench.action.quickOpenSelectNext'; - static readonly LABEL = nls.localize('quickSelectNext', "Select Next in Quick Open"); - - constructor( - id: string, - label: string, - @IQuickOpenService quickOpenService: IQuickOpenService, - @IQuickInputService quickInputService: IQuickInputService, - @IKeybindingService keybindingService: IKeybindingService - ) { - super(id, label, true, false, quickOpenService, quickInputService, keybindingService); - } -} - -export class QuickOpenSelectPreviousAction extends BaseQuickOpenNavigateAction { - - static readonly ID = 'workbench.action.quickOpenSelectPrevious'; - static readonly LABEL = nls.localize('quickSelectPrevious', "Select Previous in Quick Open"); - - constructor( - id: string, - label: string, - @IQuickOpenService quickOpenService: IQuickOpenService, - @IQuickInputService quickInputService: IQuickInputService, - @IKeybindingService keybindingService: IKeybindingService - ) { - super(id, label, false, false, quickOpenService, quickInputService, keybindingService); - } -} diff --git a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css index 25b9dcd0625..c5953e53573 100644 --- a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css +++ b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css @@ -3,9 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .sidebar > .content { +/* Removed to allow progress bar positioning to escape */ +/* .monaco-workbench .sidebar > .content { overflow: hidden; -} +} */ .monaco-workbench.nosidebar > .part.sidebar { display: none !important; diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index 76e0dcce519..8855d9e94d6 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -23,7 +23,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { Event, Emitter } from 'vs/base/common/event'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; -import { SIDE_BAR_TITLE_FOREGROUND, SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND, SIDE_BAR_BORDER } from 'vs/workbench/common/theme'; +import { SIDE_BAR_TITLE_FOREGROUND, SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND, SIDE_BAR_BORDER, SIDE_BAR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { EventType, addDisposableListener, trackFocus } from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; @@ -33,9 +33,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { LayoutPriority } from 'vs/base/browser/ui/grid/grid'; import { assertIsDefined } from 'vs/base/common/types'; -import { LocalSelectionTransfer } from 'vs/workbench/browser/dnd'; -import { DraggedViewIdentifier } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { DraggedCompositeIdentifier } from 'vs/workbench/browser/parts/compositeBarActions'; +import { CompositeDragAndDropObserver } from 'vs/workbench/browser/dnd'; export class SidebarPart extends CompositePart implements IViewletService { @@ -167,28 +165,18 @@ export class SidebarPart extends CompositePart implements IViewletServi })); this.titleLabelElement!.draggable = true; - this._register(addDisposableListener(this.titleLabelElement!, EventType.DRAG_START, e => { - const activeViewlet = this.getActiveViewlet(); - if (activeViewlet) { - const visibleViews = activeViewlet.getViewPaneContainer().views.filter(v => v.isVisible()); - if (visibleViews.length === 1) { - LocalSelectionTransfer.getInstance().setData([new DraggedViewIdentifier(visibleViews[0].id)], DraggedViewIdentifier.prototype); - } else { - LocalSelectionTransfer.getInstance().setData([new DraggedCompositeIdentifier(activeViewlet.getId())], DraggedCompositeIdentifier.prototype); - } - } - })); - this._register(addDisposableListener(this.titleLabelElement!, EventType.DRAG_END, e => { - if (LocalSelectionTransfer.getInstance().hasData(DraggedViewIdentifier.prototype)) { - LocalSelectionTransfer.getInstance().clearData(DraggedViewIdentifier.prototype); + const draggedItemProvider = (): { type: 'view' | 'composite', id: string } => { + const activeViewlet = this.getActiveViewlet()!; + const visibleViews = activeViewlet.getViewPaneContainer().views.filter(v => v.isVisible()); + if (visibleViews.length === 1) { + return { type: 'view', id: visibleViews[0].id }; + } else { + return { type: 'composite', id: activeViewlet.getId() }; } + }; - if (LocalSelectionTransfer.getInstance().hasData(DraggedCompositeIdentifier.prototype)) { - LocalSelectionTransfer.getInstance().clearData(DraggedCompositeIdentifier.prototype); - } - })); - + this._register(CompositeDragAndDropObserver.INSTANCE.registerDraggable(this.titleLabelElement!, draggedItemProvider, {})); return titleArea; } @@ -209,6 +197,7 @@ export class SidebarPart extends CompositePart implements IViewletServi container.style.borderLeftWidth = borderColor && !isPositionLeft ? '1px' : ''; container.style.borderLeftStyle = borderColor && !isPositionLeft ? 'solid' : ''; container.style.borderLeftColor = !isPositionLeft ? borderColor || '' : ''; + container.style.outlineColor = this.getColor(SIDE_BAR_DRAG_AND_DROP_BACKGROUND) ?? ''; } layout(width: number, height: number): void { diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 688534ce6fe..2ab4129eed9 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -16,7 +16,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { StatusbarAlignment, IStatusbarService, IStatusbarEntry, IStatusbarEntryAccessor } from 'vs/workbench/services/statusbar/common/statusbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Action, IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; -import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector, ThemeColor } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector, ThemeColor } from 'vs/platform/theme/common/themeService'; import { STATUS_BAR_BACKGROUND, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_ITEM_HOVER_BACKGROUND, STATUS_BAR_ITEM_ACTIVE_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_FOREGROUND, STATUS_BAR_PROMINENT_ITEM_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND, STATUS_BAR_BORDER, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_NO_FOLDER_BORDER } from 'vs/workbench/common/theme'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; @@ -31,9 +31,10 @@ import { coalesce, find } from 'vs/base/common/arrays'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { ToggleStatusbarVisibilityAction } from 'vs/workbench/browser/actions/layoutActions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { values } from 'vs/base/common/map'; import { assertIsDefined } from 'vs/base/common/types'; import { Emitter, Event } from 'vs/base/common/event'; +import { Command } from 'vs/editor/common/modes'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; interface IPendingStatusbarEntry { id: string; @@ -54,7 +55,7 @@ interface IStatusbarViewModelEntry { class StatusbarViewModel extends Disposable { - private static readonly HIDDEN_ENTRIES_KEY = 'workbench.statusbar.hidden'; + static readonly HIDDEN_ENTRIES_KEY = 'workbench.statusbar.hidden'; private readonly _entries: IStatusbarViewModelEntry[] = []; get entries(): IStatusbarViewModelEntry[] { return this._entries; } @@ -225,7 +226,7 @@ class StatusbarViewModel extends Disposable { private saveState(): void { if (this.hidden.size > 0) { - this.storageService.store(StatusbarViewModel.HIDDEN_ENTRIES_KEY, JSON.stringify(values(this.hidden)), StorageScope.GLOBAL); + this.storageService.store(StatusbarViewModel.HIDDEN_ENTRIES_KEY, JSON.stringify(Array.from(this.hidden.values())), StorageScope.GLOBAL); } else { this.storageService.remove(StatusbarViewModel.HIDDEN_ENTRIES_KEY, StorageScope.GLOBAL); } @@ -353,10 +354,12 @@ export class StatusbarPart extends Part implements IStatusbarService { @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IStorageService storageService: IStorageService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @IContextMenuService private contextMenuService: IContextMenuService + @IContextMenuService private contextMenuService: IContextMenuService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, ) { super(Parts.STATUSBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); + storageKeysSyncRegistryService.registerStorageKey({ key: StatusbarViewModel.HIDDEN_ENTRIES_KEY, version: 1 }); this.viewModel = this._register(new StatusbarViewModel(storageService)); this.onDidChangeEntryVisibility = this.viewModel.onDidChangeEntryVisibility; @@ -702,7 +705,7 @@ class StatusbarEntryItem extends Disposable { const command = entry.command; if (command) { - this.commandListener.value = addDisposableListener(this.labelContainer, EventType.CLICK, () => this.executeCommand(command, entry.arguments)); + this.commandListener.value = addDisposableListener(this.labelContainer, EventType.CLICK, () => this.executeCommand(command)); removeClass(this.labelContainer, 'disabled'); } else { @@ -738,13 +741,14 @@ class StatusbarEntryItem extends Disposable { this.entry = entry; } - private async executeCommand(id: string, args?: unknown[]): Promise { - args = args || []; + private async executeCommand(command: string | Command): Promise { + const id = typeof command === 'string' ? command : command.id; + const args = typeof command === 'string' ? [] : command.arguments ?? []; // Maintain old behaviour of always focusing the editor here - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (activeTextEditorWidget) { - activeTextEditorWidget.focus(); + const activeTextEditorControl = this.editorService.activeTextEditorControl; + if (activeTextEditorControl) { + activeTextEditorControl.focus(); } this.telemetryService.publicLog2('workbenchActionExecuted', { id, from: 'status bar' }); @@ -766,9 +770,9 @@ class StatusbarEntryItem extends Disposable { if (color) { if (isThemeColor(color)) { - colorResult = (this.themeService.getTheme().getColor(color.id) || Color.transparent).toString(); + colorResult = (this.themeService.getColorTheme().getColor(color.id) || Color.transparent).toString(); - const listener = this.themeService.onThemeChange(theme => { + const listener = this.themeService.onDidColorThemeChange(theme => { const colorValue = (theme.getColor(color.id) || Color.transparent).toString(); if (isBackground) { @@ -804,7 +808,7 @@ class StatusbarEntryItem extends Disposable { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const statusBarItemHoverBackground = theme.getColor(STATUS_BAR_ITEM_HOVER_BACKGROUND); if (statusBarItemHoverBackground) { collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item a:hover { background-color: ${statusBarItemHoverBackground}; }`); diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 7ba5a5d5d91..03a26e08124 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { IMenuService, MenuId, IMenu, SubmenuItemAction } from 'vs/platform/actions/common/actions'; -import { registerThemingParticipant, ITheme, ICssStyleCollector, IThemeService } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, IColorTheme, ICssStyleCollector, IThemeService } from 'vs/platform/theme/common/themeService'; import { MenuBarVisibility, getTitleBarStyle, IWindowOpenable, getMenuBarVisibility } from 'vs/platform/windows/common/windows'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IAction, Action } from 'vs/base/common/actions'; @@ -26,7 +26,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { MenuBar } from 'vs/base/browser/ui/menu/menubar'; +import { MenuBar, IMenuBarOptions } from 'vs/base/browser/ui/menu/menubar'; import { SubmenuAction, Direction } from 'vs/base/browser/ui/menu/menu'; import { attachMenuStyler } from 'vs/platform/theme/common/styler'; import { assign } from 'vs/base/common/objects'; @@ -35,14 +35,6 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { isFullscreen } from 'vs/base/browser/browser'; import { IHostService } from 'vs/workbench/services/host/browser/host'; - -// TODO@sbatten https://github.com/microsoft/vscode/issues/81360 -// eslint-disable-next-line code-layering, code-import-patterns -import { IElectronService } from 'vs/platform/electron/node/electron'; -import { optional } from 'vs/platform/instantiation/common/instantiation'; -// TODO@sbatten -// eslint-disable-next-line code-layering, code-import-patterns -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; import { BrowserFeatures } from 'vs/base/browser/canIUse'; export abstract class MenubarControl extends Disposable { @@ -293,9 +285,7 @@ export class CustomMenubarControl extends MenubarControl { @IThemeService private readonly themeService: IThemeService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IHostService protected readonly hostService: IHostService, - @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, - @optional(IElectronService) private readonly electronService: IElectronService, - @optional(IElectronEnvironmentService) private readonly electronEnvironmentService: IElectronEnvironmentService + @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService ) { super( @@ -323,7 +313,7 @@ export class CustomMenubarControl extends MenubarControl { this.registerListeners(); - registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { + registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const menubarActiveWindowFgColor = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND); if (menubarActiveWindowFgColor) { collector.addRule(` @@ -437,7 +427,7 @@ export class CustomMenubarControl extends MenubarControl { return new Action('update.checking', nls.localize('checkingForUpdates', "Checking for Updates..."), undefined, false); case StateType.AvailableForDownload: - return new Action('update.downloadNow', nls.localize({ key: 'download now', comment: ['&& denotes a mnemonic'] }, "D&&ownload Now"), undefined, true, () => + return new Action('update.downloadNow', nls.localize({ key: 'download now', comment: ['&& denotes a mnemonic'] }, "D&&ownload Update"), undefined, true, () => this.updateService.downloadUpdate()); case StateType.Downloading: @@ -519,20 +509,11 @@ export class CustomMenubarControl extends MenubarControl { } if (firstTime) { - this.menubar = this._register(new MenuBar( - this.container, { - enableMnemonics: this.currentEnableMenuBarMnemonics, - disableAltFocus: this.currentDisableMenuBarAltFocus, - visibility: this.currentMenubarVisibility, - getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), - compactMode: this.currentCompactMenuMode - })); + this.menubar = this._register(new MenuBar(this.container, this.getMenuBarOptions())); this.accessibilityService.alwaysUnderlineAccessKeys().then(val => { this.alwaysOnMnemonics = val; - if (this.menubar) { - this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, disableAltFocus: this.currentDisableMenuBarAltFocus, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), alwaysOnMnemonics: this.alwaysOnMnemonics, compactMode: this.currentCompactMenuMode }); - } + this.menubar?.update(this.getMenuBarOptions()); }); this._register(this.menubar.onFocusStateChange(focused => { @@ -558,9 +539,7 @@ export class CustomMenubarControl extends MenubarControl { this._register(attachMenuStyler(this.menubar, this.themeService)); } else { - if (this.menubar) { - this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, disableAltFocus: this.currentDisableMenuBarAltFocus, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), alwaysOnMnemonics: this.alwaysOnMnemonics, compactMode: this.currentCompactMenuMode }); - } + this.menubar?.update(this.getMenuBarOptions()); } // Update the menu actions @@ -631,6 +610,35 @@ export class CustomMenubarControl extends MenubarControl { } } + private getMenuBarOptions(): IMenuBarOptions { + return { + enableMnemonics: this.currentEnableMenuBarMnemonics, + disableAltFocus: this.currentDisableMenuBarAltFocus, + visibility: this.currentMenubarVisibility, + getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), + alwaysOnMnemonics: this.alwaysOnMnemonics, + compactMode: this.currentCompactMenuMode, + getCompactMenuActions: () => { + if (!isWeb) { + return []; // only for web + } + + const webNavigationActions: IAction[] = []; + const webNavigationMenu = this.menuService.createMenu(MenuId.MenubarWebNavigationMenu, this.contextKeyService); + for (const groups of webNavigationMenu.getActions()) { + const [, actions] = groups; + for (const action of actions) { + action.label = mnemonicMenuLabel(this.calculateActionLabel(action)); + webNavigationActions.push(action); + } + } + webNavigationMenu.dispose(); + + return webNavigationActions; + } + }; + } + protected onDidChangeWindowFocus(hasFocus: boolean): void { super.onDidChangeWindowFocus(hasFocus); @@ -649,14 +657,6 @@ export class CustomMenubarControl extends MenubarControl { protected registerListeners(): void { super.registerListeners(); - // Listen for maximize/unmaximize - if (!isWeb) { - this._register(Event.any( - Event.map(Event.filter(this.electronService.onWindowMaximize, id => id === this.electronEnvironmentService.windowId), _ => true), - Event.map(Event.filter(this.electronService.onWindowUnmaximize, id => id === this.electronEnvironmentService.windowId), _ => false) - )(e => this.updateMenubar())); - } - this._register(DOM.addDisposableListener(window, DOM.EventType.RESIZE, () => { if (this.menubar && !(isIOS && BrowserFeatures.pointerEvents)) { this.menubar.blur(); @@ -701,8 +701,6 @@ export class CustomMenubarControl extends MenubarControl { this.container.style.height = `${dimension.height}px`; } - if (this.menubar) { - this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, disableAltFocus: this.currentDisableMenuBarAltFocus, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), alwaysOnMnemonics: this.alwaysOnMnemonics, compactMode: this.currentCompactMenuMode }); - } + this.menubar?.update(this.getMenuBarOptions()); } } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index f23aacde162..db93ef83b78 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -19,22 +19,21 @@ import * as nls from 'vs/nls'; import { toResource, Verbosity, SideBySideEditor } from 'vs/workbench/common/editor'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_BACKGROUND, TITLE_BAR_BORDER, WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme'; import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { Color } from 'vs/base/common/color'; import { trim } from 'vs/base/common/strings'; -import { EventType, EventHelper, Dimension, isAncestor, hide, show, removeClass, addClass, append, $, addDisposableListener, runAtThisOrScheduleAtNextAnimationFrame, removeNode } from 'vs/base/browser/dom'; +import { EventType, EventHelper, Dimension, isAncestor, removeClass, addClass, append, $, addDisposableListener, runAtThisOrScheduleAtNextAnimationFrame, removeNode } from 'vs/base/browser/dom'; import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl'; -import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { template } from 'vs/base/common/labels'; import { ILabelService } from 'vs/platform/label/common/label'; import { Emitter } from 'vs/base/common/event'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, IMenu, MenuId } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -42,10 +41,6 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IProductService } from 'vs/platform/product/common/productService'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; -// TODO@sbatten https://github.com/microsoft/vscode/issues/81360 -// eslint-disable-next-line code-layering, code-import-patterns -import { IElectronService } from 'vs/platform/electron/node/electron'; - export class TitlebarPart extends Part implements ITitleService { private static readonly NLS_UNSUPPORTED = nls.localize('patchedWindowTitle', "[Unsupported]"); @@ -68,15 +63,10 @@ export class TitlebarPart extends Part implements ITitleService { _serviceBrand: undefined; - private title!: HTMLElement; - private dragRegion: HTMLElement | undefined; - private windowControls: HTMLElement | undefined; - private maxRestoreControl: HTMLElement | undefined; - private appIcon: HTMLElement | undefined; - private customMenubar: CustomMenubarControl | undefined; - private menubar?: HTMLElement; - private resizer: HTMLElement | undefined; - private lastLayoutDimensions: Dimension | undefined; + protected title!: HTMLElement; + protected customMenubar: CustomMenubarControl | undefined; + protected menubar?: HTMLElement; + protected lastLayoutDimensions: Dimension | undefined; private titleBarStyle: 'native' | 'custom'; private pendingTitle: string | undefined; @@ -86,15 +76,15 @@ export class TitlebarPart extends Part implements ITitleService { private readonly properties: ITitleProperties = { isPure: true, isAdmin: false }; private readonly activeEditorListeners = this._register(new DisposableStore()); - private titleUpdater: RunOnceScheduler = this._register(new RunOnceScheduler(() => this.doUpdateTitle(), 0)); + private readonly titleUpdater = this._register(new RunOnceScheduler(() => this.doUpdateTitle(), 0)); private contextMenu: IMenu; constructor( @IContextMenuService private readonly contextMenuService: IContextMenuService, - @IConfigurationService private readonly configurationService: IConfigurationService, + @IConfigurationService protected readonly configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @@ -105,7 +95,6 @@ export class TitlebarPart extends Part implements ITitleService { @IContextKeyService contextKeyService: IContextKeyService, @IHostService private readonly hostService: IHostService, @IProductService private readonly productService: IProductService, - @optional(IElectronService) private electronService: IElectronService ) { super(Parts.TITLEBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); @@ -136,7 +125,7 @@ export class TitlebarPart extends Part implements ITitleService { this.updateStyles(); } - private onConfigurationChanged(event: IConfigurationChangeEvent): void { + protected onConfigurationChanged(event: IConfigurationChangeEvent): void { if (event.affectsConfiguration('window.title')) { this.titleUpdater.schedule(); } @@ -150,41 +139,16 @@ export class TitlebarPart extends Part implements ITitleService { } } } - - if (event.affectsConfiguration('window.doubleClickIconToClose')) { - if (this.appIcon) { - this.onUpdateAppIconDragBehavior(); - } - } } - private onMenubarVisibilityChanged(visible: boolean) { + protected onMenubarVisibilityChanged(visible: boolean) { if (isWeb || isWindows || isLinux) { - // Hide title when toggling menu bar - if (!isWeb && this.currentMenubarVisibility === 'toggle' && visible) { - // Hack to fix issue #52522 with layered webkit-app-region elements appearing under cursor - if (this.dragRegion) { - hide(this.dragRegion); - setTimeout(() => show(this.dragRegion!), 50); - } - } - this.adjustTitleMarginToCenter(); this._onMenubarVisibilityChange.fire(visible); } } - private onMenubarFocusChanged(focused: boolean) { - if (!isWeb && (isWindows || isLinux) && this.currentMenubarVisibility !== 'compact' && this.dragRegion) { - if (focused) { - hide(this.dragRegion); - } else { - show(this.dragRegion); - } - } - } - private onActiveEditorChange(): void { // Dispose old listeners @@ -352,7 +316,7 @@ export class TitlebarPart extends Part implements ITitleService { } } - private installMenubar(): void { + protected installMenubar(): void { // If the menubar is already installed, skip if (this.menubar) { return; @@ -367,27 +331,11 @@ export class TitlebarPart extends Part implements ITitleService { this.customMenubar.create(this.menubar); this._register(this.customMenubar.onVisibilityChange(e => this.onMenubarVisibilityChanged(e))); - this._register(this.customMenubar.onFocusStateChange(e => this.onMenubarFocusChanged(e))); } createContentArea(parent: HTMLElement): HTMLElement { this.element = parent; - // Draggable region that we can manipulate for #52522 - if (!isWeb) { - this.dragRegion = append(this.element, $('div.titlebar-drag-region')); - } - - // App Icon (Native Windows/Linux) - if (!isMacintosh && !isWeb) { - this.appIcon = append(this.element, $('div.window-appicon')); - this.onUpdateAppIconDragBehavior(); - - this._register(addDisposableListener(this.appIcon, EventType.DBLCLICK, (e => { - this.electronService.closeWindow(); - }))); - } - // Menubar: install a custom menu bar depending on configuration // and when not in activity bar if (this.titleBarStyle !== 'native' @@ -415,40 +363,6 @@ export class TitlebarPart extends Part implements ITitleService { })); }); - // Window Controls (Native Windows/Linux) - if (!isMacintosh && !isWeb) { - this.windowControls = append(this.element, $('div.window-controls-container')); - - // Minimize - const minimizeIcon = append(this.windowControls, $('div.window-icon.window-minimize.codicon.codicon-chrome-minimize')); - this._register(addDisposableListener(minimizeIcon, EventType.CLICK, e => { - this.electronService.minimizeWindow(); - })); - - // Restore - this.maxRestoreControl = append(this.windowControls, $('div.window-icon.window-max-restore.codicon')); - this._register(addDisposableListener(this.maxRestoreControl, EventType.CLICK, async e => { - const maximized = await this.electronService.isMaximized(); - if (maximized) { - return this.electronService.unmaximizeWindow(); - } - - return this.electronService.maximizeWindow(); - })); - - // Close - const closeIcon = append(this.windowControls, $('div.window-icon.window-close.codicon.codicon-chrome-close')); - this._register(addDisposableListener(closeIcon, EventType.CLICK, e => { - this.electronService.closeWindow(); - })); - - // Resizer - this.resizer = append(this.element, $('div.resizer')); - - this._register(this.layoutService.onMaximizeChange(maximized => this.onDidChangeMaximized(maximized))); - this.onDidChangeMaximized(this.layoutService.isWindowMaximized()); - } - // Since the title area is used to drag the window, we do not want to steal focus from the // currently active element. So we restore focus after a timeout back to where it was. this._register(addDisposableListener(this.element, EventType.MOUSE_DOWN, e => { @@ -469,28 +383,6 @@ export class TitlebarPart extends Part implements ITitleService { return this.element; } - private onDidChangeMaximized(maximized: boolean) { - if (this.maxRestoreControl) { - if (maximized) { - removeClass(this.maxRestoreControl, 'codicon-chrome-maximize'); - addClass(this.maxRestoreControl, 'codicon-chrome-restore'); - } else { - removeClass(this.maxRestoreControl, 'codicon-chrome-restore'); - addClass(this.maxRestoreControl, 'codicon-chrome-maximize'); - } - } - - if (this.resizer) { - if (maximized) { - hide(this.resizer); - } else { - show(this.resizer); - } - } - - this.adjustTitleMarginToCenter(); - } - updateStyles(): void { super.updateStyles(); @@ -524,15 +416,6 @@ export class TitlebarPart extends Part implements ITitleService { } } - private onUpdateAppIconDragBehavior() { - const setting = this.configurationService.getValue('window.doubleClickIconToClose'); - if (setting && this.appIcon) { - (this.appIcon.style as any)['-webkit-app-region'] = 'no-drag'; - } else if (this.appIcon) { - (this.appIcon.style as any)['-webkit-app-region'] = 'drag'; - } - } - private onContextMenu(e: MouseEvent): void { // Find target anchor @@ -551,10 +434,10 @@ export class TitlebarPart extends Part implements ITitleService { }); } - private adjustTitleMarginToCenter(): void { + protected adjustTitleMarginToCenter(): void { if (this.customMenubar && this.menubar) { - const leftMarker = (this.appIcon ? this.appIcon.clientWidth : 0) + this.menubar.clientWidth + 10; - const rightMarker = this.element.clientWidth - (this.windowControls ? this.windowControls.clientWidth : 0) - 10; + const leftMarker = this.menubar.clientWidth + 10; + const rightMarker = this.element.clientWidth - 10; // Not enough space to center the titlebar within window, // Center between menu and window controls @@ -572,7 +455,7 @@ export class TitlebarPart extends Part implements ITitleService { this.title.style.transform = 'translate(-50%, 0)'; } - private get currentMenubarVisibility(): MenuBarVisibility { + protected get currentMenubarVisibility(): MenuBarVisibility { return getMenuBarVisibility(this.configurationService, this.environmentService); } @@ -583,26 +466,8 @@ export class TitlebarPart extends Part implements ITitleService { // Only prevent zooming behavior on macOS or when the menubar is not visible if ((!isWeb && isMacintosh) || this.currentMenubarVisibility === 'hidden') { this.title.style.zoom = `${1 / getZoomFactor()}`; - if (!isWeb && (isWindows || isLinux)) { - if (this.appIcon) { - this.appIcon.style.zoom = `${1 / getZoomFactor()}`; - } - - if (this.windowControls) { - this.windowControls.style.zoom = `${1 / getZoomFactor()}`; - } - } } else { - this.title.style.zoom = null; - if (!isWeb && (isWindows || isLinux)) { - if (this.appIcon) { - this.appIcon.style.zoom = null; - } - - if (this.windowControls) { - this.windowControls.style.zoom = null; - } - } + this.title.style.zoom = ''; } runAtThisOrScheduleAtNextAnimationFrame(() => this.adjustTitleMarginToCenter()); @@ -627,7 +492,7 @@ export class TitlebarPart extends Part implements ITitleService { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const titlebarActiveFg = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND); if (titlebarActiveFg) { collector.addRule(` @@ -646,5 +511,3 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { `); } }); - -registerSingleton(ITitleService, TitlebarPart); diff --git a/src/vs/workbench/browser/parts/views/media/paneviewlet.css b/src/vs/workbench/browser/parts/views/media/paneviewlet.css index fc7221b9eaa..584dd52c32e 100644 --- a/src/vs/workbench/browser/parts/views/media/paneviewlet.css +++ b/src/vs/workbench/browser/parts/views/media/paneviewlet.css @@ -6,6 +6,13 @@ .monaco-pane-view .split-view-view:first-of-type > .pane > .pane-header { border-top: none !important; /* less clutter: do not show any border for first views in a pane */ } +.monaco-pane-view .split-view-view:first-of-type > .pane { + border-left: none !important; /* less clutter: do not show any border for first views in a pane */ +} + +.monaco-pane-view .pane > .pane-header { + position: relative; +} .monaco-pane-view .pane > .pane-header > .actions.show { display: initial; @@ -19,3 +26,15 @@ -webkit-margin-before: 0; -webkit-margin-after: 0; } + +.monaco-pane-view .pane .monaco-progress-container { + position: absolute; + left: 0; + top: -2px; + z-index: 5; + height: 2px; +} + +.monaco-pane-view .pane:not(.merged-header) .monaco-progress-container { + top: 20px; +} diff --git a/src/vs/workbench/browser/parts/views/media/views.css b/src/vs/workbench/browser/parts/views/media/views.css index bd12bd60f9b..ce5f3056e3b 100644 --- a/src/vs/workbench/browser/parts/views/media/views.css +++ b/src/vs/workbench/browser/parts/views/media/views.css @@ -3,28 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/* File icon themeable OLD tree style */ -.file-icon-themable-tree .monaco-tree-row .content { - display: flex; -} - -.file-icon-themable-tree .monaco-tree-row .content::before { - background-size: 16px; - background-position: 50% 50%; - background-repeat: no-repeat; - padding-right: 6px; - width: 16px; - height: 22px; - display: inline-block; - vertical-align: top; - content: ' '; -} - -.file-icon-themable-tree.align-icons-and-twisties .monaco-tree-row:not(.has-children) .content::before, -.file-icon-themable-tree.hide-arrows .monaco-tree-row .content::before { - display: none; -} - /* File icons in trees */ .file-icon-themable-tree.align-icons-and-twisties .monaco-tl-twistie:not(.force-twistie):not(.collapsible), @@ -74,9 +52,9 @@ .monaco-workbench .pane > .pane-body > .welcome-view { width: 100%; height: 100%; - padding: 0 20px 0 20px; - position: absolute; box-sizing: border-box; + display: flex; + flex-direction: column; } .monaco-workbench .pane > .pane-body:not(.welcome) > .welcome-view, @@ -91,6 +69,15 @@ display: block; } +.monaco-workbench .pane > .pane-body .welcome-view-content { + padding: 0 20px 0 20px; + box-sizing: border-box; +} + +.monaco-workbench .pane > .pane-body .welcome-view-content > *:last-child { + margin-bottom: 1em; +} + .customview-tree .monaco-list-row .monaco-tl-contents.align-icon-with-twisty::before { display: none; } @@ -141,6 +128,10 @@ margin-top: 3px; } +.customview-tree .monaco-list .monaco-list-row.selected .custom-view-tree-node-item > .custom-view-tree-node-item-icon.codicon { + color: currentColor !important; +} + .customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .custom-view-tree-node-item-resourceLabel .monaco-icon-label-container > .monaco-icon-name-container { flex: 1; } @@ -167,6 +158,10 @@ background-repeat: no-repeat; } +.customview-tree .monaco-list .custom-view-tree-node-item .actions .action-label.codicon { + line-height: 22px; +} + .customview-tree .monaco-list .custom-view-tree-node-item .actions .action-label.codicon::before { vertical-align: middle; } diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/treeView.ts similarity index 81% rename from src/vs/workbench/browser/parts/views/customView.ts rename to src/vs/workbench/browser/parts/views/treeView.ts index 666a9e25f3f..be4c2ec82ea 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -7,19 +7,18 @@ import 'vs/css!./media/views'; import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IAction, IActionViewItem, ActionRunner, Action } from 'vs/base/common/actions'; +import { IAction, ActionRunner } from 'vs/base/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId, MenuItemAction, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { ContextAwareMenuEntryActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, ContextKeyExpr, ContextKeyEqualsExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ITreeView, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeViewDescriptor, IViewsRegistry, ITreeItemLabel, Extensions, IViewDescriptorService, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import * as DOM from 'vs/base/browser/dom'; import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; @@ -28,7 +27,7 @@ import { URI } from 'vs/base/common/uri'; import { dirname, basename } from 'vs/base/common/resources'; import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant, ThemeIcon, IThemeService } from 'vs/platform/theme/common/themeService'; import { FileKind } from 'vs/platform/files/common/files'; -import { WorkbenchAsyncDataTree, TreeResourceNavigator } from 'vs/platform/list/browser/listService'; +import { WorkbenchAsyncDataTree, ResourceNavigator } from 'vs/platform/list/browser/listService'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { localize } from 'vs/nls'; import { timeout } from 'vs/base/common/async'; @@ -45,13 +44,12 @@ import { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -export class CustomTreeViewPane extends ViewPane { +export class TreeViewPane extends ViewPane { private treeView: ITreeView; constructor( options: IViewletViewOptions, - @INotificationService private readonly notificationService: INotificationService, @IKeybindingService keybindingService: IKeybindingService, @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, @@ -62,13 +60,14 @@ export class CustomTreeViewPane extends ViewPane { @IThemeService themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title, titleMenuId: MenuId.ViewTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super({ ...(options as IViewPaneOptions), titleMenuId: MenuId.ViewTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); const { treeView } = (Registry.as(Extensions.ViewsRegistry).getView(options.id)); this.treeView = treeView; this._register(this.treeView.onDidChangeActions(() => this.updateActions(), this)); this._register(this.treeView.onDidChangeTitle((newTitle) => this.updateTitle(newTitle))); this._register(toDisposable(() => this.treeView.setVisibility(false))); this._register(this.onDidChangeBodyVisibility(() => this.updateTreeVisibility())); + this._register(this.treeView.onDidChangeWelcomeState(() => this._onDidChangeViewWelcomeState.fire())); this.updateTreeVisibility(); } @@ -80,27 +79,19 @@ export class CustomTreeViewPane extends ViewPane { renderBody(container: HTMLElement): void { super.renderBody(container); - if (this.treeView instanceof CustomTreeView) { + if (this.treeView instanceof TreeView) { this.treeView.show(container); } } + shouldShowWelcome(): boolean { + return ((this.treeView.dataProvider === undefined) || !!this.treeView.dataProvider.isTreeEmpty) && (this.treeView.message === undefined); + } + layoutBody(height: number, width: number): void { this.treeView.layout(height, width); } - getActions(): IAction[] { - return [...super.getActions(), ...this.treeView.getPrimaryActions()]; - } - - getSecondaryActions(): IAction[] { - return [...super.getSecondaryActions(), ...this.treeView.getSecondaryActions()]; - } - - getActionViewItem(action: IAction): IActionViewItem | undefined { - return action instanceof MenuItemAction ? new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService) : undefined; - } - getOptimalWidth(): number { return this.treeView.getOptimalWidth(); } @@ -120,15 +111,18 @@ class Root implements ITreeItem { const noDataProviderMessage = localize('no-dataprovider', "There is no data provider registered that can provide view data."); -class CustomTree extends WorkbenchAsyncDataTree { } +class Tree extends WorkbenchAsyncDataTree { } -export class CustomTreeView extends Disposable implements ITreeView { +export class TreeView extends Disposable implements ITreeView { private isVisible: boolean = false; - private activated: boolean = false; private _hasIconForParentNode = false; private _hasIconForLeafNode = false; - private _showCollapseAllAction = false; + + private readonly collapseAllContextKey: RawContextKey; + private readonly collapseAllContext: IContextKey; + private readonly refreshContextKey: RawContextKey; + private readonly refreshContext: IContextKey; private focused: boolean = false; private domNode!: HTMLElement; @@ -136,7 +130,7 @@ export class CustomTreeView extends Disposable implements ITreeView { private _messageValue: string | undefined; private _canSelectMany: boolean = false; private messageElement!: HTMLDivElement; - private tree: CustomTree | undefined; + private tree: Tree | undefined; private treeLabels: ResourceLabels | undefined; private root: ITreeItem; @@ -157,29 +151,37 @@ export class CustomTreeView extends Disposable implements ITreeView { private readonly _onDidChangeActions: Emitter = this._register(new Emitter()); readonly onDidChangeActions: Event = this._onDidChangeActions.event; + private readonly _onDidChangeWelcomeState: Emitter = this._register(new Emitter()); + readonly onDidChangeWelcomeState: Event = this._onDidChangeWelcomeState.event; + private readonly _onDidChangeTitle: Emitter = this._register(new Emitter()); readonly onDidChangeTitle: Event = this._onDidChangeTitle.event; private readonly _onDidCompleteRefresh: Emitter = this._register(new Emitter()); constructor( - private id: string, + protected readonly id: string, private _title: string, - @IExtensionService private readonly extensionService: IExtensionService, - @IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService, + @IThemeService private readonly themeService: IThemeService, @IInstantiationService private readonly instantiationService: IInstantiationService, @ICommandService private readonly commandService: ICommandService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IProgressService private readonly progressService: IProgressService, + @IProgressService protected readonly progressService: IProgressService, @IContextMenuService private readonly contextMenuService: IContextMenuService, @IKeybindingService private readonly keybindingService: IKeybindingService, @INotificationService private readonly notificationService: INotificationService, - @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, + @IContextKeyService contextKeyService: IContextKeyService ) { super(); this.root = new Root(); + this.collapseAllContextKey = new RawContextKey(`treeView.${this.id}.enableCollapseAll`, false); + this.collapseAllContext = this.collapseAllContextKey.bindTo(contextKeyService); + this.refreshContextKey = new RawContextKey(`treeView.${this.id}.enableRefresh`, false); + this.refreshContext = this.refreshContextKey.bindTo(contextKeyService); + this._register(this.themeService.onDidFileIconThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/)); - this._register(this.themeService.onThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/)); + this._register(this.themeService.onDidColorThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/)); this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('explorer.decorations')) { this.doRefresh([this.root]); /** soft refresh **/ @@ -190,6 +192,7 @@ export class CustomTreeView extends Disposable implements ITreeView { this.tree?.updateOptions({ overrideStyles: { listBackground: this.viewLocation === ViewContainerLocation.Sidebar ? SIDE_BAR_BACKGROUND : PANEL_BACKGROUND } }); } })); + this.registerActions(); this.create(); } @@ -214,21 +217,43 @@ export class CustomTreeView extends Disposable implements ITreeView { if (dataProvider) { this._dataProvider = new class implements ITreeViewDataProvider { + private _isEmpty: boolean = true; + private _onDidChangeEmpty: Emitter = new Emitter(); + public onDidChangeEmpty: Event = this._onDidChangeEmpty.event; + + get isTreeEmpty(): boolean { + return this._isEmpty; + } + async getChildren(node: ITreeItem): Promise { + let children: ITreeItem[]; if (node && node.children) { - return Promise.resolve(node.children); + children = node.children; + } else { + children = await (node instanceof Root ? dataProvider.getChildren() : dataProvider.getChildren(node)); + node.children = children; + } + if (node instanceof Root) { + const oldEmpty = this._isEmpty; + this._isEmpty = children.length === 0; + if (oldEmpty !== this._isEmpty) { + this._onDidChangeEmpty.fire(); + } } - const children = await (node instanceof Root ? dataProvider.getChildren() : dataProvider.getChildren(node)); - node.children = children; return children; } }; + if (this._dataProvider.onDidChangeEmpty) { + this._register(this._dataProvider.onDidChangeEmpty(() => this._onDidChangeWelcomeState.fire())); + } this.updateMessage(); this.refresh(); } else { this._dataProvider = undefined; this.updateMessage(); } + + this._onDidChangeWelcomeState.fire(); } private _message: string | undefined; @@ -239,6 +264,7 @@ export class CustomTreeView extends Disposable implements ITreeView { set message(message: string | undefined) { this._message = message; this.updateMessage(); + this._onDidChangeWelcomeState.fire(); } get title(): string { @@ -271,26 +297,61 @@ export class CustomTreeView extends Disposable implements ITreeView { } get showCollapseAllAction(): boolean { - return this._showCollapseAllAction; + return !!this.collapseAllContext.get(); } set showCollapseAllAction(showCollapseAllAction: boolean) { - if (this._showCollapseAllAction !== !!showCollapseAllAction) { - this._showCollapseAllAction = !!showCollapseAllAction; - this._onDidChangeActions.fire(); - } + this.collapseAllContext.set(showCollapseAllAction); } - getPrimaryActions(): IAction[] { - if (this.showCollapseAllAction) { - return [new Action('vs.tree.collapse', localize('collapseAll', "Collapse All"), 'monaco-tree-action codicon-collapse-all', true, () => this.tree ? new CollapseAllAction(this.tree, true).run() : Promise.resolve())]; - } else { - return []; - } + get showRefreshAction(): boolean { + return !!this.refreshContext.get(); } - getSecondaryActions(): IAction[] { - return []; + set showRefreshAction(showRefreshAction: boolean) { + this.refreshContext.set(showRefreshAction); + } + + private registerActions() { + const that = this; + this._register(registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.actions.treeView.${that.id}.refresh`, + title: localize('refresh', "Refresh"), + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', that.id), that.refreshContextKey), + group: 'navigation', + order: Number.MAX_SAFE_INTEGER - 1, + }, + icon: { id: 'codicon/refresh' } + }); + } + async run(): Promise { + return that.refresh(); + } + })); + this._register(registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.actions.treeView.${that.id}.collapseAll`, + title: localize('collapseAll', "Collapse All"), + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', that.id), that.collapseAllContextKey), + group: 'navigation', + order: Number.MAX_SAFE_INTEGER, + }, + icon: { id: 'codicon/collapse-all' } + }); + } + async run(): Promise { + if (that.tree) { + return new CollapseAllAction(that.tree, true).run(); + } + } + })); } setVisibility(isVisible: boolean): void { @@ -300,9 +361,6 @@ export class CustomTreeView extends Disposable implements ITreeView { } this.isVisible = isVisible; - if (this.isVisible) { - this.activate(); - } if (this.tree) { if (this.isVisible) { @@ -356,13 +414,13 @@ export class CustomTreeView extends Disposable implements ITreeView { const actionViewItemProvider = (action: IAction) => action instanceof MenuItemAction ? this.instantiationService.createInstance(ContextAwareMenuEntryActionViewItem, action) : undefined; const treeMenus = this._register(this.instantiationService.createInstance(TreeMenus, this.id)); this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this)); - const dataSource = this.instantiationService.createInstance(TreeDataSource, this, (task: Promise) => this.progressService.withProgress({ location: this.viewContainer.id }, () => task)); + const dataSource = this.instantiationService.createInstance(TreeDataSource, this, (task: Promise) => this.progressService.withProgress({ location: this.id }, () => task)); const aligner = new Aligner(this.themeService); const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, treeMenus, this.treeLabels, actionViewItemProvider, aligner); - this.tree = this._register(this.instantiationService.createInstance(CustomTree, 'CustomView', this.treeContainer, new CustomTreeDelegate(), [renderer], + this.tree = this._register(this.instantiationService.createInstance(Tree, this.id, this.treeContainer, new TreeViewDelegate(), [renderer], dataSource, { - identityProvider: new CustomViewIdentityProvider(), + identityProvider: new TreeViewIdentityProvider(), accessibilityProvider: { getAriaLabel(element: ITreeItem): string { return element.tooltip ? element.tooltip : element.label ? element.label.label : ''; @@ -404,9 +462,9 @@ export class CustomTreeView extends Disposable implements ITreeView { })); this.tree.setInput(this.root).then(() => this.updateContentAreas()); - const customTreeNavigator = new TreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false }); - this._register(customTreeNavigator); - this._register(customTreeNavigator.onDidOpenResource(e => { + const treeNavigator = ResourceNavigator.createTreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false }); + this._register(treeNavigator); + this._register(treeNavigator.onDidOpenResource(e => { if (!e.browserEvent) { return; } @@ -457,7 +515,7 @@ export class CustomTreeView extends Disposable implements ITreeView { }); } - private updateMessage(): void { + protected updateMessage(): void { if (this._message) { this.showMessage(this._message); } else if (!this.dataProvider) { @@ -551,7 +609,6 @@ export class CustomTreeView extends Disposable implements ITreeView { return tree.expand(element, false); })); } - return Promise.resolve(undefined); } setSelection(items: ITreeItem[]): void { @@ -567,21 +624,9 @@ export class CustomTreeView extends Disposable implements ITreeView { } } - reveal(item: ITreeItem): Promise { + async reveal(item: ITreeItem): Promise { if (this.tree) { - return Promise.resolve(this.tree.reveal(item)); - } - return Promise.resolve(); - } - - private activate() { - if (!this.activated) { - this.progressService.withProgress({ location: this.viewContainer.id }, () => this.extensionService.activateByEvent(`onView:${this.id}`)) - .then(() => timeout(2000)) - .then(() => { - this.updateMessage(); - }); - this.activated = true; + return this.tree.reveal(item); } } @@ -613,13 +658,13 @@ export class CustomTreeView extends Disposable implements ITreeView { } } -class CustomViewIdentityProvider implements IIdentityProvider { +class TreeViewIdentityProvider implements IIdentityProvider { getId(element: ITreeItem): { toString(): string; } { return element.handle; } } -class CustomTreeDelegate implements IListVirtualDelegate { +class TreeViewDelegate implements IListVirtualDelegate { getHeight(element: ITreeItem): number { return TreeRenderer.ITEM_HEIGHT; @@ -642,11 +687,11 @@ class TreeDataSource implements IAsyncDataSource { return !!this.treeView.dataProvider && (element.collapsibleState !== TreeItemCollapsibleState.None); } - getChildren(element: ITreeItem): ITreeItem[] | Promise { + async getChildren(element: ITreeItem): Promise { if (this.treeView.dataProvider) { return this.withProgress(this.treeView.dataProvider.getChildren(element)); } - return Promise.resolve([]); + return []; } } @@ -697,7 +742,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer | undefined; - constructor(private themeService: IWorkbenchThemeService) { + constructor(private themeService: IThemeService) { super(); } @@ -854,7 +899,7 @@ class Aligner extends Disposable { } private hasIcon(node: ITreeItem): boolean { - const icon = this.themeService.getTheme().type === LIGHT ? node.icon : node.iconDark; + const icon = this.themeService.getColorTheme().type === LIGHT ? node.icon : node.iconDark; if (icon) { return true; } @@ -938,3 +983,44 @@ class TreeMenus extends Disposable implements IDisposable { return result; } } + +export class CustomTreeView extends TreeView { + + private activated: boolean = false; + + constructor( + id: string, + title: string, + @IThemeService themeService: IThemeService, + @IInstantiationService instantiationService: IInstantiationService, + @ICommandService commandService: ICommandService, + @IConfigurationService configurationService: IConfigurationService, + @IProgressService progressService: IProgressService, + @IContextMenuService contextMenuService: IContextMenuService, + @IKeybindingService keybindingService: IKeybindingService, + @INotificationService notificationService: INotificationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IContextKeyService contextKeyService: IContextKeyService, + @IExtensionService private readonly extensionService: IExtensionService, + ) { + super(id, title, themeService, instantiationService, commandService, configurationService, progressService, contextMenuService, keybindingService, notificationService, viewDescriptorService, contextKeyService); + } + + setVisibility(isVisible: boolean): void { + super.setVisibility(isVisible); + if (this.visible) { + this.activate(); + } + } + + private activate() { + if (!this.activated) { + this.progressService.withProgress({ location: this.id }, () => this.extensionService.activateByEvent(`onView:${this.id}`)) + .then(() => timeout(2000)) + .then(() => { + this.updateMessage(); + }); + this.activated = true; + } + } +} diff --git a/src/vs/workbench/browser/parts/views/viewMenuActions.ts b/src/vs/workbench/browser/parts/views/viewMenuActions.ts index f55455a925e..3c0ad25bc70 100644 --- a/src/vs/workbench/browser/parts/views/viewMenuActions.ts +++ b/src/vs/workbench/browser/parts/views/viewMenuActions.ts @@ -36,7 +36,7 @@ export class ViewMenuActions extends Disposable { const updateActions = () => { this.primaryActions = []; this.secondaryActions = []; - this.titleActionsDisposable.value = createAndFillInActionBarActions(menu, undefined, { primary: this.primaryActions, secondary: this.secondaryActions }); + this.titleActionsDisposable.value = createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, { primary: this.primaryActions, secondary: this.secondaryActions }); this._onDidChangeTitle.fire(); }; this._register(menu.onDidChange(updateActions)); @@ -45,7 +45,7 @@ export class ViewMenuActions extends Disposable { const contextMenu = this._register(this.menuService.createMenu(contextMenuId, scopedContextKeyService)); const updateContextMenuActions = () => { this.contextMenuActions = []; - this.titleActionsDisposable.value = createAndFillInActionBarActions(contextMenu, undefined, { primary: [], secondary: this.contextMenuActions }); + this.titleActionsDisposable.value = createAndFillInActionBarActions(contextMenu, { shouldForwardArgs: true }, { primary: [], secondary: this.contextMenuActions }); }; this._register(contextMenu.onDidChange(updateContextMenuActions)); updateContextMenuActions(); diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 21e312a5084..8013d3a3961 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -6,24 +6,23 @@ import 'vs/css!./media/paneviewlet'; import * as nls from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; -import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; -import { attachStyler, IColorMapping, attachButtonStyler, attachLinkStyler } from 'vs/platform/theme/common/styler'; -import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER, PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { ColorIdentifier, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; +import { attachStyler, IColorMapping, attachButtonStyler, attachLinkStyler, attachProgressBarStyler } from 'vs/platform/theme/common/styler'; +import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER, PANEL_BACKGROUND, SIDE_BAR_BACKGROUND, EDITOR_DRAG_AND_DROP_BACKGROUND, PANEL_BORDER } from 'vs/workbench/common/theme'; import { append, $, trackFocus, toggleClass, EventType, isAncestor, Dimension, addDisposableListener, removeClass, addClass } from 'vs/base/browser/dom'; import { IDisposable, combinedDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { firstIndex } from 'vs/base/common/arrays'; import { IAction } from 'vs/base/common/actions'; -import { IActionViewItem, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionViewItem, ActionsOrientation, Separator, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; import { Registry } from 'vs/platform/registry/common/platform'; -import { prepareActions } from 'vs/workbench/browser/actions'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { PaneView, IPaneViewOptions, IPaneOptions, Pane, DefaultPaneDndController } from 'vs/base/browser/ui/splitview/paneview'; +import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; +import { PaneView, IPaneViewOptions, IPaneOptions, Pane } from 'vs/base/browser/ui/splitview/paneview'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor, ViewContainer, IViewDescriptorService, ViewContainerLocation, IViewPaneContainer, IViewsRegistry, IViewContentDescriptor } from 'vs/workbench/common/views'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; @@ -42,30 +41,29 @@ import { parseLinkedText } from 'vs/base/common/linkedText'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { Button } from 'vs/base/browser/ui/button/button'; import { Link } from 'vs/platform/opener/browser/link'; -import { LocalSelectionTransfer } from 'vs/workbench/browser/dnd'; +import { CompositeDragAndDropObserver, DragAndDropObserver } from 'vs/workbench/browser/dnd'; +import { Orientation } from 'vs/base/browser/ui/sash/sash'; +import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; +import { CompositeProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator'; +import { IProgressIndicator } from 'vs/platform/progress/common/progress'; +import { RunOnceScheduler } from 'vs/base/common/async'; +import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; +import { ScrollbarVisibility } from 'vs/base/common/scrollable'; export interface IPaneColors extends IColorMapping { dropBackground?: ColorIdentifier; headerForeground?: ColorIdentifier; headerBackground?: ColorIdentifier; headerBorder?: ColorIdentifier; + leftBorder?: ColorIdentifier; } export interface IViewPaneOptions extends IPaneOptions { id: string; - title: string; showActionsAlways?: boolean; titleMenuId?: MenuId; } -export class DraggedViewIdentifier { - constructor(private _viewId: string) { } - - get id(): string { - return this._viewId; - } -} - type WelcomeActionClassification = { viewId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; uri: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; @@ -181,6 +179,8 @@ export abstract class ViewPane extends Pane implements IView { title: string; private readonly menuActions: ViewMenuActions; + private progressBar!: ProgressBar; + private progressIndicator!: IProgressIndicator; private toolbar?: ToolBar; private readonly showActionsAlways: boolean = false; @@ -205,7 +205,7 @@ export abstract class ViewPane extends Pane implements IView { @IThemeService protected themeService: IThemeService, @ITelemetryService protected telemetryService: ITelemetryService, ) { - super(options); + super({ ...options, ...{ orientation: viewDescriptorService.getViewLocation(options.id) === ViewContainerLocation.Panel ? Orientation.HORIZONTAL : Orientation.VERTICAL } }); this.id = options.id; this.title = options.title; @@ -218,6 +218,15 @@ export abstract class ViewPane extends Pane implements IView { this.viewWelcomeController = new ViewWelcomeController(this.id, contextKeyService); } + get headerVisible(): boolean { + return super.headerVisible; + } + + set headerVisible(visible: boolean) { + super.headerVisible = visible; + toggleClass(this.element, 'merged-header', !visible); + } + setVisible(visible: boolean): void { if (this._isVisible !== visible) { this._isVisible = visible; @@ -303,9 +312,20 @@ export abstract class ViewPane extends Pane implements IView { this._onDidChangeTitleArea.fire(); } + private scrollableElement!: DomScrollableElement; + protected renderBody(container: HTMLElement): void { this.bodyContainer = container; - this.viewWelcomeContainer = append(container, $('.welcome-view', { tabIndex: 0 })); + + const viewWelcomeContainer = append(container, $('.welcome-view')); + this.viewWelcomeContainer = $('.welcome-view-content', { tabIndex: 0 }); + this.scrollableElement = this._register(new DomScrollableElement(this.viewWelcomeContainer, { + alwaysConsumeMouseWheel: true, + horizontal: ScrollbarVisibility.Hidden, + vertical: ScrollbarVisibility.Visible, + })); + + append(viewWelcomeContainer, this.scrollableElement.getDomNode()); const onViewWelcomeChange = Event.any(this.viewWelcomeController.onDidChange, this.onDidChangeViewWelcomeState); this._register(onViewWelcomeChange(this.updateViewWelcome, this)); @@ -313,7 +333,23 @@ export abstract class ViewPane extends Pane implements IView { } protected layoutBody(height: number, width: number): void { - // noop + this.viewWelcomeContainer.style.height = `${height}px`; + this.viewWelcomeContainer.style.width = `${width}px`; + this.scrollableElement.scanDomNode(); + } + + getProgressIndicator() { + if (this.progressBar === undefined) { + // Progress bar + this.progressBar = this._register(new ProgressBar(this.element)); + this._register(attachProgressBarStyler(this.progressBar, this.themeService)); + this.progressBar.hide(); + } + + if (this.progressIndicator === undefined) { + this.progressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, assertIsDefined(this.progressBar), this.id, this.isBodyVisible()); + } + return this.progressIndicator; } protected getProgressLocation(): string { @@ -354,15 +390,15 @@ export abstract class ViewPane extends Pane implements IView { } getActions(): IAction[] { - return this.menuActions ? this.menuActions.getPrimaryActions() : []; + return this.menuActions.getPrimaryActions(); } getSecondaryActions(): IAction[] { - return this.menuActions ? this.menuActions.getSecondaryActions() : []; + return this.menuActions.getSecondaryActions(); } getContextMenuActions(): IAction[] { - return this.menuActions ? this.menuActions.getContextMenuActions() : []; + return this.menuActions.getContextMenuActions(); } getActionViewItem(action: IAction): IActionViewItem | undefined { @@ -390,6 +426,7 @@ export abstract class ViewPane extends Pane implements IView { if (!this.shouldShowWelcome()) { removeClass(this.bodyContainer, 'welcome'); this.viewWelcomeContainer.innerHTML = ''; + this.scrollableElement.scanDomNode(); return; } @@ -398,6 +435,7 @@ export abstract class ViewPane extends Pane implements IView { if (contents.length === 0) { removeClass(this.bodyContainer, 'welcome'); this.viewWelcomeContainer.innerHTML = ''; + this.scrollableElement.scanDomNode(); return; } @@ -417,47 +455,52 @@ export abstract class ViewPane extends Pane implements IView { continue; } - const p = append(this.viewWelcomeContainer, $('p')); const linkedText = parseLinkedText(line); - for (const node of linkedText.nodes) { - if (typeof node === 'string') { - append(p, document.createTextNode(node)); - } else if (linkedText.nodes.length === 1) { - const button = new Button(p, { title: node.title }); - button.label = node.label; - button.onDidClick(_ => { - this.telemetryService.publicLog2<{ viewId: string, uri: string }, WelcomeActionClassification>('views.welcomeAction', { viewId: this.id, uri: node.href }); - this.openerService.open(node.href); - }, null, disposables); - disposables.add(button); - disposables.add(attachButtonStyler(button, this.themeService)); + if (linkedText.nodes.length === 1 && typeof linkedText.nodes[0] !== 'string') { + const node = linkedText.nodes[0]; + const button = new Button(this.viewWelcomeContainer, { title: node.title }); + button.label = node.label; + button.onDidClick(_ => { + this.telemetryService.publicLog2<{ viewId: string, uri: string }, WelcomeActionClassification>('views.welcomeAction', { viewId: this.id, uri: node.href }); + this.openerService.open(node.href); + }, null, disposables); + disposables.add(button); + disposables.add(attachButtonStyler(button, this.themeService)); - if (preconditions) { - const precondition = preconditions[buttonIndex]; + if (preconditions) { + const precondition = preconditions[buttonIndex]; - if (precondition) { - const updateEnablement = () => button.enabled = this.contextKeyService.contextMatchesRules(precondition); - updateEnablement(); + if (precondition) { + const updateEnablement = () => button.enabled = this.contextKeyService.contextMatchesRules(precondition); + updateEnablement(); - const keys = new Set(); - precondition.keys().forEach(key => keys.add(key)); - const onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys)); - onDidChangeContext(updateEnablement, null, disposables); - } + const keys = new Set(); + precondition.keys().forEach(key => keys.add(key)); + const onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys)); + onDidChangeContext(updateEnablement, null, disposables); } + } - buttonIndex++; - } else { - const link = this.instantiationService.createInstance(Link, node); - append(p, link.el); - disposables.add(link); - disposables.add(attachLinkStyler(link, this.themeService)); + buttonIndex++; + } else { + const p = append(this.viewWelcomeContainer, $('p')); + + for (const node of linkedText.nodes) { + if (typeof node === 'string') { + append(p, document.createTextNode(node)); + } else { + const link = this.instantiationService.createInstance(Link, node); + append(p, link.el); + disposables.add(link); + disposables.add(attachLinkStyler(link, this.themeService)); + } } } } } + this.scrollableElement.scanDomNode(); this.viewWelcomeDisposable = disposables; } @@ -476,6 +519,217 @@ interface IViewPaneItem { disposable: IDisposable; } +const enum DropDirection { + UP, + DOWN, + LEFT, + RIGHT +} + +class ViewPaneDropOverlay extends Themable { + + private static readonly OVERLAY_ID = 'monaco-workbench-pane-drop-overlay'; + + private container!: HTMLElement; + private overlay!: HTMLElement; + + private _currentDropOperation: DropDirection | undefined; + + // private currentDropOperation: IDropOperation | undefined; + private _disposed: boolean | undefined; + + private cleanupOverlayScheduler: RunOnceScheduler; + + get currentDropOperation(): DropDirection | undefined { + return this._currentDropOperation; + } + + constructor( + private paneElement: HTMLElement, + private orientation: Orientation | undefined, + protected themeService: IThemeService + ) { + super(themeService); + this.cleanupOverlayScheduler = this._register(new RunOnceScheduler(() => this.dispose(), 300)); + + this.create(); + } + + get disposed(): boolean { + return !!this._disposed; + } + + private create(): void { + // Container + this.container = document.createElement('div'); + this.container.id = ViewPaneDropOverlay.OVERLAY_ID; + this.container.style.top = '0px'; + + // Parent + this.paneElement.appendChild(this.container); + addClass(this.paneElement, 'dragged-over'); + this._register(toDisposable(() => { + this.paneElement.removeChild(this.container); + removeClass(this.paneElement, 'dragged-over'); + })); + + // Overlay + this.overlay = document.createElement('div'); + addClass(this.overlay, 'pane-overlay-indicator'); + this.container.appendChild(this.overlay); + + // Overlay Event Handling + this.registerListeners(); + + // Styles + this.updateStyles(); + } + + protected updateStyles(): void { + + // Overlay drop background + this.overlay.style.backgroundColor = this.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND) || ''; + + // Overlay contrast border (if any) + const activeContrastBorderColor = this.getColor(activeContrastBorder); + this.overlay.style.outlineColor = activeContrastBorderColor || ''; + this.overlay.style.outlineOffset = activeContrastBorderColor ? '-2px' : ''; + this.overlay.style.outlineStyle = activeContrastBorderColor ? 'dashed' : ''; + this.overlay.style.outlineWidth = activeContrastBorderColor ? '2px' : ''; + + this.overlay.style.borderColor = activeContrastBorderColor || ''; + this.overlay.style.borderStyle = 'solid' || ''; + this.overlay.style.borderWidth = '0px'; + } + + private registerListeners(): void { + this._register(new DragAndDropObserver(this.container, { + onDragEnter: e => undefined, + onDragOver: e => { + + // Position overlay + this.positionOverlay(e.offsetX, e.offsetY); + + // Make sure to stop any running cleanup scheduler to remove the overlay + if (this.cleanupOverlayScheduler.isScheduled()) { + this.cleanupOverlayScheduler.cancel(); + } + }, + + onDragLeave: e => this.dispose(), + onDragEnd: e => this.dispose(), + + onDrop: e => { + // Dispose overlay + this.dispose(); + } + })); + + this._register(addDisposableListener(this.container, EventType.MOUSE_OVER, () => { + // Under some circumstances we have seen reports where the drop overlay is not being + // cleaned up and as such the editor area remains under the overlay so that you cannot + // type into the editor anymore. This seems related to using VMs and DND via host and + // guest OS, though some users also saw it without VMs. + // To protect against this issue we always destroy the overlay as soon as we detect a + // mouse event over it. The delay is used to guarantee we are not interfering with the + // actual DROP event that can also trigger a mouse over event. + if (!this.cleanupOverlayScheduler.isScheduled()) { + this.cleanupOverlayScheduler.schedule(); + } + })); + } + + private positionOverlay(mousePosX: number, mousePosY: number): void { + const paneWidth = this.paneElement.clientWidth; + const paneHeight = this.paneElement.clientHeight; + + const splitWidthThreshold = paneWidth / 2; + const splitHeightThreshold = paneHeight / 2; + + let dropDirection: DropDirection | undefined; + + if (this.orientation === Orientation.VERTICAL) { + if (mousePosY < splitHeightThreshold) { + dropDirection = DropDirection.UP; + } else if (mousePosY >= splitHeightThreshold) { + dropDirection = DropDirection.DOWN; + } + } else if (this.orientation === Orientation.HORIZONTAL) { + if (mousePosX < splitWidthThreshold) { + dropDirection = DropDirection.LEFT; + } else if (mousePosX >= splitWidthThreshold) { + dropDirection = DropDirection.RIGHT; + } + } + + // Draw overlay based on split direction + switch (dropDirection) { + case DropDirection.UP: + this.doPositionOverlay({ top: '0', left: '0', width: '100%', height: '50%' }); + break; + case DropDirection.DOWN: + this.doPositionOverlay({ bottom: '0', left: '0', width: '100%', height: '50%' }); + break; + case DropDirection.LEFT: + this.doPositionOverlay({ top: '0', left: '0', width: '50%', height: '100%' }); + break; + case DropDirection.RIGHT: + this.doPositionOverlay({ top: '0', right: '0', width: '50%', height: '100%' }); + break; + default: + this.doPositionOverlay({ top: '0', left: '0', width: '100%', height: '100%' }); + } + + if ((this.orientation === Orientation.VERTICAL && paneHeight <= 25) || + (this.orientation === Orientation.HORIZONTAL && paneWidth <= 25)) { + this.doUpdateOverlayBorder(dropDirection); + } else { + this.doUpdateOverlayBorder(undefined); + } + + // Make sure the overlay is visible now + this.overlay.style.opacity = '1'; + + // Enable transition after a timeout to prevent initial animation + setTimeout(() => addClass(this.overlay, 'overlay-move-transition'), 0); + + // Remember as current split direction + this._currentDropOperation = dropDirection; + } + + private doUpdateOverlayBorder(direction: DropDirection | undefined): void { + this.overlay.style.borderTopWidth = direction === DropDirection.UP ? '2px' : '0px'; + this.overlay.style.borderLeftWidth = direction === DropDirection.LEFT ? '2px' : '0px'; + this.overlay.style.borderBottomWidth = direction === DropDirection.DOWN ? '2px' : '0px'; + this.overlay.style.borderRightWidth = direction === DropDirection.RIGHT ? '2px' : '0px'; + } + + private doPositionOverlay(options: { top?: string, bottom?: string, left?: string, right?: string, width: string, height: string }): void { + + // Container + this.container.style.height = '100%'; + + // Overlay + this.overlay.style.top = options.top || ''; + this.overlay.style.left = options.left || ''; + this.overlay.style.bottom = options.bottom || ''; + this.overlay.style.right = options.right || ''; + this.overlay.style.width = options.width; + this.overlay.style.height = options.height; + } + + + contains(element: HTMLElement): boolean { + return element === this.container || element === this.overlay; + } + + dispose(): void { + super.dispose(); + + this._disposed = true; + } +} + export class ViewPaneContainer extends Component implements IViewPaneContainer { readonly viewContainer: ViewContainer; @@ -483,8 +737,6 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { private paneItems: IViewPaneItem[] = []; private paneview?: PaneView; - private static viewTransfer = LocalSelectionTransfer.getInstance(); - private visible: boolean = false; private areExtensionsReady: boolean = false; @@ -551,10 +803,6 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { throw new Error('Could not find container'); } - // Use default pane dnd controller if not specified - if (!this.options.dnd) { - this.options.dnd = new DefaultPaneDndController(); - } this.viewContainer = container; this.visibleViewsStorageId = `${id}.numberOfVisibleViews`; @@ -564,10 +812,77 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } create(parent: HTMLElement): void { + const options = this.options as IPaneViewOptions; + options.orientation = this.orientation; this.paneview = this._register(new PaneView(parent, this.options)); this._register(this.paneview.onDidDrop(({ from, to }) => this.movePane(from as ViewPane, to as ViewPane))); this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, (e: MouseEvent) => this.showContextMenu(new StandardMouseEvent(e)))); + let overlay: ViewPaneDropOverlay | undefined; + this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(parent, { + onDragEnter: (e) => { + if (!overlay && this.panes.length === 0) { + const dropData = e.dragAndDropData.getData(); + if (dropData.type === 'view') { + + const oldViewContainer = this.viewDescriptorService.getViewContainer(dropData.id); + const viewDescriptor = this.viewDescriptorService.getViewDescriptor(dropData.id); + + if (oldViewContainer !== this.viewContainer && (!viewDescriptor || !viewDescriptor.canMoveView)) { + return; + } + + overlay = new ViewPaneDropOverlay(parent, undefined, this.themeService); + } + + if (dropData.type === 'composite' && dropData.id !== this.viewContainer.id) { + const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); + + const container = viewContainerRegistry.get(dropData.id)!; + const viewsToMove = this.viewDescriptorService.getViewDescriptors(container).allViewDescriptors; + + if (viewsToMove.length === 1 && viewsToMove[0].canMoveView) { + overlay = new ViewPaneDropOverlay(parent, undefined, this.themeService); + } + } + + } + }, + onDragLeave: (e) => { + overlay?.dispose(); + overlay = undefined; + }, + onDrop: (e) => { + if (overlay) { + const dropData = e.dragAndDropData.getData(); + + if (dropData.type === 'composite' && dropData.id !== this.viewContainer.id) { + const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); + + const container = viewContainerRegistry.get(dropData.id)!; + const viewsToMove = this.viewDescriptorService.getViewDescriptors(container).allViewDescriptors; + + if (viewsToMove.length === 1 && viewsToMove[0].canMoveView) { + dropData.type = 'view'; + dropData.id = viewsToMove[0].id; + } + } + + if (dropData.type === 'view') { + + const oldViewContainer = this.viewDescriptorService.getViewContainer(dropData.id); + const viewDescriptor = this.viewDescriptorService.getViewDescriptor(dropData.id); + if (oldViewContainer !== this.viewContainer && viewDescriptor && viewDescriptor.canMoveView) { + this.viewDescriptorService.moveViewsToContainer([viewDescriptor], this.viewContainer); + } + } + } + + overlay?.dispose(); + overlay = undefined; + } + })); + this._register(this.onDidSashChange(() => this.saveViewSizes())); this.viewsModel.onDidAdd(added => this.onDidAddViewDescriptors(added)); this.viewsModel.onDidRemove(removed => this.onDidRemoveViewDescriptors(removed)); @@ -694,8 +1009,20 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } } + private get orientation(): Orientation { + if (this.viewDescriptorService.getViewContainerLocation(this.viewContainer) === ViewContainerLocation.Sidebar) { + return Orientation.VERTICAL; + } else { + return this.layoutService.getPanelPosition() === Position.BOTTOM ? Orientation.HORIZONTAL : Orientation.VERTICAL; + } + } + layout(dimension: Dimension): void { if (this.paneview) { + if (this.paneview.orientation !== this.orientation) { + this.paneview.flipOrientation(dimension.height, dimension.width); + } + this.paneview.layout(dimension.height, dimension.width); } @@ -789,7 +1116,11 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { if (this.dimension) { const totalWeight = this.viewsModel.visibleViewDescriptors.reduce((totalWeight, { weight }) => totalWeight + (weight || 20), 0); for (const viewDescriptor of this.viewsModel.visibleViewDescriptors) { - sizes.set(viewDescriptor.id, this.dimension.height * (viewDescriptor.weight || 20) / totalWeight); + if (this.orientation === Orientation.VERTICAL) { + sizes.set(viewDescriptor.id, this.dimension.height * (viewDescriptor.weight || 20) / totalWeight); + } else { + sizes.set(viewDescriptor.id, this.dimension.width * (viewDescriptor.weight || 20) / totalWeight); + } } } return sizes; @@ -837,8 +1168,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { { id: viewDescriptor.id, title: viewDescriptor.name, - expanded: !collapsed, - minimumBodySize: this.viewDescriptorService.getViewContainerLocation(this.viewContainer) === ViewContainerLocation.Panel ? 0 : 120 + expanded: !collapsed }); pane.render(); @@ -908,6 +1238,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { headerForeground: SIDE_BAR_SECTION_HEADER_FOREGROUND, headerBackground: SIDE_BAR_SECTION_HEADER_BACKGROUND, headerBorder: SIDE_BAR_SECTION_HEADER_BORDER, + leftBorder: PANEL_BORDER, dropBackground: SIDE_BAR_DRAG_AND_DROP_BACKGROUND }, pane); const disposable = combinedDisposable(onDidFocus, onDidChangeTitleArea, paneStyler, onDidChange, onDidChangeVisibility); @@ -916,19 +1247,104 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { this.paneItems.splice(index, 0, paneItem); assertIsDefined(this.paneview).addPane(pane, size, index); - this._register(addDisposableListener(pane.draggableElement, EventType.DRAG_START, (e: DragEvent) => { - if (e.dataTransfer) { - e.dataTransfer.effectAllowed = 'move'; - } + let overlay: ViewPaneDropOverlay | undefined; - // Register as dragged to local transfer - ViewPaneContainer.viewTransfer.setData([new DraggedViewIdentifier(pane.id)], DraggedViewIdentifier.prototype); - })); + this._register(CompositeDragAndDropObserver.INSTANCE.registerDraggable(pane.draggableElement, () => { return { type: 'view', id: pane.id }; }, {})); + this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(pane.dropTargetElement, { + onDragEnter: (e) => { + if (!overlay) { + const dropData = e.dragAndDropData.getData(); + if (dropData.type === 'view' && dropData.id !== pane.id) { - this._register(addDisposableListener(pane.draggableElement, EventType.DRAG_END, (e: DragEvent) => { - if (ViewPaneContainer.viewTransfer.hasData(DraggedViewIdentifier.prototype)) { - ViewPaneContainer.viewTransfer.clearData(DraggedViewIdentifier.prototype); + const oldViewContainer = this.viewDescriptorService.getViewContainer(dropData.id); + const viewDescriptor = this.viewDescriptorService.getViewDescriptor(dropData.id); + + if (oldViewContainer !== this.viewContainer && (!viewDescriptor || !viewDescriptor.canMoveView)) { + return; + } + + overlay = new ViewPaneDropOverlay(pane.dropTargetElement, this.options.orientation ?? Orientation.VERTICAL, this.themeService); + } + + if (dropData.type === 'composite' && dropData.id !== this.viewContainer.id) { + const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); + + const container = viewContainerRegistry.get(dropData.id)!; + const viewsToMove = this.viewDescriptorService.getViewDescriptors(container).allViewDescriptors; + + if (viewsToMove.length === 1 && viewsToMove[0].canMoveView) { + overlay = new ViewPaneDropOverlay(pane.dropTargetElement, this.options.orientation ?? Orientation.VERTICAL, this.themeService); + } + } + + } + }, + onDragLeave: (e) => { + overlay?.dispose(); + overlay = undefined; + }, + onDrop: (e) => { + if (overlay) { + const dropData = e.dragAndDropData.getData(); + + if (dropData.type === 'composite' && dropData.id !== this.viewContainer.id) { + const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); + + const container = viewContainerRegistry.get(dropData.id)!; + const viewsToMove = this.viewDescriptorService.getViewDescriptors(container).allViewDescriptors; + + if (viewsToMove.length === 1 && viewsToMove[0].canMoveView) { + dropData.type = 'view'; + dropData.id = viewsToMove[0].id; + } + } + + if (dropData.type === 'view') { + + const oldViewContainer = this.viewDescriptorService.getViewContainer(dropData.id); + const viewDescriptor = this.viewDescriptorService.getViewDescriptor(dropData.id); + if (oldViewContainer !== this.viewContainer && viewDescriptor && viewDescriptor.canMoveView) { + this.viewDescriptorService.moveViewsToContainer([viewDescriptor], this.viewContainer); + } + + if (overlay.currentDropOperation === DropDirection.DOWN || + overlay.currentDropOperation === DropDirection.RIGHT) { + + const fromIndex = this.panes.findIndex(p => p.id === dropData.id); + let toIndex = this.panes.findIndex(p => p.id === pane.id); + + if (fromIndex >= 0 && toIndex >= 0) { + if (fromIndex > toIndex) { + toIndex++; + } + + if (toIndex < this.panes.length && toIndex !== fromIndex) { + this.movePane(this.panes[fromIndex], this.panes[toIndex]); + } + } + } + + if (overlay.currentDropOperation === DropDirection.UP || + overlay.currentDropOperation === DropDirection.LEFT) { + const fromIndex = this.panes.findIndex(p => p.id === dropData.id); + let toIndex = this.panes.findIndex(p => p.id === pane.id); + + if (fromIndex >= 0 && toIndex >= 0) { + if (fromIndex < toIndex) { + toIndex--; + } + + if (toIndex >= 0 && toIndex !== fromIndex) { + this.movePane(this.panes[fromIndex], this.panes[toIndex]); + } + } + } + } + } + + overlay?.dispose(); + overlay = undefined; } })); } diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index 5480d538e6a..873eeb95862 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/views'; import { Disposable, IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IViewDescriptorService, ViewContainer, IViewDescriptor, IViewContainersRegistry, Extensions as ViewExtensions, IView, ViewContainerLocation, IViewsService, IViewPaneContainer, getVisbileViewContextKey } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Event, Emitter } from 'vs/base/common/event'; @@ -17,17 +17,15 @@ import { MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/act import { localize } from 'vs/nls'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { values } from 'vs/base/common/map'; -import { IFileIconTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { toggleClass, addClass } from 'vs/base/browser/dom'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { VIEW_ID as SEARCH_VIEW_ID } from 'vs/workbench/services/search/common/search'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { PaneCompositePanel, PanelRegistry, PanelDescriptor, Extensions as PanelExtensions } from 'vs/workbench/browser/panel'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IFileIconTheme } from 'vs/platform/theme/common/themeService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -35,6 +33,8 @@ import { Viewlet, ViewletDescriptor, ViewletRegistry, Extensions as ViewletExten import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { URI } from 'vs/base/common/uri'; +import { IProgressIndicator } from 'vs/platform/progress/common/progress'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; export interface IViewState { visibleGlobal: boolean | undefined; @@ -102,30 +102,45 @@ export class ContributableViewsModel extends Disposable { } setVisible(id: string, visible: boolean, size?: number): void { - const { visibleIndex, viewDescriptor, state } = this.find(id); + this.doSetVisible([{ id, visible, size }]); + } - if (!viewDescriptor.canToggleVisibility) { - throw new Error(`Can't toggle this view's visibility`); + protected doSetVisible(viewDescriptors: { id: string, visible: boolean, size?: number }[]): void { + const added: IAddedViewDescriptorRef[] = []; + const removed: IViewDescriptorRef[] = []; + + for (const { visibleIndex, viewDescriptor, state, visible, size } of viewDescriptors.map(({ id, visible, size }) => ({ ...this.find(id), visible, size }))) { + + if (!viewDescriptor.canToggleVisibility) { + throw new Error(`Can't toggle this view's visibility`); + } + + if (this.isViewDescriptorVisible(viewDescriptor) === visible) { + return; + } + + if (viewDescriptor.workspace) { + state.visibleWorkspace = visible; + } else { + state.visibleGlobal = visible; + } + + if (typeof size === 'number') { + state.size = size; + } + + if (visible) { + added.push({ index: visibleIndex, viewDescriptor, size: state.size, collapsed: !!state.collapsed }); + } else { + removed.push({ index: visibleIndex, viewDescriptor }); + } } - if (this.isViewDescriptorVisible(viewDescriptor) === visible) { - return; + if (added.length) { + this._onDidAdd.fire(added); } - - if (viewDescriptor.workspace) { - state.visibleWorkspace = visible; - } else { - state.visibleGlobal = visible; - } - - if (typeof size === 'number') { - state.size = size; - } - - if (visible) { - this._onDidAdd.fire([{ index: visibleIndex, viewDescriptor, size: state.size, collapsed: !!state.collapsed }]); - } else { - this._onDidRemove.fire([{ index: visibleIndex, viewDescriptor }]); + if (removed.length) { + this._onDidRemove.fire(removed); } } @@ -319,15 +334,17 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { viewletStateStorageId: string, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IStorageService storageService: IStorageService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService ) { const globalViewsStateStorageId = `${viewletStateStorageId}.hidden`; + storageKeysSyncRegistryService.registerStorageKey({ key: globalViewsStateStorageId, version: 1 }); const viewStates = PersistentContributableViewsModel.loadViewsStates(viewletStateStorageId, globalViewsStateStorageId, storageService); super(container, viewDescriptorService, viewStates); + this.storageService = storageService; this.workspaceViewsStateStorageId = viewletStateStorageId; this.globalViewsStateStorageId = globalViewsStateStorageId; - this.storageService = storageService; this._register(Event.any( this.onDidAdd, @@ -335,6 +352,29 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { Event.map(this.onDidMove, ({ from, to }) => [from, to]), Event.map(this.onDidChangeViewState, viewDescriptorRef => [viewDescriptorRef])) (viewDescriptorRefs => this.saveViewsStates())); + + this._globalViewsStatesValue = this.getStoredGlobalViewsStatesValue(); + this._register(this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e))); + } + + private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void { + if (e.key === this.globalViewsStateStorageId && e.scope === StorageScope.GLOBAL + && this.globalViewsStatesValue !== this.getStoredGlobalViewsStatesValue() /* This checks if current window changed the value or not */) { + this._globalViewsStatesValue = undefined; + const storedViewsVisibilityStates = PersistentContributableViewsModel.loadGlobalViewsState(this.globalViewsStateStorageId, this.storageService, StorageScope.GLOBAL); + const changedViews: { id: string, visible: boolean }[] = []; + for (const [id, state] of storedViewsVisibilityStates) { + const viewState = this.viewStates.get(id); + if (viewState) { + if (viewState.visibleGlobal !== !state.isHidden) { + changedViews.push({ id, visible: !state.isHidden }); + } + } + } + if (changedViews.length) { + this.doSetVisible(changedViews); + } + } } private saveViewsStates(): void { @@ -373,9 +413,32 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { order: !viewDescriptor.workspace && viewState ? viewState.order : undefined }); } - this.storageService.store(this.globalViewsStateStorageId, JSON.stringify(values(storedViewsVisibilityStates)), StorageScope.GLOBAL); + this.globalViewsStatesValue = JSON.stringify(values(storedViewsVisibilityStates)); } + private _globalViewsStatesValue: string | undefined; + private get globalViewsStatesValue(): string { + if (!this._globalViewsStatesValue) { + this._globalViewsStatesValue = this.getStoredGlobalViewsStatesValue(); + } + + return this._globalViewsStatesValue; + } + + private set globalViewsStatesValue(globalViewsStatesValue: string) { + if (this.globalViewsStatesValue !== globalViewsStatesValue) { + this._globalViewsStatesValue = globalViewsStatesValue; + this.setStoredGlobalViewsStatesValue(globalViewsStatesValue); + } + } + + private getStoredGlobalViewsStatesValue(): string { + return this.storageService.get(this.globalViewsStateStorageId, StorageScope.GLOBAL, '[]'); + } + + private setStoredGlobalViewsStatesValue(value: string): void { + this.storageService.store(this.globalViewsStateStorageId, value, StorageScope.GLOBAL); + } private static loadViewsStates(workspaceViewsStateStorageId: string, globalViewsStateStorageId: string, storageService: IStorageService): Map { const viewStates = new Map(); @@ -466,6 +529,8 @@ export class ViewsService extends Disposable implements IViewsService { private readonly visibleViewContextKeys: Map>; + private readonly viewPaneContainers: Map; + constructor( @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, @IPanelService private readonly panelService: IPanelService, @@ -477,6 +542,7 @@ export class ViewsService extends Disposable implements IViewsService { this.viewContainersRegistry = Registry.as(ViewExtensions.ViewContainersRegistry); this.viewDisposable = new Map(); this.visibleViewContextKeys = new Map>(); + this.viewPaneContainers = new Map(); this._register(toDisposable(() => { this.viewDisposable.forEach(disposable => disposable.dispose()); @@ -485,13 +551,16 @@ export class ViewsService extends Disposable implements IViewsService { this.viewContainersRegistry.all.forEach(viewContainer => this.onDidRegisterViewContainer(viewContainer, this.viewContainersRegistry.getViewContainerLocation(viewContainer))); this._register(this.viewContainersRegistry.onDidRegister(({ viewContainer, viewContainerLocation }) => this.onDidRegisterViewContainer(viewContainer, viewContainerLocation))); + + this._register(this.viewContainersRegistry.onDidDeregister(e => this.viewPaneContainers.delete(e.viewContainer.id))); } - registerViewPaneContainer(viewPaneContainer: ViewPaneContainer): ViewPaneContainer { + private registerViewPaneContainer(viewPaneContainer: ViewPaneContainer): void { this._register(viewPaneContainer.onDidAddViews(views => this.onViewsAdded(views))); this._register(viewPaneContainer.onDidChangeViewVisibility(view => this.onViewsVisibilityChanged(view, view.isBodyVisible()))); this._register(viewPaneContainer.onDidRemoveViews(views => this.onViewsRemoved(views))); - return viewPaneContainer; + + this.viewPaneContainers.set(viewPaneContainer.getId(), viewPaneContainer); } private onViewsAdded(added: IView[]): void { @@ -521,7 +590,8 @@ export class ViewsService extends Disposable implements IViewsService { return contextKey; } - private onDidRegisterViewContainer(viewContainer: ViewContainer, location: ViewContainerLocation): void { + private onDidRegisterViewContainer(viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation): void { + this.registerViewletOrPanel(viewContainer, viewContainerLocation); const viewDescriptorCollection = this.viewDescriptorService.getViewDescriptors(viewContainer); this.onViewDescriptorsAdded(viewDescriptorCollection.allViewDescriptors, viewContainer); this._register(viewDescriptorCollection.onDidChangeViews(({ added, removed }) => { @@ -564,33 +634,28 @@ export class ViewsService extends Disposable implements IViewsService { } })); - const newLocation = location === ViewContainerLocation.Panel ? ViewContainerLocation.Sidebar : ViewContainerLocation.Panel; - disposables.add(registerAction2(class MoveViewAction extends Action2 { + disposables.add(registerAction2(class ResetViewLocationAction extends Action2 { constructor() { super({ - id: `${viewDescriptor.id}.moveView`, + id: `${viewDescriptor.id}.resetViewLocation`, title: { - original: newLocation === ViewContainerLocation.Sidebar ? 'Move to Sidebar' : 'Move to Panel', - value: newLocation === ViewContainerLocation.Sidebar ? localize('moveViewToSidebar', "Move to Sidebar") : localize('moveViewToPanel', "Move to Panel") + original: 'Reset View Location', + value: localize('resetViewLocation', "Reset View Location") }, menu: [{ id: MenuId.ViewTitleContext, when: ContextKeyExpr.or( ContextKeyExpr.and( ContextKeyExpr.equals('view', viewDescriptor.id), - ContextKeyExpr.has(`${viewDescriptor.id}.canMove`), - ContextKeyExpr.equals('config.workbench.view.experimental.allowMovingToNewContainer', true)), - ContextKeyExpr.and( - ContextKeyExpr.equals('view', viewDescriptor.id), - ContextKeyExpr.has(`${viewDescriptor.id}.canMove`), - ContextKeyExpr.equals('view', SEARCH_VIEW_ID) + ContextKeyExpr.equals(`${viewDescriptor.id}.defaultViewLocation`, false) ) ) }], }); } run(accessor: ServicesAccessor): void { - accessor.get(IViewDescriptorService).moveViewToLocation(viewDescriptor, newLocation); + const viewDescriptorService = accessor.get(IViewDescriptorService); + viewDescriptorService.moveViewsToContainer([viewDescriptor], viewDescriptorService.getDefaultContainer(viewDescriptor.id)!); accessor.get(IViewsService).openView(viewDescriptor.id, true); } })); @@ -699,9 +764,90 @@ export class ViewsService extends Disposable implements IViewsService { return null; } + + getProgressIndicator(id: string): IProgressIndicator | undefined { + const viewContainer = this.viewDescriptorService.getViewContainer(id); + if (viewContainer === null) { + return undefined; + } + + const view = this.viewPaneContainers.get(viewContainer.id)?.getView(id); + return view?.getProgressIndicator(); + } + + private registerViewletOrPanel(viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation): void { + switch (viewContainerLocation) { + case ViewContainerLocation.Panel: + this.registerPanel(viewContainer); + break; + case ViewContainerLocation.Sidebar: + if (viewContainer.ctorDescriptor) { + this.registerViewlet(viewContainer); + } + break; + } + } + + private registerPanel(viewContainer: ViewContainer): void { + const that = this; + class PaneContainerPanel extends PaneCompositePanel { + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IStorageService storageService: IStorageService, + @IInstantiationService instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IContextMenuService contextMenuService: IContextMenuService, + @IExtensionService extensionService: IExtensionService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + ) { + // Use composite's instantiation service to get the editor progress service for any editors instantiated within the composite + const viewPaneContainer = (instantiationService as any).createInstance(viewContainer.ctorDescriptor!.ctor, ...(viewContainer.ctorDescriptor!.staticArguments || [])); + super(viewContainer.id, viewPaneContainer, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); + that.registerViewPaneContainer(this.viewPaneContainer); + } + } + Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( + PaneContainerPanel, + viewContainer.id, + viewContainer.name, + isString(viewContainer.icon) ? viewContainer.icon : undefined, + viewContainer.order, + viewContainer.focusCommand?.id, + )); + } + + private registerViewlet(viewContainer: ViewContainer): void { + const that = this; + class PaneContainerViewlet extends Viewlet { + constructor( + @IConfigurationService configurationService: IConfigurationService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ITelemetryService telemetryService: ITelemetryService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IStorageService storageService: IStorageService, + @IInstantiationService instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IContextMenuService contextMenuService: IContextMenuService, + @IExtensionService extensionService: IExtensionService, + ) { + // Use composite's instantiation service to get the editor progress service for any editors instantiated within the composite + const viewPaneContainer = (instantiationService as any).createInstance(viewContainer.ctorDescriptor!.ctor, ...(viewContainer.ctorDescriptor!.staticArguments || [])); + super(viewContainer.id, viewPaneContainer, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, layoutService, configurationService); + that.registerViewPaneContainer(this.viewPaneContainer); + } + } + Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( + PaneContainerViewlet, + viewContainer.id, + viewContainer.name, + isString(viewContainer.icon) ? viewContainer.icon : undefined, + viewContainer.order, + viewContainer.icon instanceof URI ? viewContainer.icon : undefined + )); + } } -export function createFileIconThemableTreeContainerScope(container: HTMLElement, themeService: IWorkbenchThemeService): IDisposable { +export function createFileIconThemableTreeContainerScope(container: HTMLElement, themeService: IThemeService): IDisposable { addClass(container, 'file-icon-themable-tree'); addClass(container, 'show-file-icons'); @@ -715,79 +861,3 @@ export function createFileIconThemableTreeContainerScope(container: HTMLElement, } registerSingleton(IViewsService, ViewsService); - -// Viewlets & Panels -(function registerViewletsAndPanels(): void { - const registerPanel = (viewContainer: ViewContainer): void => { - class PaneContainerPanel extends PaneCompositePanel { - constructor( - @ITelemetryService telemetryService: ITelemetryService, - @IStorageService storageService: IStorageService, - @IInstantiationService instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, - @IContextMenuService contextMenuService: IContextMenuService, - @IExtensionService extensionService: IExtensionService, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IViewsService viewsService: ViewsService - ) { - // Use composite's instantiation service to get the editor progress service for any editors instantiated within the composite - const viewPaneContainer = viewsService.registerViewPaneContainer((instantiationService as any).createInstance(viewContainer.ctorDescriptor!.ctor, ...(viewContainer.ctorDescriptor!.staticArguments || []))); - super(viewContainer.id, viewPaneContainer, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); - } - } - Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( - PaneContainerPanel, - viewContainer.id, - viewContainer.name, - isString(viewContainer.icon) ? viewContainer.icon : undefined, - viewContainer.order, - viewContainer.focusCommand?.id, - )); - }; - - const registerViewlet = (viewContainer: ViewContainer): void => { - class PaneContainerViewlet extends Viewlet { - constructor( - @IConfigurationService configurationService: IConfigurationService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @ITelemetryService telemetryService: ITelemetryService, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IStorageService storageService: IStorageService, - @IInstantiationService instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, - @IContextMenuService contextMenuService: IContextMenuService, - @IExtensionService extensionService: IExtensionService, - @IViewsService viewsService: ViewsService - ) { - // Use composite's instantiation service to get the editor progress service for any editors instantiated within the composite - const viewPaneContainer = viewsService.registerViewPaneContainer((instantiationService as any).createInstance(viewContainer.ctorDescriptor!.ctor, ...(viewContainer.ctorDescriptor!.staticArguments || []))); - super(viewContainer.id, viewPaneContainer, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, layoutService, configurationService); - } - } - const viewletDescriptor = ViewletDescriptor.create( - PaneContainerViewlet, - viewContainer.id, - viewContainer.name, - isString(viewContainer.icon) ? viewContainer.icon : undefined, - viewContainer.order, - viewContainer.icon instanceof URI ? viewContainer.icon : undefined - ); - - Registry.as(ViewletExtensions.Viewlets).registerViewlet(viewletDescriptor); - }; - - const viewContainerRegistry = Registry.as(ViewExtensions.ViewContainersRegistry); - viewContainerRegistry.getViewContainers(ViewContainerLocation.Panel).forEach(viewContainer => registerPanel(viewContainer)); - viewContainerRegistry.onDidRegister(({ viewContainer, viewContainerLocation }) => { - switch (viewContainerLocation) { - case ViewContainerLocation.Panel: - registerPanel(viewContainer); - return; - case ViewContainerLocation.Sidebar: - if (viewContainer.ctorDescriptor) { - registerViewlet(viewContainer); - } - return; - } - }); -})(); diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index b3a0ef0e024..8588ffe9aac 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as DOM from 'vs/base/browser/dom'; import { IAction } from 'vs/base/common/actions'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -13,11 +12,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ViewPaneContainer, ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { WorkbenchTree, IListService } from 'vs/platform/list/browser/listService'; -import { IWorkbenchThemeService, IFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { ITreeConfiguration, ITreeOptions } from 'vs/base/parts/tree/browser/tree'; import { Event } from 'vs/base/common/event'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; @@ -146,30 +141,3 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { abstract getTitle(): string; } - -export class FileIconThemableWorkbenchTree extends WorkbenchTree { - - constructor( - container: HTMLElement, - configuration: ITreeConfiguration, - options: ITreeOptions, - @IContextKeyService contextKeyService: IContextKeyService, - @IListService listService: IListService, - @IThemeService themeService: IWorkbenchThemeService, - @IConfigurationService configurationService: IConfigurationService, - @IInstantiationService instantiationService: IInstantiationService - ) { - super(container, configuration, { ...options, ...{ showTwistie: false, twistiePixels: 12 } }, contextKeyService, listService, themeService, instantiationService, configurationService); - - DOM.addClass(container, 'file-icon-themable-tree'); - DOM.addClass(container, 'show-file-icons'); - - const onFileIconThemeChange = (fileIconTheme: IFileIconTheme) => { - DOM.toggleClass(container, 'align-icons-and-twisties', fileIconTheme.hasFileIcons && !fileIconTheme.hasFolderIcons); - DOM.toggleClass(container, 'hide-arrows', fileIconTheme.hidesExplorerArrows === true); - }; - - this.disposables.push(themeService.onDidFileIconThemeChange(onFileIconThemeChange)); - onFileIconThemeChange(themeService.getFileIconTheme()); - } -} diff --git a/src/vs/workbench/browser/quickaccess.ts b/src/vs/workbench/browser/quickaccess.ts new file mode 100644 index 00000000000..9cdc4544088 --- /dev/null +++ b/src/vs/workbench/browser/quickaccess.ts @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ICommandHandler } from 'vs/platform/commands/common/commands'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; + +export const inQuickPickContextKeyValue = 'inQuickOpen'; +export const InQuickPickContextKey = new RawContextKey(inQuickPickContextKeyValue, false); +export const inQuickPickContext = ContextKeyExpr.has(inQuickPickContextKeyValue); + +export const defaultQuickAccessContextKeyValue = 'inFilesPicker'; +export const defaultQuickAccessContext = ContextKeyExpr.and(inQuickPickContext, ContextKeyExpr.has(defaultQuickAccessContextKeyValue)); + +export interface IWorkbenchQuickAccessConfiguration { + workbench: { + commandPalette: { + history: number; + preserveInput: boolean; + }, + quickOpen: { + enableExperimentalNewVersion: boolean; + preserveInput: boolean; + } + }; +} + +export function getQuickNavigateHandler(id: string, next?: boolean): ICommandHandler { + return accessor => { + const keybindingService = accessor.get(IKeybindingService); + const quickInputService = accessor.get(IQuickInputService); + + const keys = keybindingService.lookupKeybindings(id); + const quickNavigate = { keybindings: keys }; + + quickInputService.navigate(!!next, quickNavigate); + }; +} diff --git a/src/vs/workbench/browser/quickopen.ts b/src/vs/workbench/browser/quickopen.ts deleted file mode 100644 index 2dda51f6ef2..00000000000 --- a/src/vs/workbench/browser/quickopen.ts +++ /dev/null @@ -1,339 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { localize } from 'vs/nls'; -import { mixin, assign } from 'vs/base/common/objects'; -import { first } from 'vs/base/common/arrays'; -import { startsWith } from 'vs/base/common/strings'; -import { isString, assertIsDefined, withNullAsUndefined } from 'vs/base/common/types'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { Action } from 'vs/base/common/actions'; -import { Mode, IEntryRunContext, IAutoFocus, IModel, IQuickNavigateConfiguration } from 'vs/base/parts/quickopen/common/quickOpen'; -import { QuickOpenEntry, QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { EditorOptions, EditorInput, IEditorInput } from 'vs/workbench/common/editor'; -import { IResourceInput, IEditorOptions } from 'vs/platform/editor/common/editor'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; -import { IConstructorSignature0, IInstantiationService, BrandedService } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { CancellationToken } from 'vs/base/common/cancellation'; - -export const CLOSE_ON_FOCUS_LOST_CONFIG = 'workbench.quickOpen.closeOnFocusLost'; -export const PRESERVE_INPUT_CONFIG = 'workbench.quickOpen.preserveInput'; -export const SEARCH_EDITOR_HISTORY = 'search.quickOpen.includeHistory'; - -export interface IWorkbenchQuickOpenConfiguration { - workbench: { - commandPalette: { - history: number; - preserveInput: boolean; - } - }; -} - -export class QuickOpenHandler { - - /** - * A quick open handler returns results for a given input string. The resolved promise - * returns an instance of quick open model. It is up to the handler to keep and reuse an - * instance of the same model across multiple calls. This helps in situations where the user is - * narrowing down a search and the model is just filtering some items out. - * - * As such, returning the same model instance across multiple searches will yield best - * results in terms of performance when many items are shown. - */ - getResults(searchValue: string, token: CancellationToken): Promise | null> { - return Promise.resolve(null); - } - - /** - * The ARIA label to apply when this quick open handler is active in quick open. - */ - getAriaLabel(): string | null { - return null; - } - - /** - * Extra CSS class name to add to the quick open widget to do custom styling of entries. - */ - getClass(): string | null { - return null; - } - - /** - * Indicates if the handler can run in the current environment. Return a string if the handler cannot run but has - * a good message to show in this case. - */ - canRun(): boolean | string { - return true; - } - - /** - * Hints to the outside that this quick open handler typically returns results fast. - */ - hasShortResponseTime(): boolean { - return false; - } - - /** - * Indicates if the handler wishes the quick open widget to automatically select the first result entry or an entry - * based on a specific prefix match. - */ - getAutoFocus(searchValue: string, context: { model: IModel, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus { - return {}; - } - - /** - * Indicates to the handler that the quick open widget has been opened. - */ - onOpen(): void { - return; - } - - /** - * Indicates to the handler that the quick open widget has been closed. Allows to free up any resources as needed. - * The parameter canceled indicates if the quick open widget was closed with an entry being run or not. - */ - onClose(canceled: boolean): void { - return; - } - - /** - * Allows to return a label that will be placed to the side of the results from this handler or null if none. - */ - getGroupLabel(): string | null { - return null; - } - - /** - * Allows to return a label that will be used when there are no results found - */ - getEmptyLabel(searchString: string): string { - if (searchString.length > 0) { - return localize('noResultsMatching', "No results matching"); - } - return localize('noResultsFound2', "No results found"); - } -} - -export interface QuickOpenHandlerHelpEntry { - prefix: string; - description: string; - needsEditor: boolean; -} - -/** - * A lightweight descriptor of a quick open handler. - */ -export class QuickOpenHandlerDescriptor { - prefix: string; - description?: string; - contextKey?: string; - helpEntries?: QuickOpenHandlerHelpEntry[]; - instantProgress: boolean; - - private id: string; - private ctor: IConstructorSignature0; - - public static create(ctor: { new(...services: Services): QuickOpenHandler }, id: string, prefix: string, contextKey: string | undefined, description: string, instantProgress?: boolean): QuickOpenHandlerDescriptor; - public static create(ctor: { new(...services: Services): QuickOpenHandler }, id: string, prefix: string, contextKey: string | undefined, helpEntries: QuickOpenHandlerHelpEntry[], instantProgress?: boolean): QuickOpenHandlerDescriptor; - public static create(ctor: { new(...services: Services): QuickOpenHandler }, id: string, prefix: string, contextKey: string | undefined, param: string | QuickOpenHandlerHelpEntry[], instantProgress: boolean = false): QuickOpenHandlerDescriptor { - return new QuickOpenHandlerDescriptor(ctor as IConstructorSignature0, id, prefix, contextKey, param, instantProgress); - } - - private constructor(ctor: IConstructorSignature0, id: string, prefix: string, contextKey: string | undefined, param: string | QuickOpenHandlerHelpEntry[], instantProgress: boolean = false) { - this.ctor = ctor; - this.id = id; - this.prefix = prefix; - this.contextKey = contextKey; - this.instantProgress = instantProgress; - - if (isString(param)) { - this.description = param; - } else { - this.helpEntries = param; - } - } - - getId(): string { - return this.id; - } - - instantiate(instantiationService: IInstantiationService): QuickOpenHandler { - return instantiationService.createInstance(this.ctor); - } -} - -export const Extensions = { - Quickopen: 'workbench.contributions.quickopen' -}; - -export interface IQuickOpenRegistry { - - /** - * Registers a quick open handler to the platform. - */ - registerQuickOpenHandler(descriptor: QuickOpenHandlerDescriptor): void; - - /** - * Registers a default quick open handler to fallback to. - */ - registerDefaultQuickOpenHandler(descriptor: QuickOpenHandlerDescriptor): void; - - /** - * Get all registered quick open handlers - */ - getQuickOpenHandlers(): QuickOpenHandlerDescriptor[]; - - /** - * Get a specific quick open handler for a given prefix. - */ - getQuickOpenHandler(prefix: string): QuickOpenHandlerDescriptor | null; - - /** - * Returns the default quick open handler. - */ - getDefaultQuickOpenHandler(): QuickOpenHandlerDescriptor; -} - -class QuickOpenRegistry implements IQuickOpenRegistry { - private handlers: QuickOpenHandlerDescriptor[] = []; - private defaultHandler: QuickOpenHandlerDescriptor | undefined; - - registerQuickOpenHandler(descriptor: QuickOpenHandlerDescriptor): void { - this.handlers.push(descriptor); - - // sort the handlers by decreasing prefix length, such that longer - // prefixes take priority: 'ext' vs 'ext install' - the latter should win - this.handlers.sort((h1, h2) => h2.prefix.length - h1.prefix.length); - } - - registerDefaultQuickOpenHandler(descriptor: QuickOpenHandlerDescriptor): void { - this.defaultHandler = descriptor; - } - - getQuickOpenHandlers(): QuickOpenHandlerDescriptor[] { - return this.handlers.slice(0); - } - - getQuickOpenHandler(text: string): QuickOpenHandlerDescriptor | null { - return text ? (first(this.handlers, h => startsWith(text, h.prefix)) || null) : null; - } - - getDefaultQuickOpenHandler(): QuickOpenHandlerDescriptor { - return assertIsDefined(this.defaultHandler); - } -} - -Registry.add(Extensions.Quickopen, new QuickOpenRegistry()); - -export interface IEditorQuickOpenEntry { - - /** - * The editor input used for this entry when opening. - */ - getInput(): IResourceInput | IEditorInput | undefined; - - /** - * The editor options used for this entry when opening. - */ - getOptions(): IEditorOptions | undefined; -} - -/** - * A subclass of quick open entry that will open an editor with input and options when running. - */ -export class EditorQuickOpenEntry extends QuickOpenEntry implements IEditorQuickOpenEntry { - - constructor(private _editorService: IEditorService) { - super(); - } - - get editorService() { - return this._editorService; - } - - getInput(): IResourceInput | IEditorInput | undefined { - return undefined; - } - - getOptions(): IEditorOptions | undefined { - return undefined; - } - - run(mode: Mode, context: IEntryRunContext): boolean { - const hideWidget = (mode === Mode.OPEN); - - if (mode === Mode.OPEN || mode === Mode.OPEN_IN_BACKGROUND) { - const sideBySide = context.keymods.ctrlCmd; - - let openOptions: IEditorOptions | undefined; - if (mode === Mode.OPEN_IN_BACKGROUND) { - openOptions = { pinned: true, preserveFocus: true }; - } else if (context.keymods.alt) { - openOptions = { pinned: true }; - } - - const input = this.getInput(); - if (input instanceof EditorInput) { - let opts = this.getOptions(); - if (opts) { - opts = mixin(opts, openOptions, true); - } else if (openOptions) { - opts = EditorOptions.create(openOptions); - } - - this.editorService.openEditor(input, withNullAsUndefined(opts), sideBySide ? SIDE_GROUP : ACTIVE_GROUP); - } else { - const resourceInput = input; - - if (openOptions) { - resourceInput.options = assign(resourceInput.options || Object.create(null), openOptions); - } - - this.editorService.openEditor(resourceInput, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); - } - } - - return hideWidget; - } -} - -/** - * A subclass of quick open entry group that provides access to editor input and options. - */ -export class EditorQuickOpenEntryGroup extends QuickOpenEntryGroup implements IEditorQuickOpenEntry { - - getInput(): IEditorInput | IResourceInput | undefined { - return undefined; - } - - getOptions(): IEditorOptions | undefined { - return undefined; - } -} - -export class QuickOpenAction extends Action { - private prefix: string; - - constructor( - id: string, - label: string, - prefix: string, - @IQuickOpenService private readonly quickOpenService: IQuickOpenService - ) { - super(id, label); - - this.prefix = prefix; - this.enabled = !!this.quickOpenService; - } - - run(): Promise { - - // Show with prefix - this.quickOpenService.show(this.prefix); - - return Promise.resolve(undefined); - } -} diff --git a/src/vs/workbench/browser/style.ts b/src/vs/workbench/browser/style.ts index ea118c2511a..c226ce43536 100644 --- a/src/vs/workbench/browser/style.ts +++ b/src/vs/workbench/browser/style.ts @@ -5,20 +5,14 @@ import 'vs/css!./media/style'; -import { registerThemingParticipant, ITheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, IColorTheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; import { iconForeground, foreground, selectionBackground, focusBorder, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listHighlightForeground, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; import { WORKBENCH_BACKGROUND, TITLE_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme'; import { isWeb, isIOS } from 'vs/base/common/platform'; import { createMetaElement } from 'vs/base/browser/dom'; import { isSafari, isStandalone } from 'vs/base/browser/browser'; -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { - - // Icon defaults - const iconForegroundColor = theme.getColor(iconForeground); - if (iconForegroundColor) { - collector.addRule(`.monaco-workbench .codicon { color: ${iconForegroundColor}; }`); - } +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { // Foreground const windowForeground = theme.getColor(foreground); @@ -26,6 +20,16 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { collector.addRule(`.monaco-workbench { color: ${windowForeground}; }`); } + // Background (We need to set the workbench background color so that on Windows we get subpixel-antialiasing) + const workbenchBackground = WORKBENCH_BACKGROUND(theme); + collector.addRule(`.monaco-workbench { background-color: ${workbenchBackground}; }`); + + // Icon defaults + const iconForegroundColor = theme.getColor(iconForeground); + if (iconForegroundColor) { + collector.addRule(`.monaco-workbench .codicon { color: ${iconForegroundColor}; }`); + } + // Selection const windowSelectionBackground = theme.getColor(selectionBackground); if (windowSelectionBackground) { @@ -51,17 +55,12 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const listHighlightForegroundColor = theme.getColor(listHighlightForeground); if (listHighlightForegroundColor) { collector.addRule(` - .monaco-workbench .monaco-tree .monaco-tree-row .monaco-highlighted-label .highlight, .monaco-workbench .monaco-list .monaco-list-row .monaco-highlighted-label .highlight { color: ${listHighlightForegroundColor}; } `); } - // We need to set the workbench background color so that on Windows we get subpixel-antialiasing. - const workbenchBackground = WORKBENCH_BACKGROUND(theme); - collector.addRule(`.monaco-workbench { background-color: ${workbenchBackground}; }`); - // Scrollbars const scrollbarShadowColor = theme.getColor(scrollbarShadow); if (scrollbarShadowColor) { @@ -115,7 +114,6 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { .monaco-workbench [tabindex="-1"]:focus, .monaco-workbench .synthetic-focus, .monaco-workbench select:focus, - .monaco-workbench .monaco-tree.focused.no-focused-item:focus:before, .monaco-workbench .monaco-list:not(.element-focused):focus:before, .monaco-workbench input[type="button"]:focus, .monaco-workbench input[type="text"]:focus, @@ -143,11 +141,6 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { outline-width: 1px; } - .hc-black .monaco-tree.focused.no-focused-item:focus:before { - outline-width: 1px; - outline-offset: -2px; - } - .hc-black .synthetic-focus input { background: transparent; /* Search input focus fix when in high contrast */ } diff --git a/src/vs/workbench/browser/viewlet.ts b/src/vs/workbench/browser/viewlet.ts index 096974a38ae..896fda4970a 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -194,10 +194,6 @@ export class ShowViewletAction extends Action { export class CollapseAction extends Action { constructor(tree: AsyncDataTree | AbstractTree, enabled: boolean, clazz?: string) { - super('workbench.action.collapse', nls.localize('collapse', "Collapse All"), clazz, enabled, () => { - tree.collapseAll(); - - return Promise.resolve(undefined); - }); + super('workbench.action.collapse', nls.localize('collapse', "Collapse All"), clazz, enabled, async () => tree.collapseAll()); } } diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 8973e3fc36c..4f1c00218a6 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -33,7 +33,6 @@ import { WorkspaceService } from 'vs/workbench/services/configuration/browser/co import { ConfigurationCache } from 'vs/workbench/services/configuration/browser/configurationCache'; import { ISignService } from 'vs/platform/sign/common/sign'; import { SignService } from 'vs/platform/sign/browser/signService'; -import { hash } from 'vs/base/common/hash'; import { IWorkbenchConstructionOptions, IWorkspace } from 'vs/workbench/workbench.web.api'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; import { BACKUPS } from 'vs/platform/environment/common/environment'; @@ -51,6 +50,7 @@ import { isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/wi import { getWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces'; import { coalesce } from 'vs/base/common/arrays'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; +import { WebResourceIdentityService, IResourceIdentityService } from 'vs/platform/resource/common/resourceIdentityService'; class BrowserMain extends Disposable { @@ -130,7 +130,7 @@ class BrowserMain extends Disposable { } private restoreBaseTheme(): void { - addClass(this.domElement, window.localStorage.getItem('vscode.baseTheme') || getThemeTypeSelector(DARK)); + addClass(this.domElement, window.localStorage.getItem('vscode.baseTheme') || getThemeTypeSelector(LIGHT) /* Fallback to a light theme by default on web */); } private saveBaseTheme(): void { @@ -157,7 +157,11 @@ class BrowserMain extends Disposable { const logService = new BufferLogService(this.configuration.logLevel); serviceCollection.set(ILogService, logService); - const payload = this.resolveWorkspaceInitializationPayload(); + // Resource Identity + const resourceIdentityService = this._register(new WebResourceIdentityService()); + serviceCollection.set(IResourceIdentityService, resourceIdentityService); + + const payload = await this.resolveWorkspaceInitializationPayload(resourceIdentityService); // Environment const environmentService = new BrowserWorkbenchEnvironmentService({ workspaceId: payload.id, logsPath, ...this.configuration }); @@ -292,7 +296,7 @@ class BrowserMain extends Disposable { } } - private resolveWorkspaceInitializationPayload(): IWorkspaceInitializationPayload { + private async resolveWorkspaceInitializationPayload(resourceIdentityService: IResourceIdentityService): Promise { let workspace: IWorkspace | undefined = undefined; if (this.configuration.workspaceProvider) { workspace = this.configuration.workspaceProvider.workspace; @@ -305,7 +309,8 @@ class BrowserMain extends Disposable { // Single-folder workspace if (workspace && isFolderToOpen(workspace)) { - return { id: hash(workspace.folderUri.toString()).toString(16), folder: workspace.folderUri }; + const id = await resourceIdentityService.resolveResourceIdentity(workspace.folderUri); + return { id, folder: workspace.folderUri }; } return { id: 'empty-window' }; diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 2fa180e6968..4ceec674a9d 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -17,6 +17,16 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio registry.registerConfiguration({ ...workbenchConfigurationNodeBase, 'properties': { + 'workbench.editor.titleScrollbarSizing': { + type: 'string', + enum: ['default', 'large'], + enumDescriptions: [ + nls.localize('workbench.editor.titleScrollbarSizing.default', "The default size."), + nls.localize('workbench.editor.titleScrollbarSizing.large', "Increases the size, so it can be grabed more easily with the mouse") + ], + description: nls.localize('tabScrollbarHeight', "Controls the height of the scrollbars used for tabs and breadcrumbs in the editor title area."), + default: 'default', + }, 'workbench.editor.showTabs': { 'type': 'boolean', 'description': nls.localize('showEditorTabs', "Controls whether opened editors should show in tabs or not."), @@ -88,7 +98,7 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio }, 'workbench.editor.showIcons': { 'type': 'boolean', - 'description': nls.localize('showIcons', "Controls whether opened editors should show with an icon or not. This requires an icon theme to be enabled as well."), + 'description': nls.localize('showIcons', "Controls whether opened editors should show with an icon or not. This requires an file icon theme to be enabled as well."), 'default': true }, 'workbench.editor.enablePreview': { @@ -221,11 +231,6 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio 'default': false, 'description': nls.localize('viewVisibility', "Controls the visibility of view header actions. View header actions may either be always visible, or only visible when that view is focused or hovered over.") }, - 'workbench.view.experimental.allowMovingToNewContainer': { - 'type': 'boolean', - 'default': true, - 'description': nls.localize('movingViewContainer', "Controls whether specific views will have a context menu entry allowing them to be moved to a new container. Currently, this setting only affects the outline view and views contributed by extensions.") - }, 'workbench.fontAliasing': { 'type': 'string', 'enum': ['default', 'antialiased', 'none', 'auto'], diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 58afab7134e..7b4b2ee2452 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -16,7 +16,6 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { isWindows, isLinux, isWeb, isNative, isMacintosh } from 'vs/base/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor'; -import { IActionBarRegistry, Extensions as ActionBarExtensions } from 'vs/workbench/browser/actions'; import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions'; import { Position, Parts, IWorkbenchLayoutService, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; import { IStorageService, WillSaveStateReason, StorageScope } from 'vs/platform/storage/common/storage'; @@ -33,7 +32,7 @@ import { NotificationsAlerts } from 'vs/workbench/browser/parts/notifications/no import { NotificationsStatus } from 'vs/workbench/browser/parts/notifications/notificationsStatus'; import { registerNotificationCommands } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; import { NotificationsToasts } from 'vs/workbench/browser/parts/notifications/notificationsToasts'; -import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { setARIAContainer } from 'vs/base/browser/ui/aria/aria'; import { readFontInfo, restoreFontInfo, serializeFontInfo } from 'vs/editor/browser/config/configuration'; @@ -132,9 +131,6 @@ export class Workbench extends Layout { // Configure emitter leak warning threshold setGlobalLeakWarningThreshold(175); - // ARIA - setARIAContainer(document.body); - // Services const instantiationService = this.initServices(this.serviceCollection); @@ -219,7 +215,6 @@ export class Workbench extends Layout { } private startRegistries(accessor: ServicesAccessor): void { - Registry.as(ActionBarExtensions.Actionbar).start(accessor); Registry.as(WorkbenchExtensions.Workbench).start(accessor); Registry.as(EditorExtensions.EditorInputFactories).start(accessor); } @@ -321,6 +316,10 @@ export class Workbench extends Layout { private renderWorkbench(instantiationService: IInstantiationService, notificationService: NotificationService, storageService: IStorageService, configurationService: IConfigurationService): void { + // ARIA + setARIAContainer(this.container); + this.container.setAttribute('role', 'application'); + // State specific classes const platformClass = isWindows ? 'windows' : isLinux ? 'linux' : 'mac'; const workbenchClasses = coalesce([ @@ -414,7 +413,7 @@ export class Workbench extends Layout { await editorGroupService.whenRestored; // then see for editors to open as instructed - let editors: IResourceEditor[]; + let editors: IResourceEditorInputType[]; if (Array.isArray(this.state.editor.editorsToOpen)) { editors = this.state.editor.editorsToOpen; } else { diff --git a/src/vs/workbench/common/actions.ts b/src/vs/workbench/common/actions.ts index 0d75f14187f..25080ac61a0 100644 --- a/src/vs/workbench/common/actions.ts +++ b/src/vs/workbench/common/actions.ts @@ -22,6 +22,7 @@ export interface IWorkbenchActionRegistry { /** * Registers a workbench action to the platform. Workbench actions are not * visible by default and can only be invoked through a keybinding if provided. + * @deprecated Register directly with KeybindingsRegistry and MenuRegistry or use registerAction2 instead. */ registerWorkbenchAction(descriptor: SyncActionDescriptor, alias: string, category?: string, when?: ContextKeyExpr): IDisposable; } diff --git a/src/vs/workbench/common/component.ts b/src/vs/workbench/common/component.ts index 4e4f68ab85e..3c1fd717ace 100644 --- a/src/vs/workbench/common/component.ts +++ b/src/vs/workbench/common/component.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Memento, MementoObject } from 'vs/workbench/common/memento'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { Themable } from 'vs/workbench/common/theme'; +import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; export class Component extends Themable { @@ -42,4 +41,4 @@ export class Component extends Themable { protected saveState(): void { // Subclasses to implement for storing state } -} \ No newline at end of file +} diff --git a/src/vs/workbench/common/contributions.ts b/src/vs/workbench/common/contributions.ts index 7d2a421a910..9625606b070 100644 --- a/src/vs/workbench/common/contributions.ts +++ b/src/vs/workbench/common/contributions.ts @@ -44,11 +44,11 @@ class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry private readonly toBeInstantiated = new Map[]>(); - registerWorkbenchContribution(ctor: new (...services: Services) => IWorkbenchContribution, phase: LifecyclePhase = LifecyclePhase.Starting): void { + registerWorkbenchContribution(ctor: IConstructorSignature0, phase: LifecyclePhase = LifecyclePhase.Starting): void { // Instantiate directly if we are already matching the provided phase if (this.instantiationService && this.lifecycleService && this.lifecycleService.phase >= phase) { - this.instantiationService.createInstance(ctor); + this.instantiationService.createInstance(ctor); } // Otherwise keep contributions by lifecycle phase diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 6e4802e474f..3e92b29acbb 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -9,22 +9,21 @@ import { assign } from 'vs/base/common/objects'; import { withNullAsUndefined, assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IEditor as ICodeEditor, IEditorViewState, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon'; -import { IEditorModel, IEditorOptions, ITextEditorOptions, IBaseResourceInput, IResourceInput, EditorActivation, EditorOpenContext, ITextEditorSelection, TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; +import { IEditor, IEditorViewState, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon'; +import { IEditorModel, IEditorOptions, ITextEditorOptions, IBaseResourceEditorInput, IResourceEditorInput, EditorActivation, EditorOpenContext, ITextEditorSelection, TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; import { IInstantiationService, IConstructorSignature0, ServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; import { ITextModel } from 'vs/editor/common/model'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { ICompositeControl } from 'vs/workbench/common/composite'; +import { ICompositeControl, IComposite } from 'vs/workbench/common/composite'; import { ActionRunner, IAction } from 'vs/base/common/actions'; import { IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { IPathData } from 'vs/platform/windows/common/windows'; import { coalesce, firstOrDefault } from 'vs/base/common/arrays'; import { ITextFileSaveOptions, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService'; import { isEqual, dirname } from 'vs/base/common/resources'; -import { IPanel } from 'vs/workbench/common/panel'; import { IRange } from 'vs/editor/common/core/range'; import { createMemoizer } from 'vs/base/common/decorators'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -61,22 +60,20 @@ export const TEXT_DIFF_EDITOR_ID = 'workbench.editors.textDiffEditor'; */ export const BINARY_DIFF_EDITOR_ID = 'workbench.editors.binaryResourceDiffEditor'; -export interface IEditor extends IPanel { +/** + * The editor pane is the container for workbench editors. + */ +export interface IEditorPane extends IComposite { /** * The assigned input of this editor. */ - input: IEditorInput | undefined; - - /** - * The assigned options of this editor. - */ - options: IEditorOptions | undefined; + readonly input: IEditorInput | undefined; /** * The assigned group this editor is showing in. */ - group: IEditorGroup | undefined; + readonly group: IEditorGroup | undefined; /** * The minimum width of this editor. @@ -104,7 +101,9 @@ export interface IEditor extends IPanel { readonly onDidSizeConstraintsChange: Event<{ width: number; height: number; } | undefined>; /** - * Returns the underlying control of this editor. + * Returns the underlying control of this editor. Callers need to cast + * the control to a specific instance as needed, e.g. by using the + * `isCodeEditor` helper method to access the text code editor. */ getControl(): IEditorControl | undefined; @@ -114,12 +113,23 @@ export interface IEditor extends IPanel { isVisible(): boolean; } -export interface ITextEditor extends IEditor { +/** + * Overrides `IEditorPane` where `input` and `group` are known to be set. + */ +export interface IVisibleEditorPane extends IEditorPane { + readonly input: IEditorInput; + readonly group: IEditorGroup; +} + +/** + * The text editor pane is the container for workbench text editors. + */ +export interface ITextEditorPane extends IEditorPane { /** * Returns the underlying text editor widget of this editor. */ - getControl(): ICodeEditor | undefined; + getControl(): IEditor | undefined; /** * Returns the current view state of the text editor if any. @@ -127,13 +137,16 @@ export interface ITextEditor extends IEditor { getViewState(): IEditorViewState | undefined; } -export function isTextEditor(thing: IEditor | undefined): thing is ITextEditor { - const candidate = thing as ITextEditor | undefined; +export function isTextEditorPane(thing: IEditorPane | undefined): thing is ITextEditorPane { + const candidate = thing as ITextEditorPane | undefined; return typeof candidate?.getViewState === 'function'; } -export interface ITextDiffEditor extends IEditor { +/** + * The text editor pane is the container for workbench text diff editors. + */ +export interface ITextDiffEditorPane extends IEditorPane { /** * Returns the underlying text editor widget of this editor. @@ -141,44 +154,45 @@ export interface ITextDiffEditor extends IEditor { getControl(): IDiffEditor | undefined; } -export interface ITextSideBySideEditor extends IEditor { - - /** - * Returns the underlying text editor widget of the master side - * of this side-by-side editor. - */ - getMasterEditor(): ITextEditor; - - /** - * Returns the underlying text editor widget of the details side - * of this side-by-side editor. - */ - getDetailsEditor(): ITextEditor; -} - /** - * Marker interface for the base editor control + * Marker interface for the control inside an editor pane. Callers + * have to cast the control to work with it, e.g. via methods + * such as `isCodeEditor(control)`. */ export interface IEditorControl extends ICompositeControl { } -export interface IFileInputFactory { +export interface IFileEditorInputFactory { - createFileInput(resource: URI, encoding: string | undefined, mode: string | undefined, instantiationService: IInstantiationService): IFileEditorInput; + createFileEditorInput(resource: URI, encoding: string | undefined, mode: string | undefined, instantiationService: IInstantiationService): IFileEditorInput; - isFileInput(obj: unknown): obj is IFileEditorInput; + isFileEditorInput(obj: unknown): obj is IFileEditorInput; +} + +interface ICustomEditorInputFactory { + createCustomEditorInput(resource: URI, instantiationService: IInstantiationService): Promise; } export interface IEditorInputFactoryRegistry { /** - * Registers the file input factory to use for file inputs. + * Registers the file editor input factory to use for file inputs. */ - registerFileInputFactory(factory: IFileInputFactory): void; + registerFileEditorInputFactory(factory: IFileEditorInputFactory): void; /** - * Returns the file input factory to use for file inputs. + * Returns the file editor input factory to use for file inputs. */ - getFileInputFactory(): IFileInputFactory; + getFileEditorInputFactory(): IFileEditorInputFactory; + + /** + * Registers the custom editor input factory to use for custom inputs. + */ + registerCustomEditorInputFactory(factory: ICustomEditorInputFactory): void; + + /** + * Returns the custom editor input factory to use for custom inputs. + */ + getCustomEditorInputFactory(): ICustomEditorInputFactory; /** * Registers a editor input factory for the given editor input to the registry. An editor input factory @@ -222,7 +236,7 @@ export interface IEditorInputFactory { deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput | undefined; } -export interface IUntitledTextResourceInput extends IBaseResourceInput { +export interface IUntitledTextResourceEditorInput extends IBaseResourceEditorInput { /** * Optional resource. If the resource is not provided a new untitled file is created (e.g. Untitled-1). @@ -248,7 +262,7 @@ export interface IUntitledTextResourceInput extends IBaseResourceInput { readonly encoding?: string; } -export interface IResourceDiffInput extends IBaseResourceInput { +export interface IResourceDiffEditorInput extends IBaseResourceEditorInput { /** * The left hand side URI to open inside a diff editor. @@ -261,19 +275,6 @@ export interface IResourceDiffInput extends IBaseResourceInput { readonly rightResource: URI; } -export interface IResourceSideBySideInput extends IBaseResourceInput { - - /** - * The right hand side URI to open inside a side by side editor. - */ - readonly masterResource: URI; - - /** - * The left hand side URI to open inside a side by side editor. - */ - readonly detailResource: URI; -} - export const enum Verbosity { SHORT, MEDIUM, @@ -346,7 +347,7 @@ export interface IRevertOptions { } export interface IMoveResult { - editor: EditorInput | IResourceEditor; + editor: EditorInput | IResourceEditorInputType; options?: IEditorOptions; } @@ -449,7 +450,7 @@ export interface IEditorInput extends IDisposable { /** * Reverts this input from the provided group. */ - revert(group: GroupIdentifier, options?: IRevertOptions): Promise; + revert(group: GroupIdentifier, options?: IRevertOptions): Promise; /** * Called to determine how to handle a resource that is moved that matches @@ -557,9 +558,7 @@ export abstract class EditorInput extends Disposable implements IEditorInput { return this; } - async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { - return true; - } + async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { } move(group: GroupIdentifier, target: URI): IMoveResult | undefined { return undefined; @@ -611,8 +610,20 @@ export abstract class TextResourceEditorInput extends EditorInput { protected registerListeners(): void { // Clear label memoizer on certain events that have impact - this._register(this.labelService.onDidChangeFormatters(() => TextResourceEditorInput.MEMOIZER.clear())); - this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(() => TextResourceEditorInput.MEMOIZER.clear())); + this._register(this.labelService.onDidChangeFormatters(e => this.onLabelEvent(e.scheme))); + this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(e => this.onLabelEvent(e.scheme))); + this._register(this.fileService.onDidChangeFileSystemProviderCapabilities(e => this.onLabelEvent(e.scheme))); + } + + private onLabelEvent(scheme: string): void { + if (scheme === this.resource.scheme) { + + // Clear any cached labels from before + TextResourceEditorInput.MEMOIZER.clear(); + + // Trigger recompute of label + this._onDidChangeLabel.fire(); + } } getName(): string { @@ -687,10 +698,6 @@ export abstract class TextResourceEditorInput extends EditorInput { return false; // untitled is never readonly } - if (!this.fileService.canHandleResource(this.resource)) { - return true; // resources without file support are always readonly - } - return this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly); } @@ -729,14 +736,14 @@ export abstract class TextResourceEditorInput extends EditorInput { } if (!isEqual(target, this.resource)) { - return this.editorService.createInput({ resource: target }); + return this.editorService.createEditorInput({ resource: target }); } return this; } - revert(group: GroupIdentifier, options?: IRevertOptions): Promise { - return this.textFileService.revert(this.resource, options); + async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { + await this.textFileService.revert(this.resource, options); } } @@ -876,7 +883,7 @@ export class SideBySideEditorInput extends EditorInput { return this.master.saveAs(group, options); } - revert(group: GroupIdentifier, options?: IRevertOptions): Promise { + revert(group: GroupIdentifier, options?: IRevertOptions): Promise { return this.master.revert(group, options); } @@ -1143,7 +1150,7 @@ export class TextEditorOptions extends EditorOptions implements ITextEditorOptio */ selectionRevealType: TextEditorSelectionRevealType | undefined; - static from(input?: IBaseResourceInput): TextEditorOptions | undefined { + static from(input?: IBaseResourceEditorInput): TextEditorOptions | undefined { if (!input || !input.options) { return undefined; } @@ -1197,7 +1204,7 @@ export class TextEditorOptions extends EditorOptions implements ITextEditorOptio /** * Create a TextEditorOptions inline to be used when the editor is opening. */ - static fromEditor(editor: ICodeEditor, settings?: IEditorOptions): TextEditorOptions { + static fromEditor(editor: IEditor, settings?: IEditorOptions): TextEditorOptions { const options = TextEditorOptions.create(settings); // View state @@ -1211,7 +1218,7 @@ export class TextEditorOptions extends EditorOptions implements ITextEditorOptio * * @return if something was applied */ - apply(editor: ICodeEditor, scrollType: ScrollType): boolean { + apply(editor: IEditor, scrollType: ScrollType): boolean { let gotApplied = false; // First try viewstate @@ -1295,6 +1302,7 @@ interface IEditorPartConfiguration { highlightModifiedTabs?: boolean; tabCloseButton?: 'left' | 'right' | 'off'; tabSizing?: 'fit' | 'shrink'; + titleScrollbarSizing?: 'default' | 'large'; focusRecentEditorAfterClose?: boolean; showIcons?: boolean; enablePreview?: boolean; @@ -1326,7 +1334,8 @@ export interface IEditorPartOptionsChangeEvent { export enum SideBySideEditor { MASTER = 1, - DETAILS = 2 + DETAILS = 2, + BOTH = 3 } export interface IResourceOptions { @@ -1334,12 +1343,22 @@ export interface IResourceOptions { filterByScheme?: string | string[]; } -export function toResource(editor: IEditorInput | undefined, options?: IResourceOptions): URI | undefined { +export function toResource(editor: IEditorInput | undefined): URI | undefined; +export function toResource(editor: IEditorInput | undefined, options: IResourceOptions & { supportSideBySide?: SideBySideEditor.MASTER | SideBySideEditor.DETAILS }): URI | undefined; +export function toResource(editor: IEditorInput | undefined, options: IResourceOptions & { supportSideBySide: SideBySideEditor.BOTH }): URI | { master?: URI, detail?: URI } | undefined; +export function toResource(editor: IEditorInput | undefined, options?: IResourceOptions): URI | { master?: URI, detail?: URI } | undefined { if (!editor) { return undefined; } if (options?.supportSideBySide && editor instanceof SideBySideEditorInput) { + if (options?.supportSideBySide === SideBySideEditor.BOTH) { + return { + master: toResource(editor.master, { filterByScheme: options.filterByScheme }), + detail: toResource(editor.details, { filterByScheme: options.filterByScheme }) + }; + } + editor = options.supportSideBySide === SideBySideEditor.MASTER ? editor.master : editor.details; } @@ -1348,12 +1367,14 @@ export function toResource(editor: IEditorInput | undefined, options?: IResource return resource; } - if (Array.isArray(options.filterByScheme) && options.filterByScheme.some(scheme => resource.scheme === scheme)) { - return resource; - } - - if (options.filterByScheme === resource.scheme) { - return resource; + if (Array.isArray(options.filterByScheme)) { + if (options.filterByScheme.some(scheme => resource.scheme === scheme)) { + return resource; + } + } else { + if (options.filterByScheme === resource.scheme) { + return resource; + } } return undefined; @@ -1380,7 +1401,8 @@ export interface IEditorMemento { class EditorInputFactoryRegistry implements IEditorInputFactoryRegistry { private instantiationService: IInstantiationService | undefined; - private fileInputFactory: IFileInputFactory | undefined; + private fileEditorInputFactory: IFileEditorInputFactory | undefined; + private customEditorInputFactory: ICustomEditorInputFactory | undefined; private readonly editorInputFactoryConstructors: Map> = new Map(); private readonly editorInputFactoryInstances: Map = new Map(); @@ -1400,12 +1422,20 @@ class EditorInputFactoryRegistry implements IEditorInputFactoryRegistry { this.editorInputFactoryInstances.set(editorInputId, instance); } - registerFileInputFactory(factory: IFileInputFactory): void { - this.fileInputFactory = factory; + registerFileEditorInputFactory(factory: IFileEditorInputFactory): void { + this.fileEditorInputFactory = factory; } - getFileInputFactory(): IFileInputFactory { - return assertIsDefined(this.fileInputFactory); + getFileEditorInputFactory(): IFileEditorInputFactory { + return assertIsDefined(this.fileEditorInputFactory); + } + + registerCustomEditorInputFactory(factory: ICustomEditorInputFactory): void { + this.customEditorInputFactory = factory; + } + + getCustomEditorInputFactory(): ICustomEditorInputFactory { + return assertIsDefined(this.customEditorInputFactory); } registerEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0): IDisposable { @@ -1433,7 +1463,7 @@ export const Extensions = { Registry.add(Extensions.EditorInputFactories, new EditorInputFactoryRegistry()); -export async function pathsToEditors(paths: IPathData[] | undefined, fileService: IFileService): Promise<(IResourceInput | IUntitledTextResourceInput)[]> { +export async function pathsToEditors(paths: IPathData[] | undefined, fileService: IFileService): Promise<(IResourceEditorInput | IUntitledTextResourceEditorInput)[]> { if (!paths || !paths.length) { return []; } @@ -1454,7 +1484,7 @@ export async function pathsToEditors(paths: IPathData[] | undefined, fileService pinned: true } : { pinned: true }; - let input: IResourceInput | IUntitledTextResourceInput; + let input: IResourceEditorInput | IUntitledTextResourceEditorInput; if (!exists) { input = { resource, options, forceUntitled: true }; } else { diff --git a/src/vs/workbench/common/editor/diffEditorModel.ts b/src/vs/workbench/common/editor/diffEditorModel.ts index f5c17787f90..93f9d7e7fe8 100644 --- a/src/vs/workbench/common/editor/diffEditorModel.ts +++ b/src/vs/workbench/common/editor/diffEditorModel.ts @@ -23,25 +23,17 @@ export class DiffEditorModel extends EditorModel { } get originalModel(): IEditorModel | null { - if (!this._originalModel) { - return null; - } - return this._originalModel; } get modifiedModel(): IEditorModel | null { - if (!this._modifiedModel) { - return null; - } - return this._modifiedModel; } async load(): Promise { await Promise.all([ - this._originalModel ? this._originalModel.load() : Promise.resolve(undefined), - this._modifiedModel ? this._modifiedModel.load() : Promise.resolve(undefined), + this._originalModel?.load(), + this._modifiedModel?.load(), ]); return this; diff --git a/src/vs/workbench/common/editor/resourceEditorInput.ts b/src/vs/workbench/common/editor/resourceEditorInput.ts index 18d7965ca6b..3ea14b511f8 100644 --- a/src/vs/workbench/common/editor/resourceEditorInput.ts +++ b/src/vs/workbench/common/editor/resourceEditorInput.ts @@ -94,7 +94,7 @@ export class ResourceEditorInput extends TextResourceEditorInput implements IMod ref.dispose(); this.modelReference = undefined; - throw new Error(`Unexpected model for ResourceInput: ${this.resource}`); + throw new Error(`Unexpected model for ResourcEditorInput: ${this.resource}`); } this.cachedModel = model; diff --git a/src/vs/workbench/common/notifications.ts b/src/vs/workbench/common/notifications.ts index 809037bf68e..cf6840d00c8 100644 --- a/src/vs/workbench/common/notifications.ts +++ b/src/vs/workbench/common/notifications.ts @@ -41,8 +41,26 @@ export interface INotificationsModel { } export const enum NotificationChangeType { + + /** + * A notification was added. + */ ADD, + + /** + * A notification changed. Check `detail` property + * on the event for additional information. + */ CHANGE, + + /** + * A notification expanded or collapsed. + */ + EXPAND_COLLAPSE, + + /** + * A notification was removed. + */ REMOVE } @@ -62,6 +80,12 @@ export interface INotificationChangeEvent { * The kind of notification change. */ kind: NotificationChangeType; + + /** + * Additional detail about the item change. Only applies to + * `NotificationChangeType.CHANGE`. + */ + detail?: NotificationViewItemContentChangeKind } export const enum StatusMessageChangeType { @@ -206,26 +230,19 @@ export class NotificationsModel extends Disposable implements INotificationsMode } // Item Events - const onItemChangeEvent = () => { + const fireNotificationChangeEvent = (kind: NotificationChangeType, detail?: NotificationViewItemContentChangeKind) => { const index = this._notifications.indexOf(item); if (index >= 0) { - this._onDidChangeNotification.fire({ item, index, kind: NotificationChangeType.CHANGE }); + this._onDidChangeNotification.fire({ item, index, kind, detail }); } }; - const itemExpansionChangeListener = item.onDidChangeExpansion(() => onItemChangeEvent()); - - const itemLabelChangeListener = item.onDidChangeLabel(e => { - // a label change in the area of actions or the message is a change that potentially has an impact - // on the size of the notification and as such we emit a change event so that viewers can redraw - if (e.kind === NotificationViewItemLabelKind.ACTIONS || e.kind === NotificationViewItemLabelKind.MESSAGE) { - onItemChangeEvent(); - } - }); + const itemExpansionChangeListener = item.onDidChangeExpansion(() => fireNotificationChangeEvent(NotificationChangeType.EXPAND_COLLAPSE)); + const itemContentChangeListener = item.onDidChangeContent(e => fireNotificationChangeEvent(NotificationChangeType.CHANGE, e.kind)); Event.once(item.onDidClose)(() => { itemExpansionChangeListener.dispose(); - itemLabelChangeListener.dispose(); + itemContentChangeListener.dispose(); const index = this._notifications.indexOf(item); if (index >= 0) { @@ -272,9 +289,9 @@ export interface INotificationViewItem { readonly hasProgress: boolean; readonly onDidChangeExpansion: Event; - readonly onDidClose: Event; readonly onDidChangeVisibility: Event; - readonly onDidChangeLabel: Event; + readonly onDidChangeContent: Event; + readonly onDidClose: Event; expand(): void; collapse(skipEvents?: boolean): void; @@ -295,15 +312,15 @@ export function isNotificationViewItem(obj: unknown): obj is INotificationViewIt return obj instanceof NotificationViewItem; } -export const enum NotificationViewItemLabelKind { +export const enum NotificationViewItemContentChangeKind { SEVERITY, MESSAGE, ACTIONS, PROGRESS } -export interface INotificationViewItemLabelChangeEvent { - kind: NotificationViewItemLabelKind; +export interface INotificationViewItemContentChangeEvent { + kind: NotificationViewItemContentChangeKind; } export interface INotificationViewItemProgressState { @@ -420,8 +437,8 @@ export class NotificationViewItem extends Disposable implements INotificationVie private readonly _onDidClose = this._register(new Emitter()); readonly onDidClose = this._onDidClose.event; - private readonly _onDidChangeLabel = this._register(new Emitter()); - readonly onDidChangeLabel = this._onDidChangeLabel.event; + private readonly _onDidChangeContent = this._register(new Emitter()); + readonly onDidChangeContent = this._onDidChangeContent.event; private readonly _onDidChangeVisibility = this._register(new Emitter()); readonly onDidChangeVisibility = this._onDidChangeVisibility.event; @@ -512,20 +529,16 @@ export class NotificationViewItem extends Disposable implements INotificationVie } private setActions(actions: INotificationActions = { primary: [], secondary: [] }): void { - if (!Array.isArray(actions.primary)) { - actions.primary = []; - } + this._actions = { + primary: Array.isArray(actions.primary) ? actions.primary : [], + secondary: Array.isArray(actions.secondary) ? actions.secondary : [] + }; - if (!Array.isArray(actions.secondary)) { - actions.secondary = []; - } - - this._actions = actions; - this._expanded = actions.primary.length > 0; + this._expanded = actions.primary && actions.primary.length > 0; } get canCollapse(): boolean { - return !this.hasPrompt; + return !this.hasActions; } get expanded(): boolean { @@ -541,11 +554,11 @@ export class NotificationViewItem extends Disposable implements INotificationVie return true; // explicitly sticky } - const hasPrompt = this.hasPrompt; + const hasActions = this.hasActions; if ( - (hasPrompt && this._severity === Severity.Error) || // notification errors with actions are sticky - (!hasPrompt && this._expanded) || // notifications that got expanded are sticky - (this._progress && !this._progress.state.done) // notifications with running progress are sticky + (hasActions && this._severity === Severity.Error) || // notification errors with actions are sticky + (!hasActions && this._expanded) || // notifications that got expanded are sticky + (this._progress && !this._progress.state.done) // notifications with running progress are sticky ) { return true; } @@ -557,7 +570,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie return !!this._silent; } - private get hasPrompt(): boolean { + private get hasActions(): boolean { if (!this._actions) { return false; } @@ -576,7 +589,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie get progress(): INotificationViewItemProgress { if (!this._progress) { this._progress = this._register(new NotificationViewItemProgress()); - this._register(this._progress.onDidChange(() => this._onDidChangeLabel.fire({ kind: NotificationViewItemLabelKind.PROGRESS }))); + this._register(this._progress.onDidChange(() => this._onDidChangeContent.fire({ kind: NotificationViewItemContentChangeKind.PROGRESS }))); } return this._progress; @@ -596,7 +609,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie updateSeverity(severity: Severity): void { this._severity = severity; - this._onDidChangeLabel.fire({ kind: NotificationViewItemLabelKind.SEVERITY }); + this._onDidChangeContent.fire({ kind: NotificationViewItemContentChangeKind.SEVERITY }); } updateMessage(input: NotificationMessage): void { @@ -606,13 +619,12 @@ export class NotificationViewItem extends Disposable implements INotificationVie } this._message = message; - this._onDidChangeLabel.fire({ kind: NotificationViewItemLabelKind.MESSAGE }); + this._onDidChangeContent.fire({ kind: NotificationViewItemContentChangeKind.MESSAGE }); } updateActions(actions?: INotificationActions): void { this.setActions(actions); - - this._onDidChangeLabel.fire({ kind: NotificationViewItemLabelKind.ACTIONS }); + this._onDidChangeContent.fire({ kind: NotificationViewItemContentChangeKind.ACTIONS }); } updateVisibility(visible: boolean): void { @@ -685,15 +697,13 @@ export class ChoiceAction extends Action { private readonly _keepOpen: boolean; constructor(id: string, choice: IPromptChoice) { - super(id, choice.label, undefined, true, () => { + super(id, choice.label, undefined, true, async () => { // Pass to runner choice.run(); // Emit Event this._onDidRun.fire(); - - return Promise.resolve(); }); this._keepOpen = !!choice.keepOpen; diff --git a/src/vs/workbench/common/resources.ts b/src/vs/workbench/common/resources.ts index 597dd5d96f9..2a7844da48f 100644 --- a/src/vs/workbench/common/resources.ts +++ b/src/vs/workbench/common/resources.ts @@ -113,8 +113,8 @@ export class ResourceGlobMatcher extends Disposable { private readonly _onExpressionChange = this._register(new Emitter()); readonly onExpressionChange = this._onExpressionChange.event; - private readonly mapRootToParsedExpression: Map = new Map(); - private readonly mapRootToExpressionConfig: Map = new Map(); + private readonly mapRootToParsedExpression = new Map(); + private readonly mapRootToExpressionConfig = new Map(); constructor( private globFn: (root?: URI) => IExpression, diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index e465329f6b0..15cf1eb94c6 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -5,13 +5,12 @@ import * as nls from 'vs/nls'; import { registerColor, editorBackground, contrastBorder, transparent, editorWidgetBackground, textLinkForeground, lighten, darken, focusBorder, activeContrastBorder, editorWidgetForeground, editorErrorForeground, editorWarningForeground, editorInfoForeground } from 'vs/platform/theme/common/colorRegistry'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { IColorTheme } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; // < --- Workbench (not customizable) --- > -export function WORKBENCH_BACKGROUND(theme: ITheme): Color { +export function WORKBENCH_BACKGROUND(theme: IColorTheme): Color { switch (theme.type) { case 'dark': return Color.fromHex('#252526'); @@ -461,20 +460,6 @@ export const SIDE_BAR_SECTION_HEADER_BORDER = registerColor('sideBarSectionHeade }, nls.localize('sideBarSectionHeaderBorder', "Side bar section header border color. The side bar is the container for views like explorer and search.")); -// < --- Quick Input -- > - -export const QUICK_INPUT_BACKGROUND = registerColor('quickInput.background', { - dark: SIDE_BAR_BACKGROUND, - light: SIDE_BAR_BACKGROUND, - hc: SIDE_BAR_BACKGROUND -}, nls.localize('quickInputBackground', "Quick Input background color. The Quick Input widget is the container for views like the color theme picker.")); - -export const QUICK_INPUT_FOREGROUND = registerColor('quickInput.foreground', { - dark: SIDE_BAR_FOREGROUND, - light: SIDE_BAR_FOREGROUND, - hc: SIDE_BAR_FOREGROUND -}, nls.localize('quickInputForeground', "Quick Input foreground color. The Quick Input widget is the container for views like the color theme picker.")); - // < --- Title Bar --- > export const TITLE_BAR_ACTIVE_FOREGROUND = registerColor('titleBar.activeForeground', { @@ -606,41 +591,3 @@ export const WINDOW_INACTIVE_BORDER = registerColor('window.inactiveBorder', { light: null, hc: contrastBorder }, nls.localize('windowInactiveBorder', "The color used for the border of the window when it is inactive. Only supported in the desktop client when using the custom title bar.")); - -/** - * Base class for all themable workbench components. - */ -export class Themable extends Disposable { - protected theme: ITheme; - - constructor( - protected themeService: IThemeService - ) { - super(); - - this.theme = themeService.getTheme(); - - // Hook up to theme changes - this._register(this.themeService.onThemeChange(theme => this.onThemeChange(theme))); - } - - protected onThemeChange(theme: ITheme): void { - this.theme = theme; - - this.updateStyles(); - } - - protected updateStyles(): void { - // Subclasses to override - } - - protected getColor(id: string, modify?: (color: Color, theme: ITheme) => Color): string | null { - let color = this.theme.getColor(id); - - if (color && modify) { - color = modify(color, this.theme); - } - - return color ? color.toString() : null; - } -} diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 8cf5db8c126..c303cd3c1ab 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -20,6 +20,8 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { flatten, mergeSort } from 'vs/base/common/arrays'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { SetMap } from 'vs/base/common/collections'; +import { IProgressIndicator } from 'vs/platform/progress/common/progress'; +import Severity from 'vs/base/common/severity'; export const TEST_VIEW_CONTAINER_ID = 'workbench.view.extension.test'; @@ -262,7 +264,8 @@ function compareViewContentDescriptors(a: IViewContentDescriptor, b: IViewConten return aPriority - bPriority; } - return a.content < b.content ? -1 : 1; + // No priroity, keep views sorted in the order they got registered + return 0; } class ViewsRegistry extends Disposable implements IViewsRegistry { @@ -401,6 +404,7 @@ export interface IView { setExpanded(expanded: boolean): boolean; + getProgressIndicator(): IProgressIndicator | undefined; } export interface IViewsViewlet extends IViewlet { @@ -425,6 +429,7 @@ export interface IViewsService { closeView(id: string): void; + getProgressIndicator(id: string): IProgressIndicator | undefined; } /** @@ -487,6 +492,8 @@ export interface ITreeView extends IDisposable { readonly onDidChangeTitle: Event; + readonly onDidChangeWelcomeState: Event; + refresh(treeItems?: ITreeItem[]): Promise; setVisibility(visible: boolean): void; @@ -505,9 +512,6 @@ export interface ITreeView extends IDisposable { setFocus(item: ITreeItem): void; - getPrimaryActions(): IAction[]; - - getSecondaryActions(): IAction[]; } export interface IRevealOptions { @@ -573,13 +577,14 @@ export interface ITreeItem { } export interface ITreeViewDataProvider { - + readonly isTreeEmpty?: boolean; + onDidChangeEmpty?: Event; getChildren(element?: ITreeItem): Promise; } export interface IEditableData { - validationMessage: (value: string) => string | null; + validationMessage: (value: string) => { content: string, severity: Severity } | null; placeholder?: string | null; startingValue?: string | null; onFinish: (value: string, success: boolean) => void; @@ -601,4 +606,3 @@ export interface IViewPaneContainer { getView(viewId: string): IView | undefined; saveState(): void; } - diff --git a/src/vs/workbench/contrib/backup/common/backupRestorer.ts b/src/vs/workbench/contrib/backup/common/backupRestorer.ts index 147cfd3d6eb..483c29c97c0 100644 --- a/src/vs/workbench/contrib/backup/common/backupRestorer.ts +++ b/src/vs/workbench/contrib/backup/common/backupRestorer.ts @@ -7,12 +7,14 @@ import { URI } from 'vs/base/common/uri'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { Schemas } from 'vs/base/common/network'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IUntitledTextResourceInput, IEditorInput } from 'vs/workbench/common/editor'; +import { IUntitledTextResourceEditorInput, IEditorInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, IEditorInputWithOptions } from 'vs/workbench/common/editor'; import { toLocalResource, isEqual } from 'vs/base/common/resources'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export class BackupRestorer implements IWorkbenchContribution { @@ -22,7 +24,8 @@ export class BackupRestorer implements IWorkbenchContribution { @IEditorService private readonly editorService: IEditorService, @IBackupFileService private readonly backupFileService: IBackupFileService, @ILifecycleService private readonly lifecycleService: ILifecycleService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IInstantiationService private readonly instantiationService: IInstantiationService, ) { this.restoreBackups(); } @@ -78,13 +81,13 @@ export class BackupRestorer implements IWorkbenchContribution { private async doOpenEditors(resources: URI[]): Promise { const hasOpenedEditors = this.editorService.visibleEditors.length > 0; - const inputs = resources.map((resource, index) => this.resolveInput(resource, index, hasOpenedEditors)); + const inputs = await Promise.all(resources.map((resource, index) => this.resolveInput(resource, index, hasOpenedEditors))); // Open all remaining backups as editors and resolve them to load their backups await this.editorService.openEditors(inputs); } - private resolveInput(resource: URI, index: number, hasOpenedEditors: boolean): IResourceInput | IUntitledTextResourceInput { + private async resolveInput(resource: URI, index: number, hasOpenedEditors: boolean): Promise { const options = { pinned: true, preserveFocus: true, inactive: index > 0 || hasOpenedEditors }; // this is a (weak) strategy to find out if the untitled input had @@ -94,6 +97,12 @@ export class BackupRestorer implements IWorkbenchContribution { return { resource: toLocalResource(resource, this.environmentService.configuration.remoteAuthority), options, forceUntitled: true }; } + if (resource.scheme === Schemas.vscodeCustomEditor) { + const editor = await Registry.as(EditorExtensions.EditorInputFactories).getCustomEditorInputFactory() + .createCustomEditorInput(resource, this.instantiationService); + return { editor, options }; + } + return { resource, options }; } } diff --git a/src/vs/workbench/contrib/backup/common/backupTracker.ts b/src/vs/workbench/contrib/backup/common/backupTracker.ts index 074a0c545aa..4121d8ae848 100644 --- a/src/vs/workbench/contrib/backup/common/backupTracker.ts +++ b/src/vs/workbench/contrib/backup/common/backupTracker.ts @@ -114,10 +114,6 @@ export abstract class BackupTracker extends Disposable { return; // skip if auto save is enabled with a short delay } - if (typeof workingCopy.backup !== 'function') { - return; // skip if working copy does not support backups - } - // Clear any running backup operation dispose(this.pendingBackups.get(workingCopy)); this.pendingBackups.delete(workingCopy); @@ -131,7 +127,7 @@ export abstract class BackupTracker extends Disposable { this.pendingBackups.delete(workingCopy); // Backup if dirty - if (workingCopy.isDirty() && typeof workingCopy.backup === 'function') { + if (workingCopy.isDirty()) { this.logService.trace(`[backup tracker] running backup`, workingCopy.resource.toString()); const backup = await workingCopy.backup(); diff --git a/src/vs/workbench/contrib/backup/electron-browser/backupTracker.ts b/src/vs/workbench/contrib/backup/electron-browser/backupTracker.ts index 1aba5636ccb..9508fba0af0 100644 --- a/src/vs/workbench/contrib/backup/electron-browser/backupTracker.ts +++ b/src/vs/workbench/contrib/backup/electron-browser/backupTracker.ts @@ -9,7 +9,6 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { ILifecycleService, LifecyclePhase, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ConfirmResult, IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs'; import Severity from 'vs/base/common/severity'; import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -20,6 +19,7 @@ import { BackupTracker } from 'vs/workbench/contrib/backup/common/backupTracker' import { ILogService } from 'vs/platform/log/common/log'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { SaveReason } from 'vs/workbench/common/editor'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; export class NativeBackupTracker extends BackupTracker implements IWorkbenchContribution { @@ -28,13 +28,13 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, @IWorkingCopyService workingCopyService: IWorkingCopyService, @ILifecycleService lifecycleService: ILifecycleService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IFileDialogService private readonly fileDialogService: IFileDialogService, @IDialogService private readonly dialogService: IDialogService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IElectronService private readonly electronService: IElectronService, @ILogService logService: ILogService, - @IEditorService private readonly editorService: IEditorService + @IEditorService private readonly editorService: IEditorService, + @IEnvironmentService private readonly environmentService: IEnvironmentService ) { super(backupFileService, filesConfigurationService, workingCopyService, logService, lifecycleService); } @@ -47,7 +47,8 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont return this.onBeforeShutdownWithDirty(reason, dirtyWorkingCopies); } - return false; // no veto (no dirty working copies) + // No dirty working copies + return this.onBeforeShutdownWithoutDirty(); } protected async onBeforeShutdownWithDirty(reason: ShutdownReason, workingCopies: IWorkingCopy[]): Promise { @@ -120,32 +121,36 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont // ever activated when quit is requested. let doBackup: boolean | undefined; - switch (reason) { - case ShutdownReason.CLOSE: - if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.filesConfigurationService.hotExitConfiguration === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { - doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured - } else if (await this.electronService.getWindowCount() > 1 || isMacintosh) { - doBackup = false; // do not backup if a window is closed that does not cause quitting of the application - } else { - doBackup = true; // backup if last window is closed on win/linux where the application quits right after - } - break; + if (this.environmentService.isExtensionDevelopment) { + doBackup = true; // always backup closing extension development window without asking to speed up debugging + } else { + switch (reason) { + case ShutdownReason.CLOSE: + if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.filesConfigurationService.hotExitConfiguration === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { + doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured + } else if (await this.electronService.getWindowCount() > 1 || isMacintosh) { + doBackup = false; // do not backup if a window is closed that does not cause quitting of the application + } else { + doBackup = true; // backup if last window is closed on win/linux where the application quits right after + } + break; - case ShutdownReason.QUIT: - doBackup = true; // backup because next start we restore all backups - break; + case ShutdownReason.QUIT: + doBackup = true; // backup because next start we restore all backups + break; - case ShutdownReason.RELOAD: - doBackup = true; // backup because after window reload, backups restore - break; + case ShutdownReason.RELOAD: + doBackup = true; // backup because after window reload, backups restore + break; - case ShutdownReason.LOAD: - if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.filesConfigurationService.hotExitConfiguration === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { - doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured - } else { - doBackup = false; // do not backup because we are switching contexts - } - break; + case ShutdownReason.LOAD: + if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.filesConfigurationService.hotExitConfiguration === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { + doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured + } else { + doBackup = false; // do not backup because we are switching contexts + } + break; + } } // Perform a backup of all dirty working copies unless a backup already exists @@ -161,12 +166,10 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont // Backup does not exist else { - if (typeof workingCopy.backup === 'function') { - const backup = await workingCopy.backup(); - await this.backupFileService.backup(workingCopy.resource, backup.content, contentVersion, backup.meta); + const backup = await workingCopy.backup(); + await this.backupFileService.backup(workingCopy.resource, backup.content, contentVersion, backup.meta); - backups.push(workingCopy); - } + backups.push(workingCopy); } })); } @@ -225,7 +228,7 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont // If we still have dirty working copies, save those directly // unless the save was not successful (e.g. cancelled) if (result !== false) { - await Promise.all(workingCopies.map(workingCopy => workingCopy.isDirty() ? workingCopy.save(saveOptions) : Promise.resolve(true))); + await Promise.all(workingCopies.map(workingCopy => workingCopy.isDirty() ? workingCopy.save(saveOptions) : true)); } } @@ -235,16 +238,13 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont const revertOptions = { soft: true }; // First revert through the editor service if we revert all - let result: boolean | undefined = undefined; if (workingCopies.length === this.workingCopyService.dirtyCount) { - result = await this.editorService.revertAll(revertOptions); + await this.editorService.revertAll(revertOptions); } // If we still have dirty working copies, revert those directly // unless the revert operation was not successful (e.g. cancelled) - if (result !== false) { - await Promise.all(workingCopies.map(workingCopy => workingCopy.isDirty() ? workingCopy.revert(revertOptions) : Promise.resolve(true))); - } + await Promise.all(workingCopies.map(workingCopy => workingCopy.isDirty() ? workingCopy.revert(revertOptions) : undefined)); } private noVeto(backupsToDiscard: IWorkingCopy[]): boolean | Promise { @@ -252,10 +252,23 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont return false; // if editors have not restored, we are not up to speed with backups and thus should not discard them } - if (this.environmentService.isExtensionDevelopment) { - return false; // extension development does not track any backups - } - return Promise.all(backupsToDiscard.map(workingCopy => this.backupFileService.discardBackup(workingCopy.resource))).then(() => false, () => false); } + + private async onBeforeShutdownWithoutDirty(): Promise { + // If we have proceeded enough that editors and dirty state + // has restored, we make sure that no backups lure around + // given we have no known dirty working copy. This helps + // to clean up stale backups as for example reported in + // https://github.com/microsoft/vscode/issues/92962 + if (this.lifecycleService.phase >= LifecyclePhase.Restored) { + try { + await this.backupFileService.discardBackups(); + } catch (error) { + this.logService.error(`[backup tracker] error discarding backups: ${error}`); + } + } + + return false; // no veto (no dirty) + } } diff --git a/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts b/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts index eab7952a824..cab5dd56e4b 100644 --- a/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts +++ b/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts @@ -121,21 +121,33 @@ suite('BackupRestorer', () => { const resource = editor.resource; if (isEqual(resource, untitledFile1)) { const model = await accessor.textFileService.untitled.resolve({ untitledResource: resource }); - assert.equal(model.textEditorModel.getValue(), 'untitled-1'); + if (model.textEditorModel.getValue() !== 'untitled-1') { + const backupContents = await backupFileService.getBackupContents(untitledFile1); + assert.fail(`Unable to restore backup for resource ${untitledFile1.toString()}. Backup contents: ${backupContents}`); + } model.dispose(); counter++; } else if (isEqual(resource, untitledFile2)) { const model = await accessor.textFileService.untitled.resolve({ untitledResource: resource }); - assert.equal(model.textEditorModel.getValue(), 'untitled-2'); + if (model.textEditorModel.getValue() !== 'untitled-2') { + const backupContents = await backupFileService.getBackupContents(untitledFile2); + assert.fail(`Unable to restore backup for resource ${untitledFile2.toString()}. Backup contents: ${backupContents}`); + } model.dispose(); counter++; } else if (isEqual(resource, fooFile)) { - const model = await accessor.textFileService.files.get(resource!)?.load(); - assert.equal(model?.textEditorModel?.getValue(), 'fooFile'); + const model = await accessor.textFileService.files.get(fooFile!)?.load(); + if (model?.textEditorModel?.getValue() !== 'fooFile') { + const backupContents = await backupFileService.getBackupContents(fooFile); + assert.fail(`Unable to restore backup for resource ${fooFile.toString()}. Backup contents: ${backupContents}`); + } counter++; } else { - const model = await accessor.textFileService.files.get(resource!)?.load(); - assert.equal(model?.textEditorModel?.getValue(), 'barFile'); + const model = await accessor.textFileService.files.get(barFile!)?.load(); + if (model?.textEditorModel?.getValue() !== 'barFile') { + const backupContents = await backupFileService.getBackupContents(barFile); + assert.fail(`Unable to restore backup for resource ${barFile.toString()}. Backup contents: ${backupContents}`); + } counter++; } } diff --git a/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts b/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts index d602adb338f..ea8c85ea656 100644 --- a/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts +++ b/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts @@ -18,7 +18,7 @@ import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; import { Registry } from 'vs/platform/registry/common/platform'; -import { EditorInput, IUntitledTextResourceInput } from 'vs/workbench/common/editor'; +import { EditorInput, IUntitledTextResourceEditorInput } from 'vs/workbench/common/editor'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; @@ -32,7 +32,6 @@ import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/wo import { ILogService } from 'vs/platform/log/common/log'; import { HotExitConfiguration } from 'vs/platform/files/common/files'; import { ShutdownReason, ILifecycleService, BeforeShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IFileDialogService, ConfirmResult, IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IWorkspaceContextService, Workspace } from 'vs/platform/workspace/common/workspace'; import { IElectronService } from 'vs/platform/electron/node/electron'; @@ -42,9 +41,10 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { TestFilesConfigurationService, TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestFilesConfigurationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; const userdataDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backuprestorer'); const backupHome = path.join(userdataDir, 'Backups'); @@ -60,15 +60,15 @@ class TestBackupTracker extends NativeBackupTracker { @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, @IWorkingCopyService workingCopyService: IWorkingCopyService, @ILifecycleService lifecycleService: ILifecycleService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IFileDialogService fileDialogService: IFileDialogService, @IDialogService dialogService: IDialogService, @IWorkspaceContextService contextService: IWorkspaceContextService, @IElectronService electronService: IElectronService, @ILogService logService: ILogService, - @IEditorService editorService: IEditorService + @IEditorService editorService: IEditorService, + @IEnvironmentService environmentService: IEnvironmentService ) { - super(backupFileService, filesConfigurationService, workingCopyService, lifecycleService, environmentService, fileDialogService, dialogService, contextService, electronService, logService, editorService); + super(backupFileService, filesConfigurationService, workingCopyService, lifecycleService, fileDialogService, dialogService, contextService, electronService, logService, editorService, environmentService); // Reduce timeout for tests BackupTracker.BACKUP_FROM_CONTENT_CHANGE_DELAY = 10; @@ -131,8 +131,7 @@ suite('BackupTracker', () => { instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService( instantiationService.createInstance(MockContextKeyService), - configurationService, - TestEnvironmentService + configurationService )); const part = instantiationService.createInstance(EditorPart); @@ -153,7 +152,7 @@ suite('BackupTracker', () => { return [accessor, part, tracker, instantiationService]; } - async function untitledBackupTest(untitled: IUntitledTextResourceInput = {}): Promise { + async function untitledBackupTest(untitled: IUntitledTextResourceEditorInput = {}): Promise { const [accessor, part, tracker] = await createTracker(); const untitledEditor = (await accessor.editorService.openEditor(untitled))?.input as UntitledTextEditorInput; @@ -215,7 +214,7 @@ suite('BackupTracker', () => { tracker.dispose(); }); - test('confirm onWillShutdown - no veto', async function () { + test('onWillShutdown - no veto if no dirty files', async function () { const [accessor, part, tracker] = await createTracker(); const resource = toResource.call(this, '/path/index.txt'); @@ -224,18 +223,14 @@ suite('BackupTracker', () => { const event = new BeforeShutdownEventImpl(); accessor.lifecycleService.fireWillShutdown(event); - const veto = event.value; - if (typeof veto === 'boolean') { - assert.ok(!veto); - } else { - assert.ok(!(await veto)); - } + const veto = await event.value; + assert.ok(!veto); part.dispose(); tracker.dispose(); }); - test.skip('confirm onWillShutdown - veto if user cancels', async function () { + test('onWillShutdown - veto if user cancels (hot.exit: off)', async function () { const [accessor, part, tracker] = await createTracker(); const resource = toResource.call(this, '/path/index.txt'); @@ -244,6 +239,7 @@ suite('BackupTracker', () => { const model = accessor.textFileService.files.get(resource); accessor.fileDialogService.setConfirmResult(ConfirmResult.CANCEL); + accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: 'off' } }); await model?.load(); model?.textEditorModel?.setValue('foo'); @@ -252,12 +248,8 @@ suite('BackupTracker', () => { const event = new BeforeShutdownEventImpl(); accessor.lifecycleService.fireWillShutdown(event); - const veto = event.value; - if (typeof veto === 'boolean') { - assert.ok(veto); - } else { - assert.ok((await veto)); - } + const veto = await event.value; + assert.ok(veto); part.dispose(); tracker.dispose(); @@ -278,12 +270,8 @@ suite('BackupTracker', () => { const event = new BeforeShutdownEventImpl(); accessor.lifecycleService.fireWillShutdown(event); - const veto = event.value; - if (typeof veto === 'boolean') { - assert.ok(!veto); - } else { - assert.ok(!(await veto)); - } + const veto = await event.value; + assert.ok(!veto); assert.equal(accessor.workingCopyService.dirtyCount, 0); @@ -291,7 +279,7 @@ suite('BackupTracker', () => { tracker.dispose(); }); - test('confirm onWillShutdown - no veto and backups cleaned up if user does not want to save (hot.exit: off)', async function () { + test('onWillShutdown - no veto and backups cleaned up if user does not want to save (hot.exit: off)', async function () { const [accessor, part, tracker] = await createTracker(); const resource = toResource.call(this, '/path/index.txt'); @@ -308,21 +296,15 @@ suite('BackupTracker', () => { const event = new BeforeShutdownEventImpl(); accessor.lifecycleService.fireWillShutdown(event); - let veto = event.value; - if (typeof veto === 'boolean') { - assert.ok(accessor.backupFileService.discardedBackups.length > 0); - assert.ok(!veto); - } else { - veto = await veto; - assert.ok(accessor.backupFileService.discardedBackups.length > 0); - assert.ok(!veto); - } + const veto = await event.value; + assert.ok(!veto); + assert.ok(accessor.backupFileService.discardedBackups.length > 0); part.dispose(); tracker.dispose(); }); - test('confirm onWillShutdown - save (hot.exit: off)', async function () { + test('onWillShutdown - save (hot.exit: off)', async function () { const [accessor, part, tracker] = await createTracker(); const resource = toResource.call(this, '/path/index.txt'); @@ -339,7 +321,7 @@ suite('BackupTracker', () => { const event = new BeforeShutdownEventImpl(); accessor.lifecycleService.fireWillShutdown(event); - const veto = await (>event.value); + const veto = await event.value; assert.ok(!veto); assert.ok(!model?.isDirty()); @@ -482,7 +464,7 @@ suite('BackupTracker', () => { event.reason = shutdownReason; accessor.lifecycleService.fireWillShutdown(event); - const veto = await (>event.value); + const veto = await event.value; assert.equal(accessor.backupFileService.discardedBackups.length, 0); // When hot exit is set, backups should never be cleaned since the confirm result is cancel assert.equal(veto, shouldVeto); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index 808c009ed03..59bffa18fca 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -4,12 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./bulkEdit'; -import { WorkbenchAsyncDataTree, TreeResourceNavigator, IOpenEvent } from 'vs/platform/list/browser/listService'; +import { WorkbenchAsyncDataTree, IOpenEvent, ResourceNavigator } from 'vs/platform/list/browser/listService'; import { WorkspaceEdit } from 'vs/editor/common/modes'; import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement, BulkEditAccessibilityProvider, BulkEditAriaProvider, CategoryElementRenderer, BulkEditNaviLabelProvider, CategoryElement, BulkEditSorter } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree'; import { FuzzyScore } from 'vs/base/common/filters'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { registerThemingParticipant, ITheme, ICssStyleCollector, IThemeService } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, IColorTheme, ICssStyleCollector, IThemeService } from 'vs/platform/theme/common/themeService'; import { diffInserted, diffRemoved } from 'vs/platform/theme/common/colorRegistry'; import { localize } from 'vs/nls'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -143,7 +143,7 @@ export class BulkEditPane extends ViewPane { this._disposables.add(this._tree.onContextMenu(this._onContextMenu, this)); - const navigator = new TreeResourceNavigator(this._tree, { openOnFocus: true }); + const navigator = ResourceNavigator.createTreeResourceNavigator(this._tree, { openOnFocus: true }); this._disposables.add(navigator); this._disposables.add(navigator.onDidOpenResource(e => this._openElementAsEditor(e))); @@ -379,7 +379,7 @@ export class BulkEditPane extends ViewPane { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const diffInsertedColor = theme.getColor(diffInserted); if (diffInsertedColor) { diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts index 06bdb85376a..4a9b99b6971 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts @@ -18,7 +18,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { Emitter, Event } from 'vs/base/common/event'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { ConflictDetector } from 'vs/workbench/services/bulkEdit/browser/conflicts'; -import { values, ResourceMap } from 'vs/base/common/map'; +import { ResourceMap } from 'vs/base/common/map'; import { localize } from 'vs/nls'; export class CheckedStates { @@ -89,7 +89,7 @@ export class BulkFileOperation { readonly parent: BulkFileOperations ) { } - addEdit(index: number, type: BulkFileOperationType, edit: WorkspaceTextEdit | WorkspaceFileEdit, ) { + addEdit(index: number, type: BulkFileOperationType, edit: WorkspaceTextEdit | WorkspaceFileEdit) { this.type |= type; this.originalEdits.set(index, edit); if (WorkspaceTextEdit.is(edit)) { @@ -126,8 +126,8 @@ export class BulkCategory { constructor(readonly metadata: WorkspaceEditMetadata = BulkCategory._defaultMetadata) { } - get fileOperations(): BulkFileOperation[] { - return values(this.operationByResource); + get fileOperations(): IterableIterator { + return this.operationByResource.values(); } } @@ -260,6 +260,17 @@ export class BulkFileOperations { } } + // sort (once) categories atop which have unconfirmed edits + this.categories.sort((a, b) => { + if (a.metadata.needsConfirmation === b.metadata.needsConfirmation) { + return a.metadata.label.localeCompare(b.metadata.label); + } else if (a.metadata.needsConfirmation) { + return -1; + } else { + return 1; + } + }); + return this; } @@ -388,7 +399,7 @@ export class BulkEditPreviewProvider implements ITextModelContentProvider { } // apply new edits and keep (future) undo edits const newEdits = this._operations.getFileEdits(uri); - const newUndoEdits = model.applyEdits(newEdits); + const newUndoEdits = model.applyEdits(newEdits, true); this._modelPreviewEdits.set(model.id, newUndoEdits); } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index 0c55961052c..7ea17f7b949 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -27,6 +27,7 @@ import { WorkspaceFileEdit } from 'vs/editor/common/modes'; import { compare } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; +import { Iterable } from 'vs/base/common/iterator'; // --- VIEW MODEL @@ -201,7 +202,7 @@ export class BulkEditDataSource implements IAsyncDataSource new FileElement(element, op)); + return [...Iterable.map(element.category.fileOperations, op => new FileElement(element, op))]; } // file: text edit @@ -258,19 +259,6 @@ export class BulkEditDataSource implements IAsyncDataSource { compare(a: BulkEditElement, b: BulkEditElement): number { - if (a instanceof CategoryElement && b instanceof CategoryElement) { - // - const aConfirm = BulkEditSorter._needsConfirmation(a.category); - const bConfirm = BulkEditSorter._needsConfirmation(b.category); - if (aConfirm === bConfirm) { - return a.category.metadata.label.localeCompare(b.category.metadata.label); - } else if (aConfirm) { - return -1; - } else { - return 1; - } - } - if (a instanceof FileElement && b instanceof FileElement) { return compare(a.edit.uri.toString(), b.edit.uri.toString()); } @@ -281,10 +269,6 @@ export class BulkEditSorter implements ITreeSorter { return 0; } - - private static _needsConfirmation(a: BulkCategory): boolean { - return a.fileOperations.some(ops => ops.needsConfirmation()); - } } // --- ACCESSI diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index e26952f121b..c6a99bfd3da 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -24,7 +24,7 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationOptions, OverviewRulerLane } from 'vs/editor/common/model'; -import { registerThemingParticipant, themeColorFromId, IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, themeColorFromId, IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { IPosition } from 'vs/editor/common/core/position'; import { Action } from 'vs/base/common/actions'; import { IActionBarOptions, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -50,10 +50,10 @@ class ChangeHierarchyDirectionAction extends Action { const update = () => { if (getDirection() === CallHierarchyDirection.CallsFrom) { this.label = localize('toggle.from', "Show Incoming Calls"); - this.class = 'calls-from'; + this.class = 'codicon codicon-call-incoming'; } else { this.label = localize('toggle.to', "Showing Outgoing Calls"); - this.class = 'calls-to'; + this.class = 'codicon codicon-call-outgoing'; } }; update(); @@ -112,8 +112,8 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget { super(editor, { showFrame: true, showArrow: true, isResizeable: true, isAccessible: true }); this.create(); this._peekViewService.addExclusiveWidget(editor, this); - this._applyTheme(themeService.getTheme()); - this._disposables.add(themeService.onThemeChange(this._applyTheme, this)); + this._applyTheme(themeService.getColorTheme()); + this._disposables.add(themeService.onDidColorThemeChange(this._applyTheme, this)); this._disposables.add(this._previewDisposable); } @@ -129,7 +129,7 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget { return this._direction; } - private _applyTheme(theme: ITheme) { + private _applyTheme(theme: IColorTheme) { const borderColor = theme.getColor(peekView.peekViewBorder) || Color.transparent; this.style({ arrowColor: borderColor, diff --git a/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-from-dark.svg b/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-from-dark.svg deleted file mode 100644 index 66406bfc5dd..00000000000 --- a/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-from-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-from.svg b/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-from.svg deleted file mode 100644 index b65e2d14a4d..00000000000 --- a/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-from.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-to-dark.svg b/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-to-dark.svg deleted file mode 100644 index ff488f1ed4c..00000000000 --- a/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-to-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-to.svg b/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-to.svg deleted file mode 100644 index 159e5b92eaa..00000000000 --- a/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-to.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/callHierarchy/browser/media/callHierarchy.css b/src/vs/workbench/contrib/callHierarchy/browser/media/callHierarchy.css index c4e553dbd9f..08af6917165 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/media/callHierarchy.css +++ b/src/vs/workbench/contrib/callHierarchy/browser/media/callHierarchy.css @@ -20,28 +20,6 @@ height: 100%; } -.monaco-workbench .action-label.calls-to { - background-image: url(action-call-to.svg); - background-size: 14px 14px; - background-repeat: no-repeat; - background-position: left center; -} - -.vs-dark .monaco-workbench .action-label.calls-to { - background-image: url(action-call-to-dark.svg); -} - -.monaco-workbench .action-label.calls-from { - background-image: url(action-call-from.svg); - background-size: 14px 14px; - background-repeat: no-repeat; - background-position: left center; -} - -.vs-dark .monaco-workbench .action-label.calls-from{ - background-image: url(action-call-from-dark.svg); -} - .monaco-workbench .call-hierarchy .editor, .monaco-workbench .call-hierarchy .tree { height: 100%; diff --git a/src/vs/workbench/contrib/codeActions/common/codeActionsContribution.ts b/src/vs/workbench/contrib/codeActions/common/codeActionsContribution.ts index 886e17a9323..a7d2ba1d7aa 100644 --- a/src/vs/workbench/contrib/codeActions/common/codeActionsContribution.ts +++ b/src/vs/workbench/contrib/codeActions/common/codeActionsContribution.ts @@ -7,7 +7,6 @@ import { flatten } from 'vs/base/common/arrays'; import { Emitter } from 'vs/base/common/event'; import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import { Disposable } from 'vs/base/common/lifecycle'; -import { values } from 'vs/base/common/map'; import { codeActionCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/codeAction'; import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import * as nls from 'vs/nls'; @@ -27,11 +26,19 @@ const codeActionsOnSaveDefaultProperties = Object.freeze({ }); const codeActionsOnSaveSchema: IConfigurationPropertySchema = { - type: 'object', - properties: codeActionsOnSaveDefaultProperties, - 'additionalProperties': { - type: 'boolean' - }, + oneOf: [ + { + type: 'object', + properties: codeActionsOnSaveDefaultProperties, + additionalProperties: { + type: 'boolean' + }, + }, + { + type: 'array', + items: { type: 'string' } + } + ], default: {}, description: nls.localize('codeActionsOnSave', "Code action kinds to be run on save."), scope: ConfigurationScope.LANGUAGE_OVERRIDABLE, @@ -137,7 +144,7 @@ export class CodeActionsContribution extends Disposable implements IWorkbenchCon out.set(action.kind, action); } } - return values(out); + return Array.from(out.values()); }; return [ diff --git a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts index 85225bed6c1..f76fc93385e 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts @@ -284,7 +284,11 @@ class ShowAccessibilityHelpAction extends EditorAction { kbOpts: { kbExpr: EditorContextKeys.focus, primary: KeyMod.Alt | KeyCode.F1, - weight: KeybindingWeight.EditorContrib + weight: KeybindingWeight.EditorContrib, + linux: { + primary: KeyMod.Alt | KeyMod.Shift | KeyCode.F1, + secondary: [KeyMod.Alt | KeyCode.F1] + } } }); } diff --git a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts index eae8927c912..b9992fde979 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts @@ -9,7 +9,10 @@ import './diffEditorHelper'; import './inspectKeybindings'; import './largeFileOptimizations'; import './inspectEditorTokens/inspectEditorTokens'; +import './quickaccess/gotoLineQuickAccess'; +import './quickaccess/gotoSymbolQuickAccess'; import './saveParticipants'; +import './semanticTokensHelp'; import './toggleColumnSelection'; import './toggleMinimap'; import './toggleMultiCursorModifier'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-next-dark.svg b/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-next-dark.svg deleted file mode 100644 index dbe70d742de..00000000000 --- a/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-next-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-next-light.svg b/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-next-light.svg deleted file mode 100644 index ec824f41cc0..00000000000 --- a/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-next-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-previous-dark.svg b/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-previous-dark.svg deleted file mode 100644 index 5db4f79da85..00000000000 --- a/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-previous-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-previous-light.svg b/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-previous-light.svg deleted file mode 100644 index aac3a5020cd..00000000000 --- a/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-previous-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/images/close-dark.svg b/src/vs/workbench/contrib/codeEditor/browser/find/images/close-dark.svg deleted file mode 100644 index 75644595d19..00000000000 --- a/src/vs/workbench/contrib/codeEditor/browser/find/images/close-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/images/close-light.svg b/src/vs/workbench/contrib/codeEditor/browser/find/images/close-light.svg deleted file mode 100644 index cf5f28ca35c..00000000000 --- a/src/vs/workbench/contrib/codeEditor/browser/find/images/close-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.css b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.css new file mode 100644 index 00000000000..88f740a1c56 --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.css @@ -0,0 +1,120 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-workbench .simple-fr-find-part-wrapper { + overflow: hidden; + z-index: 10; + position: absolute; + top: -45px; + right: 18px; + width: 318px; + max-width: calc(100% - 28px - 28px - 8px); + pointer-events: none; + transition: top 200ms linear; + visibility: hidden; +} + +.monaco-workbench .simple-fr-find-part { + /* visibility: hidden; Use visibility to maintain flex layout while hidden otherwise interferes with transition */ + z-index: 10; + position: relative; + top: 0px; + display: flex; + padding: 4px; + align-items: center; + pointer-events: all; + margin: 0 0 0 17px; +} + +.monaco-workbench .simple-fr-replace-part { + /* visibility: hidden; Use visibility to maintain flex layout while hidden otherwise interferes with transition */ + z-index: 10; + position: relative; + top: 0px; + display: flex; + padding: 4px; + align-items: center; + pointer-events: all; + margin: 0 0 0 17px; +} + +.monaco-workbench .simple-fr-find-part-wrapper .find-replace-progress { + width: 100%; + height: 2px; + position: absolute; +} + +.monaco-workbench .simple-fr-find-part-wrapper .find-replace-progress .monaco-progress-container { + height: 2px; + top: 0px !important; + z-index: 100 !important; +} + +.monaco-workbench .simple-fr-find-part-wrapper .find-replace-progress .monaco-progress-container .progress-bit { + height: 2px; +} + +.monaco-workbench .simple-fr-find-part-wrapper .monaco-findInput { + width: 224px; +} + +.monaco-workbench .simple-fr-find-part-wrapper .button { + width: 20px; + height: 20px; + flex: initial; + margin-left: 3px; + background-position: 50%; + background-repeat: no-repeat; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; +} + +.monaco-workbench .simple-fr-find-part-wrapper.visible .simple-fr-find-part { + visibility: visible; +} + +.monaco-workbench .simple-fr-find-part-wrapper .toggle { + position: absolute; + top: 0; + width: 18px; + height: 100%; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + margin-left: 0px; + pointer-events: all; +} + +.monaco-workbench .simple-fr-find-part-wrapper.visible { + visibility: visible; +} + +.monaco-workbench .simple-fr-find-part-wrapper.visible-transition { + top: 0; +} + +.monaco-workbench .simple-fr-find-part .monaco-findInput { + flex: 1; +} + +.monaco-workbench .simple-fr-find-part .button { + min-width: 20px; + width: 20px; + height: 20px; + display: flex; + flex: initial; + margin-left: 3px; + background-position: center center; + background-repeat: no-repeat; + cursor: pointer; +} + +.monaco-workbench .simple-fr-find-part .button.disabled { + opacity: 0.3; + cursor: default; +} diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.ts new file mode 100644 index 00000000000..d065d55b317 --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.ts @@ -0,0 +1,427 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./simpleFindReplaceWidget'; +import * as nls from 'vs/nls'; +import * as dom from 'vs/base/browser/dom'; +import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput'; +import { Widget } from 'vs/base/browser/ui/widget'; +import { Delayer } from 'vs/base/common/async'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contrib/find/findState'; +import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox'; +import { SimpleButton } from 'vs/editor/contrib/find/findWidget'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry'; +import { IColorTheme, registerThemingParticipant, IThemeService } from 'vs/platform/theme/common/themeService'; +import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/browser/contextScopedHistoryWidget'; +import { ReplaceInput, IReplaceInputStyles } from 'vs/base/browser/ui/findinput/replaceInput'; +import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; +import { attachProgressBarStyler } from 'vs/platform/theme/common/styler'; + +const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find"); +const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find"); +const NLS_PREVIOUS_MATCH_BTN_LABEL = nls.localize('label.previousMatchButton', "Previous match"); +const NLS_NEXT_MATCH_BTN_LABEL = nls.localize('label.nextMatchButton', "Next match"); +const NLS_CLOSE_BTN_LABEL = nls.localize('label.closeButton', "Close"); +const NLS_TOGGLE_REPLACE_MODE_BTN_LABEL = nls.localize('label.toggleReplaceButton', "Toggle Replace mode"); +const NLS_REPLACE_INPUT_LABEL = nls.localize('label.replace', "Replace"); +const NLS_REPLACE_INPUT_PLACEHOLDER = nls.localize('placeholder.replace', "Replace"); +const NLS_REPLACE_BTN_LABEL = nls.localize('label.replaceButton', "Replace"); +const NLS_REPLACE_ALL_BTN_LABEL = nls.localize('label.replaceAllButton', "Replace All"); + +export abstract class SimpleFindReplaceWidget extends Widget { + protected readonly _findInput: FindInput; + private readonly _domNode: HTMLElement; + private readonly _innerFindDomNode: HTMLElement; + private readonly _focusTracker: dom.IFocusTracker; + private readonly _findInputFocusTracker: dom.IFocusTracker; + private readonly _updateHistoryDelayer: Delayer; + private readonly prevBtn: SimpleButton; + private readonly nextBtn: SimpleButton; + + private readonly _replaceInput!: ReplaceInput; + private readonly _innerReplaceDomNode!: HTMLElement; + private _toggleReplaceBtn!: SimpleButton; + private readonly _replaceInputFocusTracker!: dom.IFocusTracker; + private _replaceBtn!: SimpleButton; + private _replaceAllBtn!: SimpleButton; + + + private _isVisible: boolean = false; + private _isReplaceVisible: boolean = false; + private foundMatch: boolean = false; + + protected _progressBar!: ProgressBar; + + + constructor( + @IContextViewService private readonly _contextViewService: IContextViewService, + @IContextKeyService contextKeyService: IContextKeyService, + @IThemeService private readonly _themeService: IThemeService, + private readonly _state: FindReplaceState = new FindReplaceState(), + showOptionButtons?: boolean + ) { + super(); + + this._domNode = document.createElement('div'); + this._domNode.classList.add('simple-fr-find-part-wrapper'); + this._register(this._state.onFindReplaceStateChange((e) => this._onStateChanged(e))); + + let progressContainer = dom.$('.find-replace-progress'); + this._progressBar = new ProgressBar(progressContainer); + this._register(attachProgressBarStyler(this._progressBar, this._themeService)); + this._domNode.appendChild(progressContainer); + + // Toggle replace button + this._toggleReplaceBtn = this._register(new SimpleButton({ + label: NLS_TOGGLE_REPLACE_MODE_BTN_LABEL, + className: 'codicon toggle left', + onTrigger: () => { + this._isReplaceVisible = !this._isReplaceVisible; + this._state.change({ isReplaceRevealed: this._isReplaceVisible }, false); + if (this._isReplaceVisible) { + this._innerReplaceDomNode.style.display = 'flex'; + } else { + this._innerReplaceDomNode.style.display = 'none'; + } + } + })); + this._toggleReplaceBtn.toggleClass('codicon-chevron-down', this._isReplaceVisible); + this._toggleReplaceBtn.toggleClass('codicon-chevron-right', !this._isReplaceVisible); + this._toggleReplaceBtn.setExpanded(this._isReplaceVisible); + this._domNode.appendChild(this._toggleReplaceBtn.domNode); + + + this._innerFindDomNode = document.createElement('div'); + this._innerFindDomNode.classList.add('simple-fr-find-part'); + + this._findInput = this._register(new ContextScopedFindInput(null, this._contextViewService, { + label: NLS_FIND_INPUT_LABEL, + placeholder: NLS_FIND_INPUT_PLACEHOLDER, + validation: (value: string): InputBoxMessage | null => { + if (value.length === 0 || !this._findInput.getRegex()) { + return null; + } + try { + new RegExp(value); + return null; + } catch (e) { + this.foundMatch = false; + this.updateButtons(this.foundMatch); + return { content: e.message }; + } + } + }, contextKeyService, showOptionButtons)); + + // Find History with update delayer + this._updateHistoryDelayer = new Delayer(500); + + this.oninput(this._findInput.domNode, (e) => { + this.foundMatch = this.onInputChanged(); + this.updateButtons(this.foundMatch); + this._delayedUpdateHistory(); + }); + + this._findInput.setRegex(!!this._state.isRegex); + this._findInput.setCaseSensitive(!!this._state.matchCase); + this._findInput.setWholeWords(!!this._state.wholeWord); + + this._register(this._findInput.onDidOptionChange(() => { + this._state.change({ + isRegex: this._findInput.getRegex(), + wholeWord: this._findInput.getWholeWords(), + matchCase: this._findInput.getCaseSensitive() + }, true); + })); + + this._register(this._state.onFindReplaceStateChange(() => { + this._findInput.setRegex(this._state.isRegex); + this._findInput.setWholeWords(this._state.wholeWord); + this._findInput.setCaseSensitive(this._state.matchCase); + this.findFirst(); + })); + + this.prevBtn = this._register(new SimpleButton({ + label: NLS_PREVIOUS_MATCH_BTN_LABEL, + className: 'codicon codicon-arrow-up', + onTrigger: () => { + this.find(true); + } + })); + + this.nextBtn = this._register(new SimpleButton({ + label: NLS_NEXT_MATCH_BTN_LABEL, + className: 'codicon codicon-arrow-down', + onTrigger: () => { + this.find(false); + } + })); + + const closeBtn = this._register(new SimpleButton({ + label: NLS_CLOSE_BTN_LABEL, + className: 'codicon codicon-close', + onTrigger: () => { + this.hide(); + } + })); + + this._innerFindDomNode.appendChild(this._findInput.domNode); + this._innerFindDomNode.appendChild(this.prevBtn.domNode); + this._innerFindDomNode.appendChild(this.nextBtn.domNode); + this._innerFindDomNode.appendChild(closeBtn.domNode); + + // _domNode wraps _innerDomNode, ensuring that + this._domNode.appendChild(this._innerFindDomNode); + + this.onkeyup(this._innerFindDomNode, e => { + if (e.equals(KeyCode.Escape)) { + this.hide(); + e.preventDefault(); + return; + } + }); + + this._focusTracker = this._register(dom.trackFocus(this._innerFindDomNode)); + this._register(this._focusTracker.onDidFocus(this.onFocusTrackerFocus.bind(this))); + this._register(this._focusTracker.onDidBlur(this.onFocusTrackerBlur.bind(this))); + + this._findInputFocusTracker = this._register(dom.trackFocus(this._findInput.domNode)); + this._register(this._findInputFocusTracker.onDidFocus(this.onFindInputFocusTrackerFocus.bind(this))); + this._register(this._findInputFocusTracker.onDidBlur(this.onFindInputFocusTrackerBlur.bind(this))); + + this._register(dom.addDisposableListener(this._innerFindDomNode, 'click', (event) => { + event.stopPropagation(); + })); + + // Replace + this._innerReplaceDomNode = document.createElement('div'); + this._innerReplaceDomNode.classList.add('simple-fr-replace-part'); + + this._replaceInput = this._register(new ContextScopedReplaceInput(null, undefined, { + label: NLS_REPLACE_INPUT_LABEL, + placeholder: NLS_REPLACE_INPUT_PLACEHOLDER, + history: [] + }, contextKeyService, false)); + this._innerReplaceDomNode.appendChild(this._replaceInput.domNode); + this._replaceInputFocusTracker = this._register(dom.trackFocus(this._replaceInput.domNode)); + this._register(this._replaceInputFocusTracker.onDidFocus(this.onReplaceInputFocusTrackerFocus.bind(this))); + this._register(this._replaceInputFocusTracker.onDidBlur(this.onReplaceInputFocusTrackerBlur.bind(this))); + + this._domNode.appendChild(this._innerReplaceDomNode); + + if (this._isReplaceVisible) { + this._innerReplaceDomNode.style.display = 'flex'; + } else { + this._innerReplaceDomNode.style.display = 'none'; + } + + this._replaceBtn = this._register(new SimpleButton({ + label: NLS_REPLACE_BTN_LABEL, + className: 'codicon codicon-replace', + onTrigger: () => { + this.replaceOne(); + } + })); + + // Replace all button + this._replaceAllBtn = this._register(new SimpleButton({ + label: NLS_REPLACE_ALL_BTN_LABEL, + className: 'codicon codicon-replace-all', + onTrigger: () => { + this.replaceAll(); + } + })); + + this._innerReplaceDomNode.appendChild(this._replaceBtn.domNode); + this._innerReplaceDomNode.appendChild(this._replaceAllBtn.domNode); + + + } + + protected abstract onInputChanged(): boolean; + protected abstract find(previous: boolean): void; + protected abstract findFirst(): void; + protected abstract replaceOne(): void; + protected abstract replaceAll(): void; + protected abstract onFocusTrackerFocus(): void; + protected abstract onFocusTrackerBlur(): void; + protected abstract onFindInputFocusTrackerFocus(): void; + protected abstract onFindInputFocusTrackerBlur(): void; + protected abstract onReplaceInputFocusTrackerFocus(): void; + protected abstract onReplaceInputFocusTrackerBlur(): void; + + protected get inputValue() { + return this._findInput.getValue(); + } + + protected get replaceValue() { + return this._replaceInput.getValue(); + } + + public get focusTracker(): dom.IFocusTracker { + return this._focusTracker; + } + + public updateTheme(theme: IColorTheme): void { + const inputStyles: IFindInputStyles = { + inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder), + inputActiveOptionBackground: theme.getColor(inputActiveOptionBackground), + inputBackground: theme.getColor(inputBackground), + inputForeground: theme.getColor(inputForeground), + inputBorder: theme.getColor(inputBorder), + inputValidationInfoBackground: theme.getColor(inputValidationInfoBackground), + inputValidationInfoForeground: theme.getColor(inputValidationInfoForeground), + inputValidationInfoBorder: theme.getColor(inputValidationInfoBorder), + inputValidationWarningBackground: theme.getColor(inputValidationWarningBackground), + inputValidationWarningForeground: theme.getColor(inputValidationWarningForeground), + inputValidationWarningBorder: theme.getColor(inputValidationWarningBorder), + inputValidationErrorBackground: theme.getColor(inputValidationErrorBackground), + inputValidationErrorForeground: theme.getColor(inputValidationErrorForeground), + inputValidationErrorBorder: theme.getColor(inputValidationErrorBorder) + }; + this._findInput.style(inputStyles); + const replaceStyles: IReplaceInputStyles = { + inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder), + inputActiveOptionBackground: theme.getColor(inputActiveOptionBackground), + inputBackground: theme.getColor(inputBackground), + inputForeground: theme.getColor(inputForeground), + inputBorder: theme.getColor(inputBorder), + inputValidationInfoBackground: theme.getColor(inputValidationInfoBackground), + inputValidationInfoForeground: theme.getColor(inputValidationInfoForeground), + inputValidationInfoBorder: theme.getColor(inputValidationInfoBorder), + inputValidationWarningBackground: theme.getColor(inputValidationWarningBackground), + inputValidationWarningForeground: theme.getColor(inputValidationWarningForeground), + inputValidationWarningBorder: theme.getColor(inputValidationWarningBorder), + inputValidationErrorBackground: theme.getColor(inputValidationErrorBackground), + inputValidationErrorForeground: theme.getColor(inputValidationErrorForeground), + inputValidationErrorBorder: theme.getColor(inputValidationErrorBorder) + }; + this._replaceInput.style(replaceStyles); + } + + private _onStateChanged(e: FindReplaceStateChangedEvent): void { + this._updateButtons(); + } + + private _updateButtons(): void { + this._findInput.setEnabled(this._isVisible); + this._replaceInput.setEnabled(this._isVisible && this._isReplaceVisible); + let findInputIsNonEmpty = (this._state.searchString.length > 0); + this._replaceBtn.setEnabled(this._isVisible && this._isReplaceVisible && findInputIsNonEmpty); + this._replaceAllBtn.setEnabled(this._isVisible && this._isReplaceVisible && findInputIsNonEmpty); + + dom.toggleClass(this._domNode, 'replaceToggled', this._isReplaceVisible); + this._toggleReplaceBtn.toggleClass('codicon-chevron-right', !this._isReplaceVisible); + this._toggleReplaceBtn.toggleClass('codicon-chevron-down', this._isReplaceVisible); + this._toggleReplaceBtn.setExpanded(this._isReplaceVisible); + } + + + dispose() { + super.dispose(); + + if (this._domNode && this._domNode.parentElement) { + this._domNode.parentElement.removeChild(this._domNode); + } + } + + public getDomNode() { + return this._domNode; + } + + public reveal(initialInput?: string): void { + if (initialInput) { + this._findInput.setValue(initialInput); + } + + if (this._isVisible) { + this._findInput.select(); + return; + } + + this._isVisible = true; + this.updateButtons(this.foundMatch); + + setTimeout(() => { + dom.addClass(this._domNode, 'visible'); + dom.addClass(this._domNode, 'visible-transition'); + this._domNode.setAttribute('aria-hidden', 'false'); + this._findInput.select(); + }, 0); + } + + public show(initialInput?: string): void { + if (initialInput && !this._isVisible) { + this._findInput.setValue(initialInput); + } + + this._isVisible = true; + + setTimeout(() => { + dom.addClass(this._domNode, 'visible'); + dom.addClass(this._domNode, 'visible-transition'); + this._domNode.setAttribute('aria-hidden', 'false'); + }, 0); + } + + public hide(): void { + if (this._isVisible) { + dom.removeClass(this._domNode, 'visible-transition'); + this._domNode.setAttribute('aria-hidden', 'true'); + // Need to delay toggling visibility until after Transition, then visibility hidden - removes from tabIndex list + setTimeout(() => { + this._isVisible = false; + this.updateButtons(this.foundMatch); + dom.removeClass(this._domNode, 'visible'); + }, 200); + } + } + + protected _delayedUpdateHistory() { + this._updateHistoryDelayer.trigger(this._updateHistory.bind(this)); + } + + protected _updateHistory() { + this._findInput.inputBox.addToHistory(); + } + + protected _getRegexValue(): boolean { + return this._findInput.getRegex(); + } + + protected _getWholeWordValue(): boolean { + return this._findInput.getWholeWords(); + } + + protected _getCaseSensitiveValue(): boolean { + return this._findInput.getCaseSensitive(); + } + + protected updateButtons(foundMatch: boolean) { + const hasInput = this.inputValue.length > 0; + this.prevBtn.setEnabled(this._isVisible && hasInput && foundMatch); + this.nextBtn.setEnabled(this._isVisible && hasInput && foundMatch); + } +} + +// theming +registerThemingParticipant((theme, collector) => { + const findWidgetBGColor = theme.getColor(editorWidgetBackground); + if (findWidgetBGColor) { + collector.addRule(`.monaco-workbench .simple-fr-find-part-wrapper { background-color: ${findWidgetBGColor} !important; }`); + } + + const widgetForeground = theme.getColor(editorWidgetForeground); + if (widgetForeground) { + collector.addRule(`.monaco-workbench .simple-fr-find-part-wrapper { color: ${widgetForeground}; }`); + } + + const widgetShadowColor = theme.getColor(widgetShadow); + if (widgetShadowColor) { + collector.addRule(`.monaco-workbench .simple-fr-find-part-wrapper { box-shadow: 0 2px 8px ${widgetShadowColor}; }`); + } +}); diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css index 4a796be0870..2be494fd722 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css @@ -43,42 +43,17 @@ min-width: 20px; width: 20px; height: 20px; + line-height: 20px; display: flex; flex: initial; + justify-content: center; margin-left: 3px; background-position: center center; background-repeat: no-repeat; cursor: pointer; } -.monaco-workbench .simple-find-part .button.previous { - background-image: url('images/chevron-previous-light.svg'); -} - -.monaco-workbench .simple-find-part .button.next { - background-image: url('images/chevron-next-light.svg'); -} - -.monaco-workbench .simple-find-part .button.close-fw { - background-image: url('images/close-light.svg'); -} - -.hc-black .monaco-workbench .simple-find-part .button.previous, -.vs-dark .monaco-workbench .simple-find-part .button.previous { - background-image: url('images/chevron-previous-dark.svg'); -} - -.hc-black .monaco-workbench .simple-find-part .button.next, -.vs-dark .monaco-workbench .simple-find-part .button.next { - background-image: url('images/chevron-next-dark.svg'); -} - -.hc-black .monaco-workbench .simple-find-part .button.close-fw, -.vs-dark .monaco-workbench .simple-find-part .button.close-fw { - background-image: url('images/close-dark.svg'); -} - .monaco-workbench .simple-find-part .button.disabled { opacity: 0.3; cursor: default; -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts index 5e58e2ef155..5024be3fea4 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts @@ -16,7 +16,7 @@ import { SimpleButton } from 'vs/editor/contrib/find/findWidget'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry'; -import { ITheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ContextScopedFindInput } from 'vs/platform/browser/contextScopedHistoryWidget'; const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find"); @@ -94,7 +94,7 @@ export abstract class SimpleFindWidget extends Widget { this.prevBtn = this._register(new SimpleButton({ label: NLS_PREVIOUS_MATCH_BTN_LABEL, - className: 'previous', + className: 'codicon codicon-arrow-up', onTrigger: () => { this.find(true); } @@ -102,7 +102,7 @@ export abstract class SimpleFindWidget extends Widget { this.nextBtn = this._register(new SimpleButton({ label: NLS_NEXT_MATCH_BTN_LABEL, - className: 'next', + className: 'codicon codicon-arrow-down', onTrigger: () => { this.find(false); } @@ -110,7 +110,7 @@ export abstract class SimpleFindWidget extends Widget { const closeBtn = this._register(new SimpleButton({ label: NLS_CLOSE_BTN_LABEL, - className: 'close-fw', + className: 'codicon codicon-close', onTrigger: () => { this.hide(); } @@ -165,7 +165,7 @@ export abstract class SimpleFindWidget extends Widget { return this._focusTracker; } - public updateTheme(theme: ITheme): void { + public updateTheme(theme: IColorTheme): void { const inputStyles: IFindInputStyles = { inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder), inputActiveOptionBackground: theme.getColor(inputActiveOptionBackground), diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts index c07914d0258..cda6157b97c 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts @@ -26,7 +26,7 @@ import { findMatchingThemeRule } from 'vs/workbench/services/textMate/common/TMH import { ITextMateService, IGrammar, IToken, StackElement } from 'vs/workbench/services/textMate/common/textMateService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { ColorThemeData, TokenStyleDefinitions, TokenStyleDefinition } from 'vs/workbench/services/themes/common/colorThemeData'; +import { ColorThemeData, TokenStyleDefinitions, TokenStyleDefinition, TextMateThemingRuleDefinitions } from 'vs/workbench/services/themes/common/colorThemeData'; import { TokenStylingRule, TokenStyleData, TokenStyle } from 'vs/platform/theme/common/tokenClassificationRegistry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -193,12 +193,11 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { private readonly _editor: IActiveCodeEditor; private readonly _modeService: IModeService; private readonly _themeService: IWorkbenchThemeService; + private readonly _textMateService: ITextMateService; private readonly _notificationService: INotificationService; private readonly _configurationService: IConfigurationService; private readonly _model: ITextModel; private readonly _domNode: HTMLElement; - private readonly _grammar: Promise; - private readonly _semanticTokens: Promise; private readonly _currentRequestCancellationTokenSource: CancellationTokenSource; constructor( @@ -214,16 +213,17 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { this._editor = editor; this._modeService = modeService; this._themeService = themeService; + this._textMateService = textMateService; this._notificationService = notificationService; this._configurationService = configurationService; this._model = this._editor.getModel(); this._domNode = document.createElement('div'); this._domNode.className = 'token-inspect-widget'; this._currentRequestCancellationTokenSource = new CancellationTokenSource(); - this._grammar = textMateService.createGrammar(this._model.getLanguageIdentifier().language); - this._semanticTokens = this._computeSemanticTokens(); this._beginCompute(this._editor.getPosition()); this._register(this._editor.onDidChangeCursorPosition((e) => this._beginCompute(this._editor.getPosition()))); + this._register(themeService.onDidColorThemeChange(_ => this._beginCompute(this._editor.getPosition()))); + this._register(configurationService.onDidChangeConfiguration(e => e.affectsConfiguration('editor.semanticHighlighting.enabled') && this._beginCompute(this._editor.getPosition()))); this._editor.addContentWidget(this); } @@ -239,10 +239,13 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { } private _beginCompute(position: Position): void { + const grammar = this._textMateService.createGrammar(this._model.getLanguageIdentifier().language); + const semanticTokens = this._computeSemanticTokens(); + dom.clearNode(this._domNode); this._domNode.appendChild(document.createTextNode(nls.localize('inspectTMScopesWidget.loading', "Loading..."))); - Promise.all([this._grammar, this._semanticTokens]).then(([grammar, semanticTokens]) => { + Promise.all([grammar, semanticTokens]).then(([grammar, semanticTokens]) => { if (this._isDisposed) { return; } @@ -260,6 +263,9 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { } private _isSemanticColoringEnabled() { + if (!this._themeService.getColorTheme().semanticHighlighting) { + return false; + } const options = this._configurationService.getValue('editor.semanticHighlighting', { overrideIdentifier: this._model.getLanguageIdentifier().language, resource: this._model.uri }); return options && options.enabled; } @@ -303,7 +309,7 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { const allDefValues = []; // remember the order // first collect to detect when the same rule is used fro multiple properties for (let property of properties) { - if (semanticTokenInfo.metadata[property]) { + if (semanticTokenInfo.metadata[property] !== undefined) { const definition = semanticTokenInfo.definitions[property]; const defValue = this._renderTokenStyleDefinition(definition, property); let properties = propertiesByDefValue[defValue]; @@ -488,6 +494,7 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { private _getSemanticTokenAtPosition(semanticTokens: SemanticTokensResult, pos: Position): ISemanticTokenInfo | null { const tokenData = semanticTokens.tokens.data; + const defaultLanguage = this._model.getLanguageIdentifier().language; let lastLine = 0; let lastCharacter = 0; const posLine = pos.lineNumber - 1, posCharacter = pos.column - 1; // to 0-based position @@ -501,8 +508,8 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { const range = new Range(line + 1, character + 1, line + 1, character + 1 + len); const definitions = {}; const colorMap = this._themeService.getColorTheme().tokenColorMap; - const theme = this._themeService.getTheme() as ColorThemeData; - const tokenStyle = theme.getTokenStyleMetadata(type, modifiers, true, definitions); + const theme = this._themeService.getColorTheme() as ColorThemeData; + const tokenStyle = theme.getTokenStyleMetadata(type, modifiers, defaultLanguage, true, definitions); let metadata: IDecodedMetadata | undefined = undefined; if (tokenStyle) { @@ -528,35 +535,27 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { if (definition === undefined) { return ''; } - const theme = this._themeService.getTheme() as ColorThemeData; + const theme = this._themeService.getColorTheme() as ColorThemeData; - const isTokenStylingRule = (d: any): d is TokenStylingRule => !!d.value; if (Array.isArray(definition)) { - for (const d of definition) { - const matchingRule = findMatchingThemeRule(theme, d, false); - if (matchingRule) { - return `${escape(d.join(' '))}
${matchingRule.rawSelector}\n${JSON.stringify(matchingRule.settings, null, '\t')}`; - } + const scopesDefinition: TextMateThemingRuleDefinitions = {}; + theme.resolveScopes(definition, scopesDefinition); + const matchingRule = scopesDefinition[property]; + if (matchingRule && scopesDefinition.scope) { + return `${escape(scopesDefinition.scope.join(' '))}
${matchingRule.scope}\n${JSON.stringify(matchingRule.settings, null, '\t')}`; } return ''; - } else if (isTokenStylingRule(definition)) { + } else if (TokenStylingRule.is(definition)) { const scope = theme.getTokenStylingRuleScope(definition); if (scope === 'setting') { - return `User settings: ${definition.selector.selectorString} - ${this._renderStyleProperty(definition.style, property)}`; + return `User settings: ${definition.selector.id} - ${this._renderStyleProperty(definition.style, property)}`; } else if (scope === 'theme') { - return `Color theme: ${definition.selector.selectorString} - ${this._renderStyleProperty(definition.style, property)}`; - } - return ''; - } else if (typeof definition === 'string') { - const [type, ...modifiers] = definition.split('.'); - const definitions: TokenStyleDefinitions = {}; - const m = theme.getTokenStyleMetadata(type, modifiers, true, definitions); - if (m && definitions.foreground) { - return this._renderTokenStyleDefinition(definitions[property], property); + return `Color theme: ${definition.selector.id} - ${this._renderStyleProperty(definition.style, property)}`; } return ''; } else { - return this._renderStyleProperty(definition, property); + const style = theme.resolveTokenStyleValue(definition); + return `Default: ${style ? this._renderStyleProperty(style, property) : ''}`; } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts b/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts index 2febc613bf7..8268057aff8 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts @@ -11,6 +11,7 @@ import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; /** * Shows a message when opening a large file which has been memory optimized (and features disabled). @@ -23,9 +24,13 @@ export class LargeFileOptimizationsWarner extends Disposable implements IEditorC private readonly _editor: ICodeEditor, @INotificationService private readonly _notificationService: INotificationService, @IConfigurationService private readonly _configurationService: IConfigurationService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService ) { super(); + // opt-in to syncing + const neverShowAgainId = 'editor.contrib.largeFileOptimizationsWarner'; + storageKeysSyncRegistryService.registerStorageKey({ key: neverShowAgainId, version: 1 }); this._register(this._editor.onDidChangeModel((e) => { const model = this._editor.getModel(); @@ -56,7 +61,7 @@ export class LargeFileOptimizationsWarner extends Disposable implements IEditorC }); } } - ], { neverShowAgain: { id: 'editor.contrib.largeFileOptimizationsWarner' } }); + ], { neverShowAgain: { id: neverShowAgainId } }); } })); } diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts new file mode 100644 index 00000000000..58bf84293ab --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts @@ -0,0 +1,90 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IKeyMods, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IEditor } from 'vs/editor/common/editorCommon'; +import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { IRange } from 'vs/editor/common/core/range'; +import { AbstractGotoLineQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoLineQuickAccess'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IQuickAccessRegistry, Extensions as QuickaccesExtensions } from 'vs/platform/quickinput/common/quickAccess'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; +import { Action } from 'vs/base/common/actions'; +import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; + +export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProvider { + + protected readonly onDidActiveTextEditorControlChange = this.editorService.onDidActiveEditorChange; + + constructor( + @IEditorService private readonly editorService: IEditorService, + @IConfigurationService private readonly configurationService: IConfigurationService + ) { + super(); + } + + private get configuration() { + const editorConfig = this.configurationService.getValue().workbench.editor; + + return { + openEditorPinned: !editorConfig.enablePreviewFromQuickOpen, + }; + } + + protected get activeTextEditorControl() { + return this.editorService.activeTextEditorControl; + } + + protected gotoLocation(editor: IEditor, options: { range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean, preserveFocus?: boolean }): void { + + // Check for sideBySide use + if ((options.keyMods.ctrlCmd || options.forceSideBySide) && this.editorService.activeEditor) { + this.editorService.openEditor(this.editorService.activeEditor, { + selection: options.range, + pinned: options.keyMods.alt || this.configuration.openEditorPinned, + preserveFocus: options.preserveFocus + }, SIDE_GROUP); + } + + // Otherwise let parent handle it + else { + super.gotoLocation(editor, options); + } + } +} + +Registry.as(QuickaccesExtensions.Quickaccess).registerQuickAccessProvider({ + ctor: GotoLineQuickAccessProvider, + prefix: AbstractGotoLineQuickAccessProvider.PREFIX, + placeholder: localize('gotoLineQuickAccessPlaceholder', "Type the line number and optional column to go to (e.g. 42:5 for line 42 and column 5)."), + helpEntries: [{ description: localize('gotoLineQuickAccess', "Go to Line/Column"), needsEditor: true }] +}); + +export class GotoLineAction extends Action { + + static readonly ID = 'workbench.action.gotoLine'; + static readonly LABEL = localize('gotoLine', "Go to Line/Column..."); + + constructor( + id: string, + label: string, + @IQuickInputService private readonly quickInputService: IQuickInputService + ) { + super(id, label); + } + + async run(): Promise { + this.quickInputService.quickAccess.show(GotoLineQuickAccessProvider.PREFIX); + } +} + +Registry.as(ActionExtensions.WorkbenchActions).registerWorkbenchAction(SyncActionDescriptor.create(GotoLineAction, GotoLineAction.ID, GotoLineAction.LABEL, { + primary: KeyMod.CtrlCmd | KeyCode.KEY_G, + mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_G } +}), 'Go to Line/Column...'); diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts new file mode 100644 index 00000000000..efe719f7483 --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts @@ -0,0 +1,134 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IKeyMods, IQuickPickSeparator, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IEditor } from 'vs/editor/common/editorCommon'; +import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { IRange } from 'vs/editor/common/core/range'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IQuickAccessRegistry, Extensions as QuickaccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; +import { AbstractGotoSymbolQuickAccessProvider, IGotoSymbolQuickPickItem } from 'vs/editor/contrib/quickAccess/gotoSymbolQuickAccess'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; +import { ITextModel } from 'vs/editor/common/model'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { timeout } from 'vs/base/common/async'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Action } from 'vs/base/common/actions'; +import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; +import { prepareQuery } from 'vs/base/common/fuzzyScorer'; + +export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccessProvider { + + protected readonly onDidActiveTextEditorControlChange = this.editorService.onDidActiveEditorChange; + + constructor( + @IEditorService private readonly editorService: IEditorService, + @IConfigurationService private readonly configurationService: IConfigurationService + ) { + super({ + openSideBySideDirection: () => this.configuration.openSideBySideDirection + }); + } + + private get configuration() { + const editorConfig = this.configurationService.getValue().workbench.editor; + + return { + openEditorPinned: !editorConfig.enablePreviewFromQuickOpen, + openSideBySideDirection: editorConfig.openSideBySideDirection + }; + } + + protected get activeTextEditorControl() { + return this.editorService.activeTextEditorControl; + } + + protected gotoLocation(editor: IEditor, options: { range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean, preserveFocus?: boolean }): void { + + // Check for sideBySide use + if ((options.keyMods.ctrlCmd || options.forceSideBySide) && this.editorService.activeEditor) { + this.editorService.openEditor(this.editorService.activeEditor, { + selection: options.range, + pinned: options.keyMods.alt || this.configuration.openEditorPinned, + preserveFocus: options.preserveFocus + }, SIDE_GROUP); + } + + // Otherwise let parent handle it + else { + super.gotoLocation(editor, options); + } + } + + + //#region public methods to use this picker from other pickers + + private static readonly SYMBOL_PICKS_TIMEOUT = 8000; + + async getSymbolPicks(model: ITextModel, filter: string, options: { extraContainerLabel?: string }, disposables: DisposableStore, token: CancellationToken): Promise> { + + // If the registry does not know the model, we wait for as long as + // the registry knows it. This helps in cases where a language + // registry was not activated yet for providing any symbols. + // To not wait forever, we eventually timeout though. + const result = await Promise.race([ + this.waitForLanguageSymbolRegistry(model, disposables), + timeout(GotoSymbolQuickAccessProvider.SYMBOL_PICKS_TIMEOUT) + ]); + + if (!result || token.isCancellationRequested) { + return []; + } + + return this.doGetSymbolPicks(this.getDocumentSymbols(model, true, token), prepareQuery(filter), options, token); + } + + addDecorations(editor: IEditor, range: IRange): void { + super.addDecorations(editor, range); + } + + clearDecorations(editor: IEditor): void { + super.clearDecorations(editor); + } + + //#endregion +} + +Registry.as(QuickaccessExtensions.Quickaccess).registerQuickAccessProvider({ + ctor: GotoSymbolQuickAccessProvider, + prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX, + contextKey: 'inFileSymbolsPicker', + placeholder: localize('gotoSymbolQuickAccessPlaceholder', "Type the name of a symbol to go to."), + helpEntries: [ + { description: localize('gotoSymbolQuickAccess', "Go to Symbol in Editor"), prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX, needsEditor: true }, + { description: localize('gotoSymbolByCategoryQuickAccess', "Go to Symbol in Editor by Category"), prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX_BY_CATEGORY, needsEditor: true } + ] +}); + +export class GotoSymbolAction extends Action { + + static readonly ID = 'workbench.action.gotoSymbol'; + static readonly LABEL = localize('gotoSymbol', "Go to Symbol in Editor..."); + + constructor( + id: string, + label: string, + @IQuickInputService private readonly quickInputService: IQuickInputService + ) { + super(id, label); + } + + async run(): Promise { + this.quickInputService.quickAccess.show(GotoSymbolQuickAccessProvider.PREFIX); + } +} + +Registry.as(ActionExtensions.WorkbenchActions).registerWorkbenchAction(SyncActionDescriptor.create(GotoSymbolAction, GotoSymbolAction.ID, GotoSymbolAction.LABEL, { + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_O +}), 'Go to Symbol in Editor...'); diff --git a/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts index 32f7f4a1722..48756921c1d 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts @@ -23,7 +23,7 @@ import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProgressStep, IProgress, Progress } from 'vs/platform/progress/common/progress'; -import { IResolvedTextFileEditorModel, ITextFileService, ITextFileSaveParticipant } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, ITextFileSaveParticipant, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { SaveReason } from 'vs/workbench/common/editor'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution, Extensions as WorkbenchContributionsExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; @@ -39,7 +39,11 @@ export class TrimWhitespaceParticipant implements ITextFileSaveParticipant { // Nothing } - async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { + async participate(model: ITextFileEditorModel, env: { reason: SaveReason; }): Promise { + if (!model.textEditorModel) { + return; + } + if (this.configurationService.getValue('files.trimTrailingWhitespace', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) { this.doTrimTrailingWhitespace(model.textEditorModel, env.reason === SaveReason.AUTO); } @@ -101,7 +105,11 @@ export class FinalNewLineParticipant implements ITextFileSaveParticipant { // Nothing } - async participate(model: IResolvedTextFileEditorModel, _env: { reason: SaveReason; }): Promise { + async participate(model: ITextFileEditorModel, _env: { reason: SaveReason; }): Promise { + if (!model.textEditorModel) { + return; + } + if (this.configurationService.getValue('files.insertFinalNewline', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) { this.doInsertFinalNewLine(model.textEditorModel); } @@ -135,7 +143,11 @@ export class TrimFinalNewLinesParticipant implements ITextFileSaveParticipant { // Nothing } - async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { + async participate(model: ITextFileEditorModel, env: { reason: SaveReason; }): Promise { + if (!model.textEditorModel) { + return; + } + if (this.configurationService.getValue('files.trimFinalNewlines', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) { this.doTrimFinalNewLines(model.textEditorModel, env.reason === SaveReason.AUTO); } @@ -205,9 +217,13 @@ class FormatOnSaveParticipant implements ITextFileSaveParticipant { // Nothing } - async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }, progress: IProgress, token: CancellationToken): Promise { - const model = editorModel.textEditorModel; - const overrides = { overrideIdentifier: model.getLanguageIdentifier().language, resource: model.uri }; + async participate(model: ITextFileEditorModel, env: { reason: SaveReason; }, progress: IProgress, token: CancellationToken): Promise { + if (!model.textEditorModel) { + return; + } + + const textEditorModel = model.textEditorModel; + const overrides = { overrideIdentifier: textEditorModel.getLanguageIdentifier().language, resource: textEditorModel.uri }; if (env.reason === SaveReason.AUTO || !this.configurationService.getValue('editor.formatOnSave', overrides)) { return undefined; @@ -222,7 +238,7 @@ class FormatOnSaveParticipant implements ITextFileSaveParticipant { ) }); }); - const editorOrModel = findEditor(model, this.codeEditorService) || model; + const editorOrModel = findEditor(textEditorModel, this.codeEditorService) || textEditorModel; await this.instantiationService.invokeFunction(formatDocumentWithSelectedProvider, editorOrModel, FormattingMode.Silent, nestedProgress, token); } } @@ -234,21 +250,31 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant { @IInstantiationService private readonly instantiationService: IInstantiationService, ) { } - async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }, progress: IProgress, token: CancellationToken): Promise { + async participate(model: ITextFileEditorModel, env: { reason: SaveReason; }, progress: IProgress, token: CancellationToken): Promise { + if (!model.textEditorModel) { + return; + } + if (env.reason === SaveReason.AUTO) { return undefined; } - const model = editorModel.textEditorModel; + const textEditorModel = model.textEditorModel; - const settingsOverrides = { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.resource }; - const setting = this.configurationService.getValue<{ [kind: string]: boolean }>('editor.codeActionsOnSave', settingsOverrides); + const settingsOverrides = { overrideIdentifier: textEditorModel.getLanguageIdentifier().language, resource: model.resource }; + const setting = this.configurationService.getValue<{ [kind: string]: boolean } | string[]>('editor.codeActionsOnSave', settingsOverrides); if (!setting) { return undefined; } - const codeActionsOnSave = Object.keys(setting) - .filter(x => setting[x]).map(x => new CodeActionKind(x)) - .sort((a, b) => { + const settingItems: string[] = Array.isArray(setting) + ? setting + : Object.keys(setting).filter(x => setting[x]); + + const codeActionsOnSave = settingItems + .map(x => new CodeActionKind(x)); + + if (!Array.isArray(setting)) { + codeActionsOnSave.sort((a, b) => { if (CodeActionKind.SourceFixAll.contains(a)) { if (CodeActionKind.SourceFixAll.contains(b)) { return 0; @@ -260,17 +286,20 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant { } return 0; }); + } if (!codeActionsOnSave.length) { return undefined; } - const excludedActions = Object.keys(setting) - .filter(x => setting[x] === false) - .map(x => new CodeActionKind(x)); + const excludedActions = Array.isArray(setting) + ? [] + : Object.keys(setting) + .filter(x => setting[x] === false) + .map(x => new CodeActionKind(x)); progress.report({ message: localize('codeaction', "Quick Fixes") }); - await this.applyOnSaveActions(model, codeActionsOnSave, excludedActions, progress, token); + await this.applyOnSaveActions(textEditorModel, codeActionsOnSave, excludedActions, progress, token); } private async applyOnSaveActions(model: ITextModel, codeActionsOnSave: readonly CodeActionKind[], excludes: readonly CodeActionKind[], progress: IProgress, token: CancellationToken): Promise { diff --git a/src/vs/workbench/contrib/codeEditor/browser/semanticTokensHelp.ts b/src/vs/workbench/contrib/codeEditor/browser/semanticTokensHelp.ts new file mode 100644 index 00000000000..e694687c95b --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/semanticTokensHelp.ts @@ -0,0 +1,109 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import * as path from 'vs/base/common/path'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { URI } from 'vs/base/common/uri'; +import { ITextModel } from 'vs/editor/common/model'; +import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; + +/** + * Shows a message when semantic tokens are shown the first time. + */ +export class SemanticTokensHelp extends Disposable implements IEditorContribution { + + public static readonly ID = 'editor.contrib.semanticHighlightHelp'; + + constructor( + _editor: ICodeEditor, + @INotificationService _notificationService: INotificationService, + @IOpenerService _openerService: IOpenerService, + @IWorkbenchThemeService _themeService: IWorkbenchThemeService, + @IEditorService _editorService: IEditorService, + @IStorageService _storageService: IStorageService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService + ) { + super(); + // opt-in to syncing + const neverShowAgainId = 'editor.contrib.semanticTokensHelp'; + + if (_storageService.getBoolean(neverShowAgainId, StorageScope.GLOBAL)) { + return; + } + + storageKeysSyncRegistryService.registerStorageKey({ key: neverShowAgainId, version: 1 }); + + const toDispose = this._register(new DisposableStore()); + const localToDispose = toDispose.add(new DisposableStore()); + const installChangeTokenListener = (model: ITextModel) => { + localToDispose.add(model.onDidChangeTokens((e) => { + if (!e.semanticTokensApplied) { + return; + } + if (_storageService.getBoolean(neverShowAgainId, StorageScope.GLOBAL)) { + toDispose.dispose(); + return; + } + const activeEditorControl = _editorService.activeTextEditorControl; + if (!isCodeEditor(activeEditorControl) || activeEditorControl.getModel() !== model) { + return; // only show if model is in the active code editor + } + + toDispose.dispose(); // uninstall all listeners, make sure the notification is only shown once per window + _storageService.store(neverShowAgainId, true, StorageScope.GLOBAL); // never show again + + const message = nls.localize( + { + key: 'semanticTokensHelp', + comment: [ + 'Variable 0 will be a file name.', + 'Variable 1 will be a theme name.' + ] + }, + "Code coloring of '{0}' has been updated as the theme '{1}' has [semantic highlighting](https://go.microsoft.com/fwlink/?linkid=2122588) enabled.", + path.basename(model.uri.path), _themeService.getColorTheme().label + ); + + _notificationService.prompt(Severity.Info, message, [ + { + label: nls.localize('learnMoreButton', "Learn More"), + run: () => { + const url = 'https://go.microsoft.com/fwlink/?linkid=2122588'; + + _openerService.open(URI.parse(url)); + } + } + ]); + })); + }; + + + const model = _editor.getModel(); + if (model !== null) { + installChangeTokenListener(model); + } + + toDispose.add(_editor.onDidChangeModel((e) => { + localToDispose.clear(); + + const model = _editor.getModel(); + if (!model) { + return; + } + installChangeTokenListener(model); + })); + } +} + +registerEditorContribution(SemanticTokensHelp.ID, SemanticTokensHelp); diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts index b3a167aca13..17acd2da4b4 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts @@ -19,7 +19,6 @@ import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/commo import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { DefaultSettingsEditorContribution } from 'vs/workbench/contrib/preferences/browser/preferencesEditor'; -import { registerAndGetAmdImageURL } from 'vs/base/common/amd'; const transientWordWrapState = 'transientWordWrapState'; const isWordWrapMinifiedKey = 'isWordWrapMinified'; @@ -272,16 +271,12 @@ registerEditorContribution(ToggleWordWrapController.ID, ToggleWordWrapController registerEditorAction(ToggleWordWrapAction); -const WORD_WRAP_DARK_ICON = URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/codeEditor/browser/word-wrap-dark.svg')); -const WORD_WRAP_LIGHT_ICON = URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/codeEditor/browser/word-wrap-light.svg')); - MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_WORD_WRAP_ID, title: nls.localize('unwrapMinified', "Disable wrapping for this file"), icon: { - dark: WORD_WRAP_DARK_ICON, - light: WORD_WRAP_LIGHT_ICON + id: 'codicon/word-wrap' } }, group: 'navigation', @@ -297,8 +292,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { id: TOGGLE_WORD_WRAP_ID, title: nls.localize('wrapMinified', "Enable wrapping for this file"), icon: { - dark: WORD_WRAP_DARK_ICON, - light: WORD_WRAP_LIGHT_ICON + id: 'codicon/word-wrap' } }, group: 'navigation', diff --git a/src/vs/workbench/contrib/codeEditor/browser/word-wrap-dark.svg b/src/vs/workbench/contrib/codeEditor/browser/word-wrap-dark.svg deleted file mode 100644 index f54d621127f..00000000000 --- a/src/vs/workbench/contrib/codeEditor/browser/word-wrap-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/codeEditor/browser/word-wrap-light.svg b/src/vs/workbench/contrib/codeEditor/browser/word-wrap-light.svg deleted file mode 100644 index 6a8e3fdf933..00000000000 --- a/src/vs/workbench/contrib/codeEditor/browser/word-wrap-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index fbdfdab2e22..4ab7882127b 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -158,7 +158,7 @@ export class CommentNode extends Disposable { }, this.actionRunner!, undefined, - 'toolbar-toggle-pickReactions', + 'toolbar-toggle-pickReactions codicon codicon-reactions', () => { return AnchorAlignment.RIGHT; } ); } diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index e9f233496aa..21ae5cfd0f6 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -35,7 +35,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { contrastBorder, editorForeground, focusBorder, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, textBlockQuoteBackground, textBlockQuoteBorder, textLinkActiveForeground, textLinkForeground, transparent } from 'vs/platform/theme/common/colorRegistry'; -import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { CommentFormActions } from 'vs/workbench/contrib/comments/browser/commentFormActions'; import { CommentGlyphWidget } from 'vs/workbench/contrib/comments/browser/commentGlyphWidget'; import { CommentMenus } from 'vs/workbench/contrib/comments/browser/commentMenus'; @@ -50,7 +50,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; export const COMMENTEDITOR_DECORATION_KEY = 'commenteditordecoration'; -const COLLAPSE_ACTION_CLASS = 'expand-review-action'; +const COLLAPSE_ACTION_CLASS = 'expand-review-action codicon-chevron-up'; const COMMENT_SCHEME = 'comment'; @@ -144,13 +144,13 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this.create(); this._styleElement = dom.createStyleSheet(this.domNode); - this._globalToDispose.add(this.themeService.onThemeChange(this._applyTheme, this)); + this._globalToDispose.add(this.themeService.onDidColorThemeChange(this._applyTheme, this)); this._globalToDispose.add(this.editor.onDidChangeConfiguration(e => { if (e.hasChanged(EditorOption.fontInfo)) { - this._applyTheme(this.themeService.getTheme()); + this._applyTheme(this.themeService.getColorTheme()); } })); - this._applyTheme(this.themeService.getTheme()); + this._applyTheme(this.themeService.getColorTheme()); this._markdownRenderer = this._globalToDispose.add(new MarkdownRenderer(editor, this.modeService, this.openerService)); this._parentEditor = editor; @@ -758,7 +758,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget renderOptions: { after: { contentText: placeholder, - color: `${transparent(editorForeground, 0.4)(this.themeService.getTheme())}` + color: `${transparent(editorForeground, 0.4)(this.themeService.getColorTheme())}` } } }]; @@ -828,7 +828,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } } - private _applyTheme(theme: ITheme) { + private _applyTheme(theme: IColorTheme) { const borderColor = theme.getColor(peekViewBorder) || Color.transparent; this.style({ arrowColor: borderColor, diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index 0d5573b88d3..c6e976913b5 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -739,21 +739,21 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ }); export function getActiveEditor(accessor: ServicesAccessor): IActiveCodeEditor | null { - let activeTextEditorWidget = accessor.get(IEditorService).activeTextEditorWidget; + let activeTextEditorControl = accessor.get(IEditorService).activeTextEditorControl; - if (isDiffEditor(activeTextEditorWidget)) { - if (activeTextEditorWidget.getOriginalEditor().hasTextFocus()) { - activeTextEditorWidget = activeTextEditorWidget.getOriginalEditor(); + if (isDiffEditor(activeTextEditorControl)) { + if (activeTextEditorControl.getOriginalEditor().hasTextFocus()) { + activeTextEditorControl = activeTextEditorControl.getOriginalEditor(); } else { - activeTextEditorWidget = activeTextEditorWidget.getModifiedEditor(); + activeTextEditorControl = activeTextEditorControl.getModifiedEditor(); } } - if (!isCodeEditor(activeTextEditorWidget) || !activeTextEditorWidget.hasModel()) { + if (!isCodeEditor(activeTextEditorControl) || !activeTextEditorControl.hasModel()) { return null; } - return activeTextEditorWidget; + return activeTextEditorControl; } registerThemingParticipant((theme, collector) => { diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index f047707083b..1f751f9b21c 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -20,7 +20,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { WorkbenchAsyncDataTree, IListService } from 'vs/platform/list/browser/listService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; +import { IColorMapping } from 'vs/platform/theme/common/styler'; export const COMMENTS_VIEW_ID = 'workbench.panel.comments'; export const COMMENTS_VIEW_TITLE = 'Comments'; @@ -149,10 +149,15 @@ export class CommentNodeRenderer implements IListRenderer } } +export interface ICommentsListOptions { + overrideStyles?: IColorMapping; +} + export class CommentsList extends WorkbenchAsyncDataTree { constructor( labels: ResourceLabels, container: HTMLElement, + options: ICommentsListOptions, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, @@ -202,9 +207,7 @@ export class CommentsList extends WorkbenchAsyncDataTree { collapseByDefault: () => { return false; }, - overrideStyles: { - listBackground: PANEL_BACKGROUND - } + overrideStyles: options.overrideStyles }, contextKeyService, listService, diff --git a/src/vs/workbench/contrib/comments/browser/commentsView.ts b/src/vs/workbench/contrib/comments/browser/commentsView.ts index 700a75d9d5d..8addd0ef105 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsView.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsView.ts @@ -10,7 +10,6 @@ import { IAction, Action } from 'vs/base/common/actions'; import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { TreeResourceNavigator } from 'vs/platform/list/browser/listService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { CommentNode, CommentsModel, ResourceWithCommentThreads, ICommentThreadChangedEvent } from 'vs/workbench/contrib/comments/common/commentModel'; import { CommentController } from 'vs/workbench/contrib/comments/browser/commentsEditorContribution'; @@ -28,6 +27,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ResourceNavigator } from 'vs/platform/list/browser/listService'; export class CommentsPanel extends ViewPane { @@ -55,7 +55,7 @@ export class CommentsPanel extends ViewPane { @ICommentService private readonly commentService: ICommentService, @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), id: COMMENTS_VIEW_ID, ariaHeaderLabel: COMMENTS_VIEW_TITLE }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); } public renderBody(container: HTMLElement): void { @@ -75,7 +75,7 @@ export class CommentsPanel extends ViewPane { const styleElement = dom.createStyleSheet(container); this.applyStyles(styleElement); - this._register(this.themeService.onThemeChange(_ => this.applyStyles(styleElement))); + this._register(this.themeService.onDidColorThemeChange(_ => this.applyStyles(styleElement))); this._register(this.onDidChangeBodyVisibility(visible => { if (visible) { @@ -89,7 +89,7 @@ export class CommentsPanel extends ViewPane { private applyStyles(styleElement: HTMLStyleElement) { const content: string[] = []; - const theme = this.themeService.getTheme(); + const theme = this.themeService.getColorTheme(); const linkColor = theme.getColor(textLinkForeground); if (linkColor) { content.push(`.comments-panel .comments-panel-container a { color: ${linkColor}; }`); @@ -121,7 +121,7 @@ export class CommentsPanel extends ViewPane { public getActions(): IAction[] { if (!this.collapseAllAction) { - this.collapseAllAction = new Action('vs.tree.collapse', nls.localize('collapseAll', "Collapse All"), 'monaco-tree-action collapse-all', true, () => this.tree ? new CollapseAllAction(this.tree, true).run() : Promise.resolve()); + this.collapseAllAction = new Action('vs.tree.collapse', nls.localize('collapseAll', "Collapse All"), 'collapse-all', true, () => this.tree ? new CollapseAllAction(this.tree, true).run() : Promise.resolve()); this._register(this.collapseAllAction); } @@ -149,9 +149,9 @@ export class CommentsPanel extends ViewPane { private createTree(): void { this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this)); - this.tree = this._register(this.instantiationService.createInstance(CommentsList, this.treeLabels, this.treeContainer)); + this.tree = this._register(this.instantiationService.createInstance(CommentsList, this.treeLabels, this.treeContainer, { overrideStyles: { listBackground: this.getBackgroundColor() } })); - const commentsNavigator = this._register(new TreeResourceNavigator(this.tree, { openOnFocus: true })); + const commentsNavigator = this._register(ResourceNavigator.createTreeResourceNavigator(this.tree, { openOnFocus: true })); this._register(commentsNavigator.onDidOpenResource(e => { this.openFile(e.element, e.editorOptions.pinned, e.editorOptions.preserveFocus, e.sideBySide); })); @@ -173,7 +173,7 @@ export class CommentsPanel extends ViewPane { if (currentActiveResource && currentActiveResource.toString() === element.resource.toString()) { const threadToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].threadId : element.threadId; const commentToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].comment.uniqueIdInThread : element.comment.uniqueIdInThread; - const control = this.editorService.activeTextEditorWidget; + const control = this.editorService.activeTextEditorControl; if (threadToReveal && isCodeEditor(control)) { const controller = CommentController.get(control); controller.revealCommentThread(threadToReveal, commentToReveal, false); diff --git a/src/vs/workbench/contrib/comments/browser/media/close-dark.svg b/src/vs/workbench/contrib/comments/browser/media/close-dark.svg deleted file mode 100644 index bf323a41d2c..00000000000 --- a/src/vs/workbench/contrib/comments/browser/media/close-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/comments/browser/media/close-hc.svg b/src/vs/workbench/contrib/comments/browser/media/close-hc.svg deleted file mode 100644 index 29fafa5300a..00000000000 --- a/src/vs/workbench/contrib/comments/browser/media/close-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/comments/browser/media/close-light.svg b/src/vs/workbench/contrib/comments/browser/media/close-light.svg deleted file mode 100644 index f214cc22e3b..00000000000 --- a/src/vs/workbench/contrib/comments/browser/media/close-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/comments/browser/media/close.svg b/src/vs/workbench/contrib/comments/browser/media/close.svg deleted file mode 100644 index 751e89b3b02..00000000000 --- a/src/vs/workbench/contrib/comments/browser/media/close.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/comments/browser/media/reaction-dark.svg b/src/vs/workbench/contrib/comments/browser/media/reaction-dark.svg deleted file mode 100644 index f2772365adb..00000000000 --- a/src/vs/workbench/contrib/comments/browser/media/reaction-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/comments/browser/media/reaction-hc.svg b/src/vs/workbench/contrib/comments/browser/media/reaction-hc.svg deleted file mode 100644 index 3fae8d2124b..00000000000 --- a/src/vs/workbench/contrib/comments/browser/media/reaction-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/comments/browser/media/reaction-light.svg b/src/vs/workbench/contrib/comments/browser/media/reaction-light.svg deleted file mode 100644 index 7933a47cc5d..00000000000 --- a/src/vs/workbench/contrib/comments/browser/media/reaction-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/comments/browser/media/review.css b/src/vs/workbench/contrib/comments/browser/media/review.css index 60ba34714c5..df221e87d14 100644 --- a/src/vs/workbench/contrib/comments/browser/media/review.css +++ b/src/vs/workbench/contrib/comments/browser/media/review.css @@ -89,18 +89,11 @@ white-space: pre; } -.monaco-editor.vs-dark .review-widget .body .comment-body h4 { - margin: 0; -} .monaco-editor .review-widget .body .review-comment .review-comment-contents .author { line-height: 22px; } -.monaco-editor.vs-dark .review-widget .body .review-comment .review-comment-contents .author { - color: #fff; - font-weight: 600; -} .monaco-editor .review-widget .body .review-comment .review-comment-contents .isPending { margin: 0 5px 0 5px; @@ -108,7 +101,7 @@ font-style: italic; } -.monaco-editor.vs-dark .review-widget .body .review-comment .review-comment-contents .comment-body { +.monaco-editor .review-widget .body .review-comment .review-comment-contents .comment-body { padding-top: 4px; } @@ -146,22 +139,8 @@ margin-right: 4px; } -.monaco-editor .review-widget .head .review-actions > .monaco-action-bar .codicon.expand-review-action { - background-image: url("./close-light.svg"); - background-size: 16px; -} - -.monaco-editor.vs-dark .review-widget .head .review-actions > .monaco-action-bar .codicon.expand-review-action { - background-image: url("./close-dark.svg"); -} - -.monaco-editor.hc-black .review-widget .head .review-actions > .monaco-action-bar .codicon.expand-review-action { - background-image: url("./close-hc.svg"); -} - .monaco-editor .review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item a.action-label.toolbar-toggle-pickReactions { display: none; - background-image: url("./reaction-light.svg"); background-size: 16px; width: 26px; height: 16px; @@ -176,14 +155,6 @@ background-size: 16px; } -.monaco-editor.vs-dark .review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item a.action-label.toolbar-toggle-pickReactions { - background-image: url("./reaction-dark.svg"); -} - -.monaco-editor.hc-black .review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item a.action-label.toolbar-toggle-pickReactions { - background-image: url("./reaction-hc.svg"); -} - .monaco-editor .review-widget .body .review-comment .comment-title .action-label { display: block; height: 16px; @@ -193,23 +164,6 @@ background-repeat: no-repeat; } -.monaco-editor .review-widget .body .review-comment .comment-title .action-label.toolbar-toggle-pickReactions { - background-image: url("./reaction-light.svg"); - width: 16px; - height: 16px; - background-size: 16px; - background-position: center; - background-repeat: no-repeat; -} - -.monaco-editor.vs-dark .review-widget .body .review-comment .comment-title .action-label.toolbar-toggle-pickReactions { - background-image: url("./reaction-dark.svg"); -} - -.monaco-editor.hc-black .review-widget .body .review-comment .comment-title .action-label.toolbar-toggle-pickReactions { - background-image: url("./reaction-hc.svg"); -} - .monaco-editor .review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item a.action-label { border: 1px solid transparent; } @@ -222,10 +176,6 @@ opacity: 0.6; } -.monaco-editor.vs-dark .review-widget .body span.created_at { - color: #e0e0e0; -} - .monaco-editor .review-widget .body .comment-body p, .monaco-editor .review-widget .body .comment-body ul { margin: 8px 0; @@ -412,10 +362,6 @@ margin: 0; } -.monaco-editor .review-widget .head .review-actions .action-label.codicon.close-review-action { - background: url("./close.svg") center center no-repeat; -} - .monaco-editor .review-widget > .body { border-top: 1px solid; position: relative; diff --git a/src/vs/workbench/contrib/customEditor/browser/commands.ts b/src/vs/workbench/contrib/customEditor/browser/commands.ts index a6b3efb63be..94b8ea8294a 100644 --- a/src/vs/workbench/contrib/customEditor/browser/commands.ts +++ b/src/vs/workbench/contrib/customEditor/browser/commands.ts @@ -40,7 +40,7 @@ CommandsRegistry.registerCommand('_workbench.openWith', (accessor: ServicesAcces // #region Reopen With const REOPEN_WITH_COMMAND_ID = 'reOpenWith'; -const REOPEN_WITH_TITLE = { value: nls.localize('reopenWith.title', 'Reopen With...'), original: 'Reopen With' }; +const REOPEN_WITH_TITLE = { value: nls.localize('reopenWith.title', 'Reopen With...'), original: 'Reopen With...' }; KeybindingsRegistry.registerCommandAndKeybindingRule({ id: REOPEN_WITH_COMMAND_ID, @@ -115,7 +115,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { public runCommand(accessor: ServicesAccessor): void { const editorService = accessor.get(IEditorService); - const activeInput = editorService.activeControl?.input; + const activeInput = editorService.activeEditorPane?.input; if (activeInput instanceof CustomEditorInput) { activeInput.undo(); } @@ -142,7 +142,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { public runCommand(accessor: ServicesAccessor): void { const editorService = accessor.get(IEditorService); - const activeInput = editorService.activeControl?.input; + const activeInput = editorService.activeEditorPane?.input; if (activeInput instanceof CustomEditorInput) { activeInput.redo(); } @@ -161,13 +161,13 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { public runCommand(accessor: ServicesAccessor): void { const editorService = accessor.get(IEditorService); - const activeControl = editorService.activeControl; - if (!activeControl) { + const activeEditorPane = editorService.activeEditorPane; + if (!activeEditorPane) { return; } - const activeGroup = activeControl.group; - const activeEditor = activeControl.input; + const activeGroup = activeEditorPane.group; + const activeEditor = activeEditorPane.input; const targetResource = activeEditor.resource; if (!targetResource) { @@ -186,7 +186,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { } } - const newEditorInput = customEditorService.createInput(targetResource, toggleView, activeGroup); + const newEditorInput = customEditorService.createInput(targetResource, toggleView, activeGroup.id); editorService.replaceEditors([{ editor: activeEditor, diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 82ee3c38afa..8ff0b5e10ec 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -5,43 +5,40 @@ import { memoize } from 'vs/base/common/decorators'; import { Lazy } from 'vs/base/common/lazy'; +import { IReference } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; import { basename } from 'vs/base/common/path'; import { isEqual } from 'vs/base/common/resources'; import { assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { generateUuid } from 'vs/base/common/uuid'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IEditorModel, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; +import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { GroupIdentifier, IEditorInput, IRevertOptions, ISaveOptions, Verbosity } from 'vs/workbench/common/editor'; import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; -import { IWebviewService, WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; +import { IWebviewService, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { IWebviewWorkbenchService, LazilyResolvedWebviewEditorInput } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; - -export const enum ModelType { - Custom = 'custom', - Text = 'text', -} export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { - public static typeId = 'workbench.editors.webviewEditor'; private readonly _editorResource: URI; + private readonly _startsDirty: boolean | undefined; + get resource() { return this._editorResource; } - private _model?: { readonly type: ModelType.Custom, readonly model: ICustomEditorModel } | { readonly type: ModelType.Text }; + private _modelRef?: IReference; constructor( resource: URI, viewType: string, id: string, - webview: Lazy, + webview: Lazy, + options: { startsDirty?: boolean }, @IWebviewService webviewService: IWebviewService, @IWebviewWorkbenchService webviewWorkbenchService: IWebviewWorkbenchService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -50,15 +47,13 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { @IFileDialogService private readonly fileDialogService: IFileDialogService, @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, @IEditorService private readonly editorService: IEditorService, - @ITextFileService private readonly textFileService: ITextFileService, - + @IUndoRedoService private readonly undoRedoService: IUndoRedoService, ) { super(id, viewType, '', webview, webviewService, webviewWorkbenchService); this._editorResource = resource; + this._startsDirty = options.startsDirty; } - public modelType?: ModelType; - public getTypeId(): string { return CustomEditorInput.typeId; } @@ -106,24 +101,18 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { } public isReadonly(): boolean { - return false; // TODO + return this._modelRef ? this._modelRef.object.isReadonly() : false; + } + + public isUntitled(): boolean { + return this.resource.scheme === Schemas.untitled; } public isDirty(): boolean { - if (!this._model) { - return false; - } - - switch (this._model.type) { - case ModelType.Text: - return this.textFileService.isDirty(this.resource); - - case ModelType.Custom: - return this._model.model.isDirty(); - - default: - throw new Error('Unknown model type'); + if (!this._modelRef) { + return !!this._startsDirty; } + return this._modelRef.object.isDirty(); } public isSaving(): boolean { @@ -139,157 +128,109 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { } public async save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { - if (!this._model) { - return undefined; + const modelRef = assertIsDefined(this._modelRef); + const target = await modelRef.object.saveCustomEditor(options); + if (!target) { + return undefined; // save cancelled } - switch (this._model.type) { - case ModelType.Text: - { - const result = await this.textFileService.save(this.resource, options); - return result ? this : undefined; - } - case ModelType.Custom: - { - const result = await this._model.model.save(options); - return result ? this : undefined; - } - default: - throw new Error('Unknown model type'); + if (!isEqual(target, this.resource)) { + return this.customEditorService.createInput(target, this.viewType, groupId); } + + return this; } public async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { - if (!this._model) { - return undefined; - } + const modelRef = assertIsDefined(this._modelRef); - let dialogPath = this._editorResource; + const dialogPath = this._editorResource; const target = await this.fileDialogService.pickFileToSave(dialogPath, options?.availableFileSystems); if (!target) { return undefined; // save cancelled } - switch (this._model.type) { - case ModelType.Text: - if (!await this.textFileService.saveAs(this.resource, target, options)) { - return undefined; - } - break; - - case ModelType.Custom: - if (!await this._model.model.saveAs(this._editorResource, target, options)) { - return undefined; - } - break; - - default: - throw new Error('Unknown model type'); + if (!await modelRef.object.saveCustomEditorAs(this._editorResource, target, options)) { + return undefined; } - return this.handleMove(groupId, target) || this.editorService.createInput({ resource: target, forceFile: true }); + return this.move(groupId, target)?.editor; } - public async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { - if (!this._model) { - return false; - } - - switch (this._model.type) { - case ModelType.Text: - return this.textFileService.revert(this.resource, options); - - case ModelType.Custom: - return this._model.model.revert(options); - - default: - throw new Error('Unknown model type'); - } + public async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { + return assertIsDefined(this._modelRef).object.revert(options); } - public async resolve(): Promise { - const editorModel = await super.resolve(); - if (!this._model) { - switch (this.modelType) { - case ModelType.Custom: - const model = await this.customEditorService.models.resolve(this.resource, this.viewType); - this._model = { type: ModelType.Custom, model }; - this._register(model.onDidChangeDirty(() => this._onDidChangeDirty.fire())); + public async resolve(): Promise { + await super.resolve(); - break; + if (this.isDisposed()) { + return null; + } - case ModelType.Text: - this._model = { type: ModelType.Text, }; - this.textFileService.files.onDidChangeDirty(e => { - if (isEqual(this.resource, e.resource)) { - this._onDidChangeDirty.fire(); - } - }); + if (!this._modelRef) { + this._modelRef = this._register(assertIsDefined(await this.customEditorService.models.tryRetain(this.resource, this.viewType))); + this._register(this._modelRef.object.onDidChangeDirty(() => this._onDidChangeDirty.fire())); - break; - - default: - throw new Error('Unknown model type'); + if (this.isDirty()) { + this._onDidChangeDirty.fire(); } } - if (this.isDirty()) { - this._onDidChangeDirty.fire(); - } - - return editorModel; + return null; } - public handleMove(groupId: GroupIdentifier, uri: URI, options?: ITextEditorOptions): IEditorInput | undefined { + move(group: GroupIdentifier, newResource: URI): { editor: IEditorInput } | undefined { + // See if we can keep using the same custom editor provider const editorInfo = this.customEditorService.getCustomEditor(this.viewType); - if (editorInfo?.matches(uri)) { - const webview = assertIsDefined(this.takeOwnershipOfWebview()); - const newInput = this.instantiationService.createInstance(CustomEditorInput, - uri, - this.viewType, - generateUuid(), - new Lazy(() => webview)); - newInput.updateGroup(groupId); - return newInput; + if (editorInfo?.matches(newResource)) { + return { editor: this.doMove(group, newResource) }; } - return undefined; + + return { editor: this.editorService.createEditorInput({ resource: newResource, forceFile: true }) }; + } + + private doMove(group: GroupIdentifier, newResource: URI): IEditorInput { + if (!this._moveHandler) { + return this.customEditorService.createInput(newResource, this.viewType, group); + } + + this._moveHandler(newResource); + const newEditor = this.instantiationService.createInstance(CustomEditorInput, + newResource, + this.viewType, + this.id, + new Lazy(() => undefined!), + { startsDirty: this._startsDirty }); // this webview is replaced in the transfer call + this.transfer(newEditor); + newEditor.updateGroup(group); + return newEditor; } public undo(): void { - if (!this._model) { - return; - } - - switch (this._model.type) { - case ModelType.Custom: - this._model.model.undo(); - return; - - case ModelType.Text: - this.textFileService.files.get(this.resource)?.textEditorModel?.undo(); - return; - - default: - throw new Error('Unknown model type'); - } + assertIsDefined(this._modelRef); + this.undoRedoService.undo(this.resource); } public redo(): void { - if (!this._model) { + assertIsDefined(this._modelRef); + this.undoRedoService.redo(this.resource); + } + + private _moveHandler?: (newResource: URI) => void; + + public onMove(handler: (newResource: URI) => void): void { + // TODO: Move this to the service + this._moveHandler = handler; + } + + protected transfer(other: CustomEditorInput): CustomEditorInput | undefined { + if (!super.transfer(other)) { return; } - switch (this._model.type) { - case ModelType.Custom: - this._model.model.redo(); - return; - - case ModelType.Text: - this.textFileService.files.get(this.resource)?.textEditorModel?.redo(); - return; - - default: - throw new Error('Unknown model type'); - } + other._moveHandler = this._moveHandler; + this._moveHandler = undefined; + return other; } } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts index 558dc3d380d..48e23af0568 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts @@ -3,30 +3,56 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI } from 'vs/base/common/uri'; -import { generateUuid } from 'vs/base/common/uuid'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { CustomEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; -import { WebviewEditorInputFactory } from 'vs/workbench/contrib/webview/browser/webviewEditorInputFactory'; -import { IWebviewWorkbenchService } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; import { Lazy } from 'vs/base/common/lazy'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IEditorInput } from 'vs/workbench/common/editor'; +import { CustomEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; +import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; +import { WebviewEditorInputFactory, SerializedWebview } from 'vs/workbench/contrib/webview/browser/webviewEditorInputFactory'; +import { IWebviewWorkbenchService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; + +export interface CustomDocumentBackupData { + readonly viewType: string; + readonly editorResource: UriComponents; + readonly extension: undefined | { + readonly location: UriComponents; + readonly id: string; + }; + + readonly webview: { + readonly id: string; + readonly options: WebviewInputOptions; + readonly state: any; + }; +} + +interface SerializedCustomEditor extends SerializedWebview { + readonly editorResource: UriComponents; + readonly dirty?: boolean; +} + export class CustomEditorInputFactory extends WebviewEditorInputFactory { public static readonly ID = CustomEditorInput.typeId; public constructor( + @IWebviewWorkbenchService webviewWorkbenchService: IWebviewWorkbenchService, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IWebviewWorkbenchService private readonly webviewWorkbenchService: IWebviewWorkbenchService, + @IWebviewService private readonly _webviewService: IWebviewService, ) { super(webviewWorkbenchService); } public serialize(input: CustomEditorInput): string | undefined { - const data = { + const data: SerializedCustomEditor = { ...this.toJson(input), editorResource: input.resource.toJSON(), - modelType: input.modelType + dirty: input.isDirty(), }; try { @@ -44,20 +70,58 @@ export class CustomEditorInputFactory extends WebviewEditorInputFactory { const id = data.id || generateUuid(); const webview = new Lazy(() => { - const webviewInput = this.webviewWorkbenchService.reviveWebview(id, data.viewType, data.title, data.iconPath, data.state, data.options, data.extensionLocation && data.extensionId ? { - location: data.extensionLocation, - id: data.extensionId - } : undefined, data.group); - return webviewInput.webview; + const webview = this._webviewService.createWebviewOverlay(id, { + enableFindWidget: data.options.enableFindWidget, + retainContextWhenHidden: data.options.retainContextWhenHidden + }, data.options); + + if (data.extensionLocation && data.extensionId) { + webview.extension = { + location: data.extensionLocation, + id: data.extensionId + }; + } + + return webview; }); - const customInput = this._instantiationService.createInstance(CustomEditorInput, URI.from((data as any).editorResource), data.viewType, id, webview); + const customInput = this._instantiationService.createInstance(CustomEditorInput, URI.from((data as any).editorResource), data.viewType, id, webview, { startsDirty: (data as any).dirty }); if (typeof data.group === 'number') { customInput.updateGroup(data.group); } - if ((data as any).modelType) { - customInput.modelType = (data as any).modelType; - } return customInput; } + + public static createCustomEditorInput(resource: URI, instantiationService: IInstantiationService): Promise { + return instantiationService.invokeFunction(async accessor => { + const webviewService = accessor.get(IWebviewService); + const backupFileService = accessor.get(IBackupFileService); + + const backup = await backupFileService.resolve(resource); + if (!backup?.meta) { + throw new Error(`No backup found for custom editor: ${resource}`); + } + + const backupData = backup.meta; + const id = backupData.webview.id; + + const webview = new Lazy(() => { + const webview = webviewService.createWebviewOverlay(id, { + enableFindWidget: backupData.webview.options.enableFindWidget, + retainContextWhenHidden: backupData.webview.options.retainContextWhenHidden + }, backupData.webview.options); + + webview.extension = backupData.extension ? { + location: URI.revive(backupData.extension.location), + id: new ExtensionIdentifier(backupData.extension.id), + } : undefined; + + return webview; + }); + + const editor = instantiationService.createInstance(CustomEditorInput, URI.revive(backupData.editorResource), backupData.viewType, id, webview, { startsDirty: true }); + editor.updateGroup(0); + return editor; + }); + } } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index ca4581ede56..6cf436a76ef 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -13,31 +13,34 @@ import { generateUuid } from 'vs/base/common/uuid'; import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { EditorActivation, IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { FileOperation, IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ILabelService } from 'vs/platform/label/common/label'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { EditorInput, EditorOptions, IEditor, IEditorInput } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, GroupIdentifier, IEditorInput, IEditorPane } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; -import { webviewEditorsExtensionPoint } from 'vs/workbench/contrib/customEditor/browser/extensionPoint'; -import { CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CONTEXT_CUSTOM_EDITORS, CustomEditorInfo, CustomEditorInfoCollection, CustomEditorPriority, CustomEditorSelector, ICustomEditor, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { webviewEditorsExtensionPoint, IWebviewEditorsExtensionPoint } from 'vs/workbench/contrib/customEditor/browser/extensionPoint'; +import { CONTEXT_CUSTOM_EDITORS, CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CustomEditorInfo, CustomEditorInfoCollection, CustomEditorPriority, CustomEditorSelector, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { CustomEditorModelManager } from 'vs/workbench/contrib/customEditor/common/customEditorModelManager'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { IWebviewService, webviewHasOwnEditFunctionsContext } from 'vs/workbench/contrib/webview/browser/webview'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService'; -import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { CustomEditorInput } from './customEditorInput'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; export const defaultEditorId = 'default'; +const builtinProviderDisplayName = nls.localize('builtinProviderDisplayName', "Built-in"); + const defaultEditorInfo = new CustomEditorInfo({ id: defaultEditorId, - displayName: nls.localize('promptOpenWith.defaultEditor', "VS Code's standard text editor"), + displayName: nls.localize('promptOpenWith.defaultEditor.displayName', "Text Editor"), + providerDisplayName: builtinProviderDisplayName, selector: [ { filenamePattern: '*' } ], @@ -59,8 +62,9 @@ export class CustomEditorInfoStore extends Disposable { this.add(new CustomEditorInfo({ id: webviewEditorContribution.viewType, displayName: webviewEditorContribution.displayName, + providerDisplayName: extension.description.isBuiltin ? builtinProviderDisplayName : extension.description.displayName || extension.description.identifier.value, selector: webviewEditorContribution.selector || [], - priority: webviewEditorContribution.priority || CustomEditorPriority.default, + priority: getPriorityFromContribution(webviewEditorContribution, extension.description), })); } } @@ -96,7 +100,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ private readonly _editorInfoStore = this._register(new CustomEditorInfoStore()); - private readonly _models: CustomEditorModelManager; + private readonly _models = new CustomEditorModelManager(); private readonly _customEditorContextKey: IContextKey; private readonly _focusedCustomEditorIsEditable: IContextKey; @@ -104,7 +108,6 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ constructor( @IContextKeyService contextKeyService: IContextKeyService, - @IWorkingCopyService workingCopyService: IWorkingCopyService, @IFileService fileService: IFileService, @IConfigurationService private readonly configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, @@ -112,12 +115,9 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ @IInstantiationService private readonly instantiationService: IInstantiationService, @IQuickInputService private readonly quickInputService: IQuickInputService, @IWebviewService private readonly webviewService: IWebviewService, - @ILabelService labelService: ILabelService ) { super(); - this._models = new CustomEditorModelManager(workingCopyService, labelService); - this._customEditorContextKey = CONTEXT_CUSTOM_EDITORS.bindTo(contextKeyService); this._focusedCustomEditorIsEditable = CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE.bindTo(contextKeyService); this._webviewHasOwnEditFunctions = webviewHasOwnEditFunctionsContext.bindTo(contextKeyService); @@ -136,15 +136,6 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ public get models() { return this._models; } - public get activeCustomEditor(): ICustomEditor | undefined { - const activeInput = this.editorService.activeControl?.input; - if (!(activeInput instanceof CustomEditorInput)) { - return undefined; - } - const resource = activeInput.resource; - return { resource, viewType: activeInput.viewType }; - } - public getCustomEditor(viewType: string): CustomEditorInfo | undefined { return this._editorInfoStore.get(viewType); } @@ -161,15 +152,33 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ .map(association => this._editorInfoStore.get(association.viewType)))); } + public getAllCustomEditors(resource: URI): CustomEditorInfoCollection { + return new CustomEditorInfoCollection([ + ...this.getUserConfiguredCustomEditors(resource).allEditors, + ...this.getContributedCustomEditors(resource).allEditors, + ]); + } + public async promptOpenWith( resource: URI, options?: ITextEditorOptions, group?: IEditorGroup, - ): Promise { + ): Promise { + const pick = await this.showOpenWithPrompt(resource, group); + if (!pick) { + return; + } + + return this.openWith(resource, pick, options, group); + } + + private showOpenWithPrompt( + resource: URI, + group?: IEditorGroup, + ): Promise { const customEditors = new CustomEditorInfoCollection([ defaultEditorInfo, - ...this.getUserConfiguredCustomEditors(resource).allEditors, - ...this.getContributedCustomEditors(resource).allEditors, + ...this.getAllCustomEditors(resource).allEditors, ]); let currentlyOpenedEditorType: undefined | string; @@ -188,6 +197,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ description: editorDescriptor.id === currentlyOpenedEditorType ? nls.localize('openWithCurrentlyActive', "Currently Active") : undefined, + detail: editorDescriptor.providerDisplayName, buttons: resourceExt ? [{ iconClass: 'codicon-settings-gear', tooltip: nls.localize('promptOpenWith.setDefaultTooltip', "Set as default editor for '{0}' files", resourceExt) @@ -198,7 +208,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ picker.items = items; picker.placeholder = nls.localize('promptOpenWith.placeHolder', "Select editor to use for '{0}'...", basename(resource)); - const pick = await new Promise(resolve => { + return new Promise(resolve => { picker.onDidAccept(() => { resolve(picker.selectedItems.length === 1 ? picker.selectedItems[0].id : undefined); picker.dispose(); @@ -230,12 +240,6 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ }); picker.show(); }); - - if (!pick) { - return; - } - - return this.openWith(resource, pick, options, group); } public openWith( @@ -243,37 +247,37 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ viewType: string, options?: ITextEditorOptions, group?: IEditorGroup, - ): Promise { + ): Promise { if (viewType === defaultEditorId) { - const fileInput = this.editorService.createInput({ resource, forceFile: true }); - return this.openEditorForResource(resource, fileInput, { ...options, ignoreOverrides: true }, group); + const fileEditorInput = this.editorService.createEditorInput({ resource, forceFile: true }); + return this.openEditorForResource(resource, fileEditorInput, { ...options, ignoreOverrides: true }, group); } if (!this._editorInfoStore.get(viewType)) { return this.promptOpenWith(resource, options, group); } - const input = this.createInput(resource, viewType, group); + const input = this.createInput(resource, viewType, group?.id); return this.openEditorForResource(resource, input, options, group); } public createInput( resource: URI, viewType: string, - group: IEditorGroup | undefined, + group: GroupIdentifier | undefined, options?: { readonly customClasses: string; }, ): IEditorInput { if (viewType === defaultEditorId) { - return this.editorService.createInput({ resource, forceFile: true }); + return this.editorService.createEditorInput({ resource, forceFile: true }); } const id = generateUuid(); const webview = new Lazy(() => { - return this.webviewService.createWebviewEditorOverlay(id, { customClasses: options?.customClasses }, {}); + return this.webviewService.createWebviewOverlay(id, { customClasses: options?.customClasses }, {}); }); - const input = this.instantiationService.createInstance(CustomEditorInput, resource, viewType, id, webview); - if (group) { - input.updateGroup(group.id); + const input = this.instantiationService.createInstance(CustomEditorInput, resource, viewType, id, webview, {}); + if (typeof group !== 'undefined') { + input.updateGroup(group); } return input; } @@ -283,9 +287,13 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ input: IEditorInput, options?: IEditorOptions, group?: IEditorGroup - ): Promise { + ): Promise { const targetGroup = group || this.editorGroupService.activeGroup; + if (options && typeof options.activation === 'undefined') { + options = { ...options, activation: options.preserveFocus ? EditorActivation.RESTORE : undefined }; + } + // Try to replace existing editors for resource const existingEditors = targetGroup.editors.filter(editor => editor.resource && isEqual(editor.resource, resource)); if (existingEditors.length) { @@ -307,8 +315,8 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ } private updateContexts() { - const activeControl = this.editorService.activeControl; - const resource = activeControl?.input.resource; + const activeEditorPane = this.editorService.activeEditorPane; + const resource = activeEditorPane?.input?.resource; if (!resource) { this._customEditorContextKey.reset(); this._focusedCustomEditorIsEditable.reset(); @@ -316,38 +324,79 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ return; } - const possibleEditors = [ - ...this.getContributedCustomEditors(resource).allEditors, - ...this.getUserConfiguredCustomEditors(resource).allEditors, - ]; + const possibleEditors = this.getAllCustomEditors(resource).allEditors; + this._customEditorContextKey.set(possibleEditors.map(x => x.id).join(',')); - this._focusedCustomEditorIsEditable.set(activeControl?.input instanceof CustomEditorInput); + this._focusedCustomEditorIsEditable.set(activeEditorPane?.input instanceof CustomEditorInput); this._webviewHasOwnEditFunctions.set(possibleEditors.length > 0); } - private handleMovedFileInOpenedFileEditors(oldResource: URI, newResource: URI): void { + private async handleMovedFileInOpenedFileEditors(oldResource: URI, newResource: URI): Promise { + if (extname(oldResource) === extname(newResource)) { + return; + } + + const possibleEditors = this.getAllCustomEditors(newResource); + + // See if we have any non-optional custom editor for this resource + if (!possibleEditors.allEditors.some(editor => editor.priority !== CustomEditorPriority.option)) { + return; + } + + // If so, check all editors to see if there are any file editors open for the new resource + const editorsToReplace = new Map(); for (const group of this.editorGroupService.groups) { for (const editor of group.editors) { - if (!(editor instanceof CustomEditorInput)) { - continue; + if (editor instanceof FileEditorInput + && !(editor instanceof CustomEditorInput) + && isEqual(editor.resource, newResource) + ) { + let entry = editorsToReplace.get(group.id); + if (!entry) { + entry = []; + editorsToReplace.set(group.id, entry); + } + entry.push(editor); } - - const editorInfo = this._editorInfoStore.get(editor.viewType); - if (!editorInfo?.matches(newResource)) { - continue; - } - - const replacement = this.createInput(newResource, editor.viewType, group); - this.editorService.replaceEditors([{ - editor: editor, - replacement: replacement, - }], group); } } + + if (!editorsToReplace.size) { + return; + } + + let viewType: string | undefined; + if (possibleEditors.defaultEditor) { + viewType = possibleEditors.defaultEditor.id; + } else { + // If there is, show a single prompt for all editors to see if the user wants to re-open them + // + // TODO: instead of prompting eagerly, it'd likly be better to replace all the editors with + // ones that would prompt when they first become visible + await new Promise(resolve => setTimeout(resolve, 50)); + viewType = await this.showOpenWithPrompt(newResource); + } + + if (!viewType) { + return; + } + + for (const [group, entries] of editorsToReplace) { + this.editorService.replaceEditors(entries.map(editor => { + const replacement = this.createInput(newResource, viewType!, group); + return { + editor, + replacement, + options: { + preserveFocus: true, + } + }; + }), group); + } } } -export const customEditorsAssociationsKey = 'workbench.experimental.editorAssociations'; +export const customEditorsAssociationsKey = 'workbench.editorAssociations'; export type CustomEditorAssociation = CustomEditorSelector & { readonly viewType: string; @@ -426,7 +475,11 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo const existingEditorForResource = group.editors.find(editor => isEqual(resource, editor.resource)); if (existingEditorForResource) { return { - override: this.editorService.openEditor(existingEditorForResource, { ...options, ignoreOverrides: true }, group) + override: this.editorService.openEditor(existingEditorForResource, { + ...options, + ignoreOverrides: true, + activation: options?.preserveFocus ? EditorActivation.RESTORE : undefined, + }, group) }; } @@ -498,7 +551,7 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo return undefined; } - const input = this.customEditorService.createInput(resource, bestAvailableEditor.id, group, { customClasses }); + const input = this.customEditorService.createInput(resource, bestAvailableEditor.id, group.id, { customClasses }); if (input instanceof EditorInput) { return input; } @@ -522,9 +575,29 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo } } +function getPriorityFromContribution( + contribution: IWebviewEditorsExtensionPoint, + extension: IExtensionDescription, +): CustomEditorPriority { + switch (contribution.priority) { + case CustomEditorPriority.default: + case CustomEditorPriority.option: + return contribution.priority; + + case CustomEditorPriority.builtin: + // Builtin is only valid for builtin extensions + return extension.isBuiltin ? CustomEditorPriority.builtin : CustomEditorPriority.default; + + default: + return CustomEditorPriority.default; + } +} + registerThemingParticipant((theme, collector) => { const shadow = theme.getColor(colorRegistry.scrollbarShadow); if (shadow) { collector.addRule(`.webview.modified { box-shadow: -6px 0 5px -5px ${shadow}; }`); } }); + + diff --git a/src/vs/workbench/contrib/customEditor/browser/extensionPoint.ts b/src/vs/workbench/contrib/customEditor/browser/extensionPoint.ts index 7c7a65b2d63..6ba0b39e4bc 100644 --- a/src/vs/workbench/contrib/customEditor/browser/extensionPoint.ts +++ b/src/vs/workbench/contrib/customEditor/browser/extensionPoint.ts @@ -16,17 +16,25 @@ namespace WebviewEditorContribution { export const priority = 'priority'; } -interface IWebviewEditorsExtensionPoint { +export interface IWebviewEditorsExtensionPoint { readonly [WebviewEditorContribution.viewType]: string; readonly [WebviewEditorContribution.displayName]: string; readonly [WebviewEditorContribution.selector]?: readonly CustomEditorSelector[]; - readonly [WebviewEditorContribution.priority]?: CustomEditorPriority; + readonly [WebviewEditorContribution.priority]?: string; } const webviewEditorsContribution: IJSONSchema = { description: nls.localize('contributes.customEditors', 'Contributed custom editors.'), type: 'array', - defaultSnippets: [{ body: [{ viewType: '', displayName: '' }] }], + defaultSnippets: [{ + body: [{ + [WebviewEditorContribution.viewType]: '$1', + [WebviewEditorContribution.displayName]: '$2', + [WebviewEditorContribution.selector]: [{ + filenamePattern: '$3' + }], + }] + }], items: { type: 'object', required: [ @@ -48,6 +56,11 @@ const webviewEditorsContribution: IJSONSchema = { description: nls.localize('contributes.selector', 'Set of globs that the custom editor is enabled for.'), items: { type: 'object', + defaultSnippets: [{ + body: { + filenamePattern: '$1', + } + }], properties: { filenamePattern: { type: 'string', @@ -62,12 +75,10 @@ const webviewEditorsContribution: IJSONSchema = { enum: [ CustomEditorPriority.default, CustomEditorPriority.option, - CustomEditorPriority.builtin, ], - enumDescriptions: [ + markdownEnumDescriptions: [ nls.localize('contributes.priority.default', 'Editor is automatically used for a resource if no other default custom editors are registered for it.'), nls.localize('contributes.priority.option', 'Editor is not automatically used but can be selected by a user.'), - nls.localize('contributes.priority.builtin', 'Editor automatically used if no other `default` or `builtin` editors are registered for the resource.'), ], default: 'default' } diff --git a/src/vs/workbench/contrib/customEditor/browser/webviewEditor.contribution.ts b/src/vs/workbench/contrib/customEditor/browser/webviewEditor.contribution.ts index 00f28e3ecd9..ac0b0da7543 100644 --- a/src/vs/workbench/contrib/customEditor/browser/webviewEditor.contribution.ts +++ b/src/vs/workbench/contrib/customEditor/browser/webviewEditor.contribution.ts @@ -38,27 +38,31 @@ Registry.as(EditorInputExtensions.EditorInputFactor CustomEditorInputFactory.ID, CustomEditorInputFactory); +Registry.as(EditorInputExtensions.EditorInputFactories).registerCustomEditorInputFactory(CustomEditorInputFactory); + Registry.as(ConfigurationExtensions.Configuration) .registerConfiguration({ ...workbenchConfigurationNodeBase, 'properties': { [customEditorsAssociationsKey]: { type: 'array', - markdownDescription: nls.localize('editor.editorAssociations', "Configure which editor to use for a resource."), + markdownDescription: nls.localize('editor.editorAssociations', "Configure which editor to use for specific file types."), items: { type: 'object', + defaultSnippets: [{ + body: { + 'viewType': '$1', + 'filenamePattern': '$2' + } + }], properties: { 'viewType': { type: 'string', - description: nls.localize('editor.editorAssociations.viewType', "Editor view type."), - }, - 'mime': { - type: 'string', - description: nls.localize('editor.editorAssociations.mime', "Mime type the editor should be used for. This is used for binary files."), + description: nls.localize('editor.editorAssociations.viewType', "The unique id of the editor to use."), }, 'filenamePattern': { type: 'string', - description: nls.localize('editor.editorAssociations.filenamePattern', "Glob pattern the editor should be used for."), + description: nls.localize('editor.editorAssociations.filenamePattern', "Glob pattern specifying which files the editor should be used for."), } } } diff --git a/src/vs/workbench/contrib/customEditor/common/customEditor.ts b/src/vs/workbench/contrib/customEditor/common/customEditor.ts index f14ffcf0a81..af547205a4f 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditor.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditor.ts @@ -3,86 +3,63 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { distinct, find, mergeSort } from 'vs/base/common/arrays'; -import { CancelablePromise } from 'vs/base/common/async'; +import { distinct, mergeSort } from 'vs/base/common/arrays'; import { Event } from 'vs/base/common/event'; import * as glob from 'vs/base/common/glob'; +import { IDisposable, IReference } from 'vs/base/common/lifecycle'; +import { posix } from 'vs/base/common/path'; import { basename } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IEditor, IRevertOptions, ISaveOptions, IEditorInput } from 'vs/workbench/common/editor'; +import { GroupIdentifier, IEditorInput, IEditorPane, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; export const ICustomEditorService = createDecorator('customEditorService'); export const CONTEXT_CUSTOM_EDITORS = new RawContextKey('customEditors', ''); export const CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE = new RawContextKey('focusedCustomEditorIsEditable', false); -export interface ICustomEditor { - readonly resource: URI; - readonly viewType: string; -} - export interface ICustomEditorService { _serviceBrand: any; readonly models: ICustomEditorModelManager; - readonly activeCustomEditor: ICustomEditor | undefined; - getCustomEditor(viewType: string): CustomEditorInfo | undefined; + getAllCustomEditors(resource: URI): CustomEditorInfoCollection; getContributedCustomEditors(resource: URI): CustomEditorInfoCollection; getUserConfiguredCustomEditors(resource: URI): CustomEditorInfoCollection; - createInput(resource: URI, viewType: string, group: IEditorGroup | undefined, options?: { readonly customClasses: string }): IEditorInput; + createInput(resource: URI, viewType: string, group: GroupIdentifier | undefined, options?: { readonly customClasses: string }): IEditorInput; - openWith(resource: URI, customEditorViewType: string, options?: ITextEditorOptions, group?: IEditorGroup): Promise; - promptOpenWith(resource: URI, options?: ITextEditorOptions, group?: IEditorGroup): Promise; + openWith(resource: URI, customEditorViewType: string, options?: ITextEditorOptions, group?: IEditorGroup): Promise; + promptOpenWith(resource: URI, options?: ITextEditorOptions, group?: IEditorGroup): Promise; } export interface ICustomEditorModelManager { - get(resource: URI, viewType: string): ICustomEditorModel | undefined; + get(resource: URI, viewType: string): Promise; - resolve(resource: URI, viewType: string): Promise; + tryRetain(resource: URI, viewType: string): Promise> | undefined; - disposeModel(model: ICustomEditorModel): void; + add(resource: URI, viewType: string, model: Promise): Promise>; disposeAllModelsForView(viewType: string): void; } -export interface CustomEditorSaveEvent { - readonly resource: URI; - readonly waitUntil: (until: Promise) => void; -} - -export interface CustomEditorSaveAsEvent { - readonly resource: URI; - readonly targetResource: URI; - readonly waitUntil: (until: Promise) => void; -} - -export interface ICustomEditorModel extends IWorkingCopy { +export interface ICustomEditorModel extends IDisposable { readonly viewType: string; + readonly resource: URI; - readonly onUndo: Event; - readonly onRedo: Event; - readonly onRevert: Event; + isReadonly(): boolean; - readonly onWillSave: Event; - readonly onWillSaveAs: Event; + isDirty(): boolean; + readonly onDidChangeDirty: Event; - onBackup(f: () => CancelablePromise): void; + revert(options?: IRevertOptions): Promise; - setDirty(dirty: boolean): void; - undo(): void; - redo(): void; - revert(options?: IRevertOptions): Promise; - - save(options?: ISaveOptions): Promise; - saveAs(resource: URI, targetResource: URI, currentOptions?: ISaveOptions): Promise; + saveCustomEditor(options?: ISaveOptions): Promise; + saveCustomEditorAs(resource: URI, targetResource: URI, currentOptions?: ISaveOptions): Promise; } export const enum CustomEditorPriority { @@ -99,17 +76,20 @@ export class CustomEditorInfo { public readonly id: string; public readonly displayName: string; + public readonly providerDisplayName: string | undefined; public readonly priority: CustomEditorPriority; public readonly selector: readonly CustomEditorSelector[]; constructor(descriptor: { readonly id: string; readonly displayName: string; + readonly providerDisplayName: string | undefined; readonly priority: CustomEditorPriority; readonly selector: readonly CustomEditorSelector[]; }) { this.id = descriptor.id; this.displayName = descriptor.displayName; + this.providerDisplayName = descriptor.providerDisplayName; this.priority = descriptor.priority; this.selector = descriptor.selector; } @@ -120,7 +100,9 @@ export class CustomEditorInfo { static selectorMatches(selector: CustomEditorSelector, resource: URI): boolean { if (selector.filenamePattern) { - if (glob.match(selector.filenamePattern.toLowerCase(), basename(resource).toLowerCase())) { + const matchOnPath = selector.filenamePattern.indexOf(posix.sep) >= 0; + const target = matchOnPath ? resource.path : basename(resource); + if (glob.match(selector.filenamePattern.toLowerCase(), target.toLowerCase())) { return true; } } @@ -145,7 +127,7 @@ export class CustomEditorInfoCollection { * other contributed editors. */ public get defaultEditor(): CustomEditorInfo | undefined { - return find(this.allEditors, editor => { + return this.allEditors.find(editor => { switch (editor.priority) { case CustomEditorPriority.default: case CustomEditorPriority.builtin: diff --git a/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts b/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts deleted file mode 100644 index 43f887a82a0..00000000000 --- a/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts +++ /dev/null @@ -1,206 +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 { CancelablePromise } from 'vs/base/common/async'; -import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; -import { IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; -import { CustomEditorSaveAsEvent, CustomEditorSaveEvent, ICustomEditorModel } from 'vs/workbench/contrib/customEditor/common/customEditor'; -import { IWorkingCopyBackup, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { ILabelService } from 'vs/platform/label/common/label'; -import { basename } from 'vs/base/common/path'; - -namespace HotExitState { - export const enum Type { - NotSupported, - Allowed, - NotAllowed, - Pending, - } - - export const NotSupported = Object.freeze({ type: Type.NotSupported } as const); - export const Allowed = Object.freeze({ type: Type.Allowed } as const); - export const NotAllowed = Object.freeze({ type: Type.NotAllowed } as const); - - export class Pending { - readonly type = Type.Pending; - - constructor( - public readonly operation: CancelablePromise, - ) { } - } - - export type State = typeof NotSupported | typeof Allowed | typeof NotAllowed | Pending; -} - -export class CustomEditorModel extends Disposable implements ICustomEditorModel { - - private _hotExitState: HotExitState.State = HotExitState.NotSupported; - private _dirty = false; - - constructor( - public readonly viewType: string, - private readonly _resource: URI, - private readonly labelService: ILabelService, - ) { - super(); - } - - //#region IWorkingCopy - - public get resource() { - return this._resource; - } - - public get name() { - return basename(this.labelService.getUriLabel(this._resource)); - } - - public get capabilities(): WorkingCopyCapabilities { - return 0; - } - - public isDirty(): boolean { - return this._dirty; - } - - private readonly _onDidChangeDirty: Emitter = this._register(new Emitter()); - readonly onDidChangeDirty: Event = this._onDidChangeDirty.event; - - private readonly _onDidChangeContent: Emitter = this._register(new Emitter()); - readonly onDidChangeContent: Event = this._onDidChangeContent.event; - - //#endregion - - private readonly _onUndo = this._register(new Emitter()); - public readonly onUndo = this._onUndo.event; - - private readonly _onRedo = this._register(new Emitter()); - public readonly onRedo = this._onRedo.event; - - private readonly _onRevert = this._register(new Emitter()); - public readonly onRevert = this._onRevert.event; - - private readonly _onWillSave = this._register(new Emitter()); - public readonly onWillSave = this._onWillSave.event; - - private readonly _onWillSaveAs = this._register(new Emitter()); - public readonly onWillSaveAs = this._onWillSaveAs.event; - - private _onBackup: undefined | (() => CancelablePromise); - - public onBackup(f: () => CancelablePromise) { - if (this._onBackup) { - throw new Error('Backup already implemented'); - } - this._onBackup = f; - - if (this._hotExitState === HotExitState.NotSupported) { - this._hotExitState = this.isDirty() ? HotExitState.NotAllowed : HotExitState.Allowed; - } - } - - public setDirty(dirty: boolean): void { - this._onDidChangeContent.fire(); - - if (this._dirty !== dirty) { - this._dirty = dirty; - this._onDidChangeDirty.fire(); - } - } - - public async revert(_options?: IRevertOptions) { - if (!this._dirty) { - return true; - } - - this._onRevert.fire(); - return true; - } - - public undo() { - this._onUndo.fire(); - } - - public redo() { - this._onRedo.fire(); - } - - public async save(_options?: ISaveOptions): Promise { - const untils: Promise[] = []; - const handler: CustomEditorSaveEvent = { - resource: this._resource, - waitUntil: (until: Promise) => untils.push(until) - }; - - try { - this._onWillSave.fire(handler); - await Promise.all(untils); - } catch { - return false; - } - - this.setDirty(false); - - return true; - } - - public async saveAs(resource: URI, targetResource: URI, _options?: ISaveOptions): Promise { - const untils: Promise[] = []; - const handler: CustomEditorSaveAsEvent = { - resource, - targetResource, - waitUntil: (until: Promise) => untils.push(until) - }; - - try { - this._onWillSaveAs.fire(handler); - await Promise.all(untils); - } catch { - return false; - } - - this.setDirty(false); - - return true; - } - - public async backup(): Promise { - if (this._hotExitState === HotExitState.NotSupported) { - throw new Error('Not supported'); - } - - if (this._hotExitState.type === HotExitState.Type.Pending) { - this._hotExitState.operation.cancel(); - } - this._hotExitState = HotExitState.NotAllowed; - - const pendingState = new HotExitState.Pending(this._onBackup!()); - this._hotExitState = pendingState; - - try { - await pendingState.operation; - // Make sure state has not changed in the meantime - if (this._hotExitState === pendingState) { - this._hotExitState = HotExitState.Allowed; - } - } catch (e) { - // Make sure state has not changed in the meantime - if (this._hotExitState === pendingState) { - this._hotExitState = HotExitState.NotAllowed; - } - } - - if (this._hotExitState === HotExitState.Allowed) { - return { - meta: { - viewType: this.viewType, - } - }; - } - throw new Error('Cannot back up in this state'); - } -} diff --git a/src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts b/src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts index e6cbfb4be4d..b2b185ed874 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts @@ -3,60 +3,66 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IReference } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ICustomEditorModel, ICustomEditorModelManager } from 'vs/workbench/contrib/customEditor/common/customEditor'; -import { CustomEditorModel } from 'vs/workbench/contrib/customEditor/common/customEditorModel'; -import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { ILabelService } from 'vs/platform/label/common/label'; +import { once } from 'vs/base/common/functional'; export class CustomEditorModelManager implements ICustomEditorModelManager { - private readonly _models = new Map(); - constructor( - @IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService, - @ILabelService private readonly _labelService: ILabelService - ) { } + private readonly _references = new Map, + counter: number + }>(); - - public get(resource: URI, viewType: string): ICustomEditorModel | undefined { - return this._models.get(this.key(resource, viewType))?.model; + public async get(resource: URI, viewType: string): Promise { + const key = this.key(resource, viewType); + const entry = this._references.get(key); + return entry?.model; } - public async resolve(resource: URI, viewType: string): Promise { - const existing = this.get(resource, viewType); - if (existing) { - return existing; + public tryRetain(resource: URI, viewType: string): Promise> | undefined { + const key = this.key(resource, viewType); + + const entry = this._references.get(key); + if (!entry) { + return undefined; } - const model = new CustomEditorModel(viewType, resource, this._labelService); - const disposables = new DisposableStore(); - disposables.add(this._workingCopyService.registerWorkingCopy(model)); - this._models.set(this.key(resource, viewType), { model, disposables }); - return model; - } + entry.counter++; - public disposeModel(model: ICustomEditorModel): void { - let foundKey: string | undefined; - this._models.forEach((value, key) => { - if (model === value.model) { - value.disposables.dispose(); - value.model.dispose(); - foundKey = key; - } + return entry.model.then(model => { + return { + object: model, + dispose: once(() => { + if (--entry!.counter <= 0) { + entry.model.then(x => x.dispose()); + this._references.delete(key); + } + }), + }; }); - if (typeof foundKey === 'string') { - this._models.delete(foundKey); + } + + public add(resource: URI, viewType: string, model: Promise): Promise> { + const key = this.key(resource, viewType); + const existing = this._references.get(key); + if (existing) { + throw new Error('Model already exists'); } - return; + + this._references.set(key, { viewType, model, counter: 0 }); + return this.tryRetain(resource, viewType)!; } public disposeAllModelsForView(viewType: string): void { - this._models.forEach((value) => { - if (value.model.viewType === viewType) { - this.disposeModel(value.model); + for (const [key, value] of this._references) { + if (value.viewType === viewType) { + value.model.then(x => x.dispose()); + this._references.delete(key); } - }); + } } private key(resource: URI, viewType: string): string { diff --git a/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts b/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts new file mode 100644 index 00000000000..76e5d082964 --- /dev/null +++ b/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts @@ -0,0 +1,78 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, IReference } from 'vs/base/common/lifecycle'; +import { isEqual } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; +import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; +import { IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; +import { ICustomEditorModel } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; + +export class CustomTextEditorModel extends Disposable implements ICustomEditorModel { + + public static async create( + instantiationService: IInstantiationService, + viewType: string, + resource: URI + ): Promise { + return instantiationService.invokeFunction(async accessor => { + const textModelResolverService = accessor.get(ITextModelService); + const textFileService = accessor.get(ITextFileService); + const model = await textModelResolverService.createModelReference(resource); + return new CustomTextEditorModel(viewType, resource, model, textFileService); + }); + } + + private constructor( + public readonly viewType: string, + private readonly _resource: URI, + private readonly _model: IReference, + @ITextFileService private readonly textFileService: ITextFileService, + ) { + super(); + + this._register(_model); + + this._register(this.textFileService.files.onDidChangeDirty(e => { + if (isEqual(this.resource, e.resource)) { + this._onDidChangeDirty.fire(); + this._onDidChangeContent.fire(); + } + })); + } + + public get resource() { + return this._resource; + } + + public isReadonly(): boolean { + return this._model.object.isReadonly(); + } + + public isDirty(): boolean { + return this.textFileService.isDirty(this.resource); + } + + private readonly _onDidChangeDirty: Emitter = this._register(new Emitter()); + readonly onDidChangeDirty: Event = this._onDidChangeDirty.event; + + private readonly _onDidChangeContent: Emitter = this._register(new Emitter()); + readonly onDidChangeContent: Event = this._onDidChangeContent.event; + + public async revert(options?: IRevertOptions) { + return this.textFileService.revert(this.resource, options); + } + + public saveCustomEditor(options?: ISaveOptions): Promise { + return this.textFileService.save(this.resource, options); + } + + public async saveCustomEditorAs(resource: URI, targetResource: URI, options?: ISaveOptions): Promise { + return !!await this.textFileService.saveAs(resource, targetResource, options); + } +} diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index 2100b5a8228..745457c418f 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -15,7 +15,7 @@ import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IDebugService, IBreakpoint, BreakpointWidgetContext as Context, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, DEBUG_SCHEME, CONTEXT_IN_BREAKPOINT_WIDGET, IBreakpointUpdateData, IBreakpointEditorContribution, BREAKPOINT_EDITOR_CONTRIBUTION_ID } from 'vs/workbench/contrib/debug/common/debug'; import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions'; @@ -56,7 +56,7 @@ function isCurlyBracketOpen(input: IActiveCodeEditor): boolean { return false; } -function createDecorations(theme: ITheme, placeHolder: string): IDecorationOptions[] { +function createDecorations(theme: IColorTheme, placeHolder: string): IDecorationOptions[] { const transparentForeground = transparent(editorForeground, 0.4)(theme); return [{ range: { @@ -225,11 +225,11 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi this.toDispose.push(model); const setDecorations = () => { const value = this.input.getModel().getValue(); - const decorations = !!value ? [] : createDecorations(this.themeService.getTheme(), this.placeholder); + const decorations = !!value ? [] : createDecorations(this.themeService.getColorTheme(), this.placeholder); this.input.setDecorations(DECORATION_KEY, decorations); }; this.input.getModel().onDidChangeContent(() => setDecorations()); - this.themeService.onThemeChange(() => setDecorations()); + this.themeService.onDidColorThemeChange(() => setDecorations()); this.toDispose.push(CompletionProviderRegistry.register({ scheme: DEBUG_SCHEME, hasAccessToAllModels: true }, { provideCompletionItems: (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken): Promise => { diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index da287aae45d..1252201533e 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -18,7 +18,7 @@ import { Constants } from 'vs/base/common/uint'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IListVirtualDelegate, IListContextMenuEvent, IListRenderer } from 'vs/base/browser/ui/list/list'; -import { IEditor } from 'vs/workbench/common/editor'; +import { IEditorPane } from 'vs/workbench/common/editor'; import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -28,7 +28,7 @@ import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { ILabelService } from 'vs/platform/label/common/label'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Gesture } from 'vs/base/browser/touch'; @@ -36,6 +36,7 @@ import { IViewDescriptorService } from 'vs/workbench/common/views'; import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { Orientation } from 'vs/base/browser/ui/splitview/splitview'; const $ = dom.$; @@ -74,9 +75,9 @@ export class BreakpointsView extends ViewPane { @IOpenerService openerService: IOpenerService, @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('breakpointsSection', "Breakpoints Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); - this.minimumBodySize = this.maximumBodySize = getExpandedBodySize(this.debugService.getModel()); + this.updateSize(); this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange())); } @@ -227,12 +228,15 @@ export class BreakpointsView extends ViewPane { ]; } + private updateSize(): void { + // Adjust expanded body size + this.minimumBodySize = this.orientation === Orientation.VERTICAL ? getExpandedBodySize(this.debugService.getModel()) : 170; + this.maximumBodySize = this.orientation === Orientation.VERTICAL ? this.minimumBodySize : Number.POSITIVE_INFINITY; + } + private onBreakpointsChange(): void { if (this.isBodyVisible()) { - this.minimumBodySize = getExpandedBodySize(this.debugService.getModel()); - if (this.maximumBodySize < Number.POSITIVE_INFINITY) { - this.maximumBodySize = this.minimumBodySize; - } + this.updateSize(); if (this.list) { this.list.splice(0, this.list.length, this.elements); this.needsRefresh = false; @@ -617,7 +621,7 @@ class FunctionBreakpointInputRenderer implements IListRenderer { +export function openBreakpointSource(breakpoint: IBreakpoint, sideBySide: boolean, preserveFocus: boolean, debugService: IDebugService, editorService: IEditorService): Promise { if (breakpoint.uri.scheme === DEBUG_SCHEME && debugService.state === State.Inactive) { return Promise.resolve(undefined); } diff --git a/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts index fad30d12449..ce7a000439b 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts @@ -10,6 +10,7 @@ import { IDebugService, IStackFrame } from 'vs/workbench/contrib/debug/common/de import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; import { localize } from 'vs/nls'; +import { Event } from 'vs/base/common/event'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -91,7 +92,7 @@ export class CallStackEditorContribution implements IEditorContribution { @IDebugService private readonly debugService: IDebugService, ) { const setDecorations = () => this.decorationIds = this.editor.deltaDecorations(this.decorationIds, this.createCallStackDecorations()); - this.toDispose.push(this.debugService.getViewModel().onDidFocusStackFrame(() => { + this.toDispose.push(Event.any(this.debugService.getViewModel().onDidFocusStackFrame, this.debugService.getModel().onDidChangeCallStack)(() => { setDecorations(); })); this.toDispose.push(this.editor.onDidChangeModel(e => { diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 377041b25f9..7fec39ab47b 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -18,26 +18,28 @@ import { IAction, Action } from 'vs/base/common/actions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { ILabelService } from 'vs/platform/label/common/label'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; -import { TreeResourceNavigator, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; +import { ResourceNavigator, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; import { Event } from 'vs/base/common/event'; -import { dispose } from 'vs/base/common/lifecycle'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { isSessionAttach } from 'vs/workbench/contrib/debug/common/debugUtils'; import { STOP_ID, STOP_LABEL, DISCONNECT_ID, DISCONNECT_LABEL, RESTART_SESSION_ID, RESTART_LABEL, STEP_OVER_ID, STEP_OVER_LABEL, STEP_INTO_LABEL, STEP_INTO_ID, STEP_OUT_LABEL, STEP_OUT_ID, PAUSE_ID, PAUSE_LABEL, CONTINUE_ID, CONTINUE_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { CollapseAction } from 'vs/workbench/browser/viewlet'; import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { attachStylerCallback } from 'vs/platform/theme/common/styler'; const $ = dom.$; @@ -104,7 +106,7 @@ export class CallStackView extends ViewPane { @IThemeService themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('callstackSection', "Call Stack Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.callStackItemType = CONTEXT_CALLSTACK_ITEM_TYPE.bindTo(contextKeyService); this.contributedContextMenu = menuService.createMenu(MenuId.DebugCallStackContext, contextKeyService); @@ -131,7 +133,11 @@ export class CallStackView extends ViewPane { this.needsRefresh = false; this.dataSource.deemphasizedStackFramesToShow = []; this.tree.updateChildren().then(() => { - this.parentSessionToExpand.forEach(s => this.tree.expand(s)); + try { + this.parentSessionToExpand.forEach(s => this.tree.expand(s)); + } catch (e) { + // Ignore tree expand errors if element no longer present + } this.parentSessionToExpand.clear(); if (this.selectionNeedsUpdate) { this.selectionNeedsUpdate = false; @@ -170,8 +176,8 @@ export class CallStackView extends ViewPane { new ThreadsRenderer(this.instantiationService), this.instantiationService.createInstance(StackFramesRenderer), new ErrorsRenderer(), - new LoadMoreRenderer(), - new ShowMoreRenderer() + new LoadMoreRenderer(this.themeService), + new ShowMoreRenderer(this.themeService) ], this.dataSource, { accessibilityProvider: new CallStackAccessibilityProvider(), ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'callStackAriaLabel' }, "Debug Call Stack"), @@ -213,7 +219,7 @@ export class CallStackView extends ViewPane { this.tree.setInput(this.debugService.getModel()); - const callstackNavigator = new TreeResourceNavigator(this.tree); + const callstackNavigator = ResourceNavigator.createTreeResourceNavigator(this.tree); this._register(callstackNavigator); this._register(callstackNavigator.onDidOpenResource(e => { if (this.ignoreSelectionChangedEvent) { @@ -408,6 +414,7 @@ interface IErrorTemplateData { interface ILabelTemplateData { label: HTMLElement; + toDispose: IDisposable; } interface IStackFrameTemplateData { @@ -433,6 +440,7 @@ class SessionsRenderer implements ITreeRenderer { + if (colors.textLinkForeground) { + label.style.color = colors.textLinkForeground.toString(); + } + }); - return { label }; + return { label, toDispose }; } renderElement(element: ITreeNode, index: number, data: ILabelTemplateData): void { @@ -609,21 +624,29 @@ class LoadMoreRenderer implements ITreeRenderer { static readonly ID = 'showMore'; + constructor(private readonly themeService: IThemeService) { } + + get templateId(): string { return ShowMoreRenderer.ID; } - renderTemplate(container: HTMLElement): IErrorTemplateData { + renderTemplate(container: HTMLElement): ILabelTemplateData { const label = dom.append(container, $('.show-more')); + const toDispose = attachStylerCallback(this.themeService, { textLinkForeground }, colors => { + if (colors.textLinkForeground) { + label.style.color = colors.textLinkForeground.toString(); + } + }); - return { label }; + return { label, toDispose }; } renderElement(element: ITreeNode, index: number, data: ILabelTemplateData): void { @@ -636,13 +659,20 @@ class ShowMoreRenderer implements ITreeRenderer { getHeight(element: CallStackItem): number { + if (element instanceof StackFrame && element.presentationHint === 'label') { + return 16; + } + if (element instanceof ThreadAndSessionIds || element instanceof Array) { + return 16; + } + return 22; } diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 06f84bae24b..1f84f7d57ed 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -27,13 +27,11 @@ import { DebugToolBar } from 'vs/workbench/contrib/debug/browser/debugToolBar'; import * as service from 'vs/workbench/contrib/debug/browser/debugService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { registerCommands, ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID, COPY_STACK_TRACE_ID, REVERSE_CONTINUE_ID, STEP_BACK_ID, RESTART_SESSION_ID, TERMINATE_THREAD_ID, STEP_OVER_ID, STEP_INTO_ID, STEP_OUT_ID, PAUSE_ID, DISCONNECT_ID, STOP_ID, RESTART_FRAME_ID, CONTINUE_ID, FOCUS_REPL_ID, JUMP_TO_CURSOR_ID, RESTART_LABEL, STEP_INTO_LABEL, STEP_OVER_LABEL, STEP_OUT_LABEL, PAUSE_LABEL, DISCONNECT_LABEL, STOP_LABEL, CONTINUE_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands'; -import { IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { StatusBarColorProvider } from 'vs/workbench/contrib/debug/browser/statusbarColorProvider'; import { IViewsRegistry, Extensions as ViewExtensions, IViewContainersRegistry, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views'; import { isMacintosh } from 'vs/base/common/platform'; import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { URI } from 'vs/base/common/uri'; -import { DebugQuickOpenHandler } from 'vs/workbench/contrib/debug/browser/debugQuickOpen'; import { DebugStatusContribution } from 'vs/workbench/contrib/debug/browser/debugStatus'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration'; @@ -44,18 +42,21 @@ import { WatchExpressionsView } from 'vs/workbench/contrib/debug/browser/watchEx import { VariablesView } from 'vs/workbench/contrib/debug/browser/variablesView'; import { ClearReplAction, Repl } from 'vs/workbench/contrib/debug/browser/repl'; import { DebugContentProvider } from 'vs/workbench/contrib/debug/common/debugContentProvider'; -import { StartView } from 'vs/workbench/contrib/debug/browser/startView'; +import { WelcomeView } from 'vs/workbench/contrib/debug/browser/welcomeView'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { DebugViewPaneContainer, OpenDebugPanelAction } from 'vs/workbench/contrib/debug/browser/debugViewlet'; +import { DebugViewPaneContainer, OpenDebugConsoleAction } from 'vs/workbench/contrib/debug/browser/debugViewlet'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { CallStackEditorContribution } from 'vs/workbench/contrib/debug/browser/callStackEditorContribution'; import { BreakpointEditorContribution } from 'vs/workbench/contrib/debug/browser/breakpointEditorContribution'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; +import { StartDebugQuickAccessProvider } from 'vs/workbench/contrib/debug/browser/debugQuickAccess'; +import { DebugProgressContribution } from 'vs/workbench/contrib/debug/browser/debugProgress'; class OpenDebugViewletAction extends ShowViewletAction { public static readonly ID = VIEWLET_ID; - public static readonly LABEL = nls.localize('toggleDebugViewlet', "Show Debug"); + public static readonly LABEL = nls.localize('toggleDebugViewlet', "Show Run and Debug"); constructor( id: string, @@ -73,7 +74,7 @@ const viewContainer = Registry.as(ViewExtensions.ViewCo name: nls.localize('run', "Run"), ctorDescriptor: new SyncDescriptor(DebugViewPaneContainer), icon: 'codicon-debug-alt-2', - order: 3 + order: 2 }, ViewContainerLocation.Sidebar); const openViewletKb: IKeybindings = { @@ -90,15 +91,19 @@ const VIEW_CONTAINER: ViewContainer = Registry.as(ViewE name: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugPanel' }, 'Debug Console'), ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [DEBUG_PANEL_ID, DEBUG_PANEL_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), focusCommand: { - id: OpenDebugPanelAction.ID, + id: OpenDebugConsoleAction.ID, keybindings: openPanelKb - } + }, + order: 3, + hideIfEmpty: true }, ViewContainerLocation.Panel); Registry.as(ViewExtensions.ViewsRegistry).registerViews([{ id: REPL_VIEW_ID, name: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugPanel' }, 'Debug Console'), + containerIcon: 'codicon-debug-console', canToggleVisibility: false, + canMoveView: true, ctorDescriptor: new SyncDescriptor(Repl), }], VIEW_CONTAINER); @@ -108,27 +113,28 @@ viewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variab viewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), ctorDescriptor: new SyncDescriptor(WatchExpressionsView), order: 20, weight: 10, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusWatchView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); viewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), ctorDescriptor: new SyncDescriptor(CallStackView), order: 30, weight: 30, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusCallStackView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); viewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), ctorDescriptor: new SyncDescriptor(BreakpointsView), order: 40, weight: 20, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusBreakpointsView' }, when: ContextKeyExpr.or(CONTEXT_BREAKPOINTS_EXIST, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer); -viewsRegistry.registerViews([{ id: StartView.ID, name: StartView.LABEL, ctorDescriptor: new SyncDescriptor(StartView), order: 10, weight: 40, canToggleVisibility: true, when: CONTEXT_DEBUG_UX.isEqualTo('simple') }], viewContainer); +viewsRegistry.registerViews([{ id: WelcomeView.ID, name: WelcomeView.LABEL, ctorDescriptor: new SyncDescriptor(WelcomeView), order: 10, weight: 40, canToggleVisibility: true, when: CONTEXT_DEBUG_UX.isEqualTo('simple') }], viewContainer); viewsRegistry.registerViews([{ id: LOADED_SCRIPTS_VIEW_ID, name: nls.localize('loadedScripts', "Loaded Scripts"), ctorDescriptor: new SyncDescriptor(LoadedScriptsView), order: 35, weight: 5, canToggleVisibility: true, canMoveView: true, collapsed: true, when: ContextKeyExpr.and(CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer); registerCommands(); // register action to open viewlet const registry = Registry.as(WorkbenchActionRegistryExtensions.WorkbenchActions); -registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenDebugPanelAction, OpenDebugPanelAction.ID, OpenDebugPanelAction.LABEL, openPanelKb), 'View: Debug Console', nls.localize('view', "View")); -registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenDebugViewletAction, OpenDebugViewletAction.ID, OpenDebugViewletAction.LABEL, openViewletKb), 'View: Show Debug', nls.localize('view', "View")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenDebugConsoleAction, OpenDebugConsoleAction.ID, OpenDebugConsoleAction.LABEL, openPanelKb), 'View: Debug Console', nls.localize('view', "View")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenDebugViewletAction, OpenDebugViewletAction.ID, OpenDebugViewletAction.LABEL, openViewletKb), 'View: Show Run and Debug', nls.localize('view', "View")); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugToolBar, LifecyclePhase.Restored); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugContentProvider, LifecyclePhase.Eventually); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(StatusBarColorProvider, LifecyclePhase.Eventually); const debugCategory = nls.localize('debugCategory', "Debug"); +const runCategroy = nls.localize('runCategory', "Run"); registry.registerWorkbenchAction(SyncActionDescriptor.create(StartAction, StartAction.ID, StartAction.LABEL, { primary: KeyCode.F5 }, CONTEXT_IN_DEBUG_MODE.toNegated()), 'Debug: Start Debugging', debugCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(ConfigureAction, ConfigureAction.ID, ConfigureAction.LABEL), 'Debug: Open launch.json', debugCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(AddFunctionBreakpointAction, AddFunctionBreakpointAction.ID, AddFunctionBreakpointAction.LABEL), 'Debug: Add Function Breakpoint', debugCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(ReapplyBreakpointsAction, ReapplyBreakpointsAction.ID, ReapplyBreakpointsAction.LABEL), 'Debug: Reapply All Breakpoints', debugCategory); -registry.registerWorkbenchAction(SyncActionDescriptor.create(RunAction, RunAction.ID, RunAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.F5, mac: { primary: KeyMod.WinCtrl | KeyCode.F5 } }), 'Debug: Run (Start Without Debugging)', debugCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(RunAction, RunAction.ID, RunAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.F5, mac: { primary: KeyMod.WinCtrl | KeyCode.F5 } }), 'Run: Start Without Debugging', runCategroy); registry.registerWorkbenchAction(SyncActionDescriptor.create(RemoveAllBreakpointsAction, RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL), 'Debug: Remove All Breakpoints', debugCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(EnableAllBreakpointsAction, EnableAllBreakpointsAction.ID, EnableAllBreakpointsAction.LABEL), 'Debug: Enable All Breakpoints', debugCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(DisableAllBreakpointsAction, DisableAllBreakpointsAction.ID, DisableAllBreakpointsAction.LABEL), 'Debug: Disable All Breakpoints', debugCategory); @@ -161,16 +167,14 @@ registerDebugCommandPaletteItem(RunToCursorAction.ID, RunToCursorAction.LABEL, C registerDebugCommandPaletteItem(TOGGLE_INLINE_BREAKPOINT_ID, nls.localize('inlineBreakpoint', "Inline Breakpoint")); -// Register Quick Open -(Registry.as(QuickOpenExtensions.Quickopen)).registerQuickOpenHandler( - QuickOpenHandlerDescriptor.create( - DebugQuickOpenHandler, - DebugQuickOpenHandler.ID, - 'debug ', - 'inLaunchConfigurationsPicker', - nls.localize('debugCommands', "Debug Configuration") - ) -); +// Register Quick Access +Registry.as(QuickAccessExtensions.Quickaccess).registerQuickAccessProvider({ + ctor: StartDebugQuickAccessProvider, + prefix: StartDebugQuickAccessProvider.PREFIX, + contextKey: 'inLaunchConfigurationsPicker', + placeholder: nls.localize('startDebugPlaceholder', "Type the name of a launch configuration to run."), + helpEntries: [{ description: nls.localize('startDebuggingHelp', "Start Debugging"), needsEditor: false }] +}); // register service registerSingleton(IDebugService, service.DebugService); @@ -287,6 +291,7 @@ configurationRegistry.registerConfiguration({ // Register Debug Status Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugStatusContribution, LifecyclePhase.Eventually); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugProgressContribution, LifecyclePhase.Eventually); // Debug toolbar @@ -358,7 +363,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { group: '4_panels', command: { - id: OpenDebugPanelAction.ID, + id: OpenDebugConsoleAction.ID, title: nls.localize({ key: 'miToggleDebugConsole', comment: ['&& denotes a mnemonic'] }, "De&&bug Console") }, order: 2 diff --git a/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts b/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts index c1d7184360a..47356271445 100644 --- a/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts +++ b/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts @@ -227,7 +227,7 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector, theme * nothing. */ function setBasicColor(styleCode: number): void { - const theme = themeService.getTheme(); + const theme = themeService.getColorTheme(); let colorType: 'foreground' | 'background' | undefined; let colorIndex: number | undefined; diff --git a/src/vs/workbench/contrib/debug/browser/debugActions.ts b/src/vs/workbench/contrib/debug/browser/debugActions.ts index 3883e76be0a..370855e84e6 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActions.ts @@ -10,7 +10,6 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/ import { IDebugService, State, IEnablement, IBreakpoint, IDebugSession, ILaunch } from 'vs/workbench/contrib/debug/common/debug'; import { Variable, Breakpoint, FunctionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; @@ -160,7 +159,7 @@ export class StartAction extends AbstractDebugAction { export class RunAction extends StartAction { static readonly ID = 'workbench.action.debug.run'; - static LABEL = nls.localize('startWithoutDebugging', "Run (Start Without Debugging)"); + static LABEL = nls.localize('startWithoutDebugging', "Start Without Debugging"); protected isNoDebug(): boolean { return true; @@ -174,13 +173,13 @@ export class SelectAndStartAction extends AbstractDebugAction { constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService, - @IQuickOpenService private readonly quickOpenService: IQuickOpenService + @IQuickInputService private readonly quickInputService: IQuickInputService ) { super(id, label, '', debugService, keybindingService); } - run(): Promise { - return this.quickOpenService.show('debug '); + async run(): Promise { + this.quickInputService.quickAccess.show('debug '); } } @@ -393,20 +392,18 @@ export class CopyValueAction extends Action { async run(): Promise { const stackFrame = this.debugService.getViewModel().focusedStackFrame; const session = this.debugService.getViewModel().focusedSession; - - if (typeof this.value === 'string') { - return this.clipboardService.writeText(this.value); + if (!stackFrame || !session) { + return; } - if (stackFrame && session && this.value.evaluateName) { - try { - const evaluation = await session.evaluate(this.value.evaluateName, stackFrame.frameId, this.context); - this.clipboardService.writeText(evaluation.body.result); - } catch (e) { - this.clipboardService.writeText(this.value.value); - } - } else { - this.clipboardService.writeText(this.value.value); + const context = session.capabilities.supportsClipboardContext ? 'clipboard' : this.context; + const toEvaluate = typeof this.value === 'string' ? this.value : this.value.evaluateName || this.value.value; + + try { + const evaluation = await session.evaluate(toEvaluate, stackFrame.frameId, context); + this.clipboardService.writeText(evaluation.body.result); + } catch (e) { + this.clipboardService.writeText(typeof this.value === 'string' ? this.value : this.value.value); } } } diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index d0f1548d52b..e4e4ab8323d 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -158,13 +158,13 @@ export function registerCommands(): void { const debugService = accessor.get(IDebugService); const stackFrame = debugService.getViewModel().focusedStackFrame; const editorService = accessor.get(IEditorService); - const activeEditor = editorService.activeTextEditorWidget; + const activeEditorControl = editorService.activeTextEditorControl; const notificationService = accessor.get(INotificationService); const quickInputService = accessor.get(IQuickInputService); - if (stackFrame && isCodeEditor(activeEditor) && activeEditor.hasModel()) { - const position = activeEditor.getPosition(); - const resource = activeEditor.getModel().uri; + if (stackFrame && isCodeEditor(activeEditorControl) && activeEditorControl.hasModel()) { + const position = activeEditorControl.getPosition(); + const resource = activeEditorControl.getModel().uri; const source = stackFrame.thread.session.getSourceForUri(resource); if (source) { const response = await stackFrame.thread.session.gotoTargets(source.raw, position.lineNumber, position.column); @@ -368,11 +368,11 @@ export function registerCommands(): void { handler: (accessor) => { const debugService = accessor.get(IDebugService); const editorService = accessor.get(IEditorService); - const widget = editorService.activeTextEditorWidget; - if (isCodeEditor(widget)) { - const model = widget.getModel(); + const control = editorService.activeTextEditorControl; + if (isCodeEditor(control)) { + const model = control.getModel(); if (model) { - const position = widget.getPosition(); + const position = control.getPosition(); if (position) { const bps = debugService.getModel().getBreakpoints({ uri: model.uri, lineNumber: position.lineNumber }); if (bps.length) { @@ -514,11 +514,11 @@ export function registerCommands(): void { const inlineBreakpointHandler = (accessor: ServicesAccessor) => { const debugService = accessor.get(IDebugService); const editorService = accessor.get(IEditorService); - const widget = editorService.activeTextEditorWidget; - if (isCodeEditor(widget)) { - const position = widget.getPosition(); - if (position && widget.hasModel() && debugService.getConfigurationManager().canSetBreakpointsIn(widget.getModel())) { - const modelUri = widget.getModel().uri; + const control = editorService.activeTextEditorControl; + if (isCodeEditor(control)) { + const position = control.getPosition(); + if (position && control.hasModel() && debugService.getConfigurationManager().canSetBreakpointsIn(control.getModel())) { + const modelUri = control.getModel().uri; const breakpointAlreadySet = debugService.getModel().getBreakpoints({ lineNumber: position.lineNumber, uri: modelUri }) .some(bp => (bp.sessionAgnosticData.column === position.column || (!bp.column && position.column <= 1))); diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 1a4ec6cf4d7..aae1cb7919b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -12,7 +12,7 @@ import { URI as uri } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { ITextModel } from 'vs/editor/common/model'; -import { IEditor } from 'vs/workbench/common/editor'; +import { IEditorPane } from 'vs/workbench/common/editor'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -162,6 +162,15 @@ export class ConfigurationManager implements IConfigurationManager { return Promise.resolve(undefined); } + getDebuggerLabel(session: IDebugSession): string | undefined { + const dbgr = this.getDebugger(session.configuration.type); + if (dbgr) { + return dbgr.label; + } + + return undefined; + } + get onDidRegisterDebugger(): Event { return this._onDidRegisterDebugger.event; } @@ -337,7 +346,7 @@ export class ConfigurationManager implements IConfigurationManager { private setCompoundSchemaValues(): void { const compoundConfigurationsSchema = (launchSchema.properties!['compounds'].items).properties!['configurations']; const launchNames = this.launches.map(l => - l.getConfigurationNames(false)).reduce((first, second) => first.concat(second), []); + l.getConfigurationNames(true)).reduce((first, second) => first.concat(second), []); (compoundConfigurationsSchema.items).oneOf![0].enum = launchNames; (compoundConfigurationsSchema.items).oneOf![1].properties!.name.enum = launchNames; @@ -443,10 +452,10 @@ export class ConfigurationManager implements IConfigurationManager { return Promise.resolve(adapter); } - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; + const activeTextEditorControl = this.editorService.activeTextEditorControl; let candidates: Debugger[] | undefined; - if (isCodeEditor(activeTextEditorWidget)) { - const model = activeTextEditorWidget.getModel(); + if (isCodeEditor(activeTextEditorControl)) { + const model = activeTextEditorControl.getModel(); const language = model ? model.getLanguageIdentifier().language : undefined; const adapters = this.debuggers.filter(a => language && a.languages && a.languages.indexOf(language) >= 0); if (adapters.length === 1) { @@ -514,7 +523,7 @@ abstract class AbstractLaunch { return config.compounds.filter(compound => compound.name === name).pop(); } - getConfigurationNames(includeCompounds = true): string[] { + getConfigurationNames(ignoreCompoundsAndPresentation = false): string[] { const config = this.getConfig(); if (!config || (!Array.isArray(config.configurations) && !Array.isArray(config.compounds))) { return []; @@ -523,12 +532,14 @@ abstract class AbstractLaunch { if (config.configurations) { configurations.push(...config.configurations.filter(cfg => cfg && typeof cfg.name === 'string')); } - if (includeCompounds && config.compounds) { - if (config.compounds) { - configurations.push(...config.compounds.filter(compound => typeof compound.name === 'string' && compound.configurations && compound.configurations.length)); - } + + if (ignoreCompoundsAndPresentation) { + return configurations.map(c => c.name); } + if (config.compounds) { + configurations.push(...config.compounds.filter(compound => typeof compound.name === 'string' && compound.configurations && compound.configurations.length)); + } return getVisibleAndSorted(configurations).map(c => c.name); } } @@ -573,7 +584,7 @@ class Launch extends AbstractLaunch implements ILaunch { return this.configurationService.inspect('launch', { resource: this.workspace.uri }).workspaceFolderValue; } - async openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditor | null, created: boolean }> { + async openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditorPane | null, created: boolean }> { const resource = this.uri; let created = false; let content = ''; @@ -653,7 +664,7 @@ class WorkspaceLaunch extends AbstractLaunch implements ILaunch { return this.configurationService.inspect('launch').workspaceValue; } - async openConfigFile(sideBySide: boolean, preserveFocus: boolean): Promise<{ editor: IEditor | null, created: boolean }> { + async openConfigFile(sideBySide: boolean, preserveFocus: boolean): Promise<{ editor: IEditorPane | null, created: boolean }> { const editor = await this.editorService.openEditor({ resource: this.contextService.getWorkspace().configuration!, @@ -696,8 +707,8 @@ class UserLaunch extends AbstractLaunch implements ILaunch { return this.configurationService.inspect('launch').userValue; } - async openConfigFile(_: boolean, preserveFocus: boolean): Promise<{ editor: IEditor | null, created: boolean }> { - const editor = await this.preferencesService.openGlobalSettings(false, { preserveFocus }); + async openConfigFile(_: boolean, preserveFocus: boolean): Promise<{ editor: IEditorPane | null, created: boolean }> { + const editor = await this.preferencesService.openGlobalSettings(true, { preserveFocus }); return ({ editor: withUndefinedAsNull(editor), created: false diff --git a/src/vs/workbench/contrib/debug/browser/debugProgress.ts b/src/vs/workbench/contrib/debug/browser/debugProgress.ts new file mode 100644 index 00000000000..ec8e239c141 --- /dev/null +++ b/src/vs/workbench/contrib/debug/browser/debugProgress.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. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IDebugService, VIEWLET_ID, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; + +export class DebugProgressContribution implements IWorkbenchContribution { + + private toDispose: IDisposable[] = []; + + constructor( + @IDebugService private readonly debugService: IDebugService, + @IProgressService private readonly progressService: IProgressService + ) { + let progressListener: IDisposable; + const onFocusSession = (session: IDebugSession | undefined) => { + if (progressListener) { + progressListener.dispose(); + } + if (session) { + progressListener = session.onDidProgressStart(async progressStartEvent => { + const promise = new Promise(r => { + // Show progress until a progress end event comes or the session ends + const listener = Event.any(Event.filter(session.onDidProgressEnd, e => e.body.progressId === progressStartEvent.body.progressId), + session.onDidEndAdapter)(() => { + listener.dispose(); + r(); + }); + }); + + this.progressService.withProgress({ location: VIEWLET_ID }, () => promise); + const source = this.debugService.getConfigurationManager().getDebuggerLabel(session); + this.progressService.withProgress({ + location: ProgressLocation.Notification, + title: progressStartEvent.body.title, + cancellable: progressStartEvent.body.cancellable, + silent: true, + source, + delay: 500 + }, progressStep => { + let total = 0; + const reportProgress = (progress: { message?: string, percentage?: number }) => { + let increment = undefined; + if (typeof progress.percentage === 'number') { + increment = progress.percentage - total; + total += increment; + } + progressStep.report({ + message: progress.message, + increment, + total: typeof increment === 'number' ? 100 : undefined, + }); + }; + + if (progressStartEvent.body.message) { + reportProgress(progressStartEvent.body); + } + const progressUpdateListener = session.onDidProgressUpdate(e => { + if (e.body.progressId === progressStartEvent.body.progressId) { + reportProgress(e.body); + } + }); + + return promise.then(() => progressUpdateListener.dispose()); + }, () => session.cancel(progressStartEvent.body.progressId)); + }); + } + }; + this.toDispose.push(this.debugService.getViewModel().onDidFocusSession(onFocusSession)); + onFocusSession(this.debugService.getViewModel().focusedSession); + } + + dispose(): void { + dispose(this.toDispose); + } +} diff --git a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts new file mode 100644 index 00000000000..58074e88eca --- /dev/null +++ b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts @@ -0,0 +1,103 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { PickerQuickAccessProvider, IPickerQuickAccessItem, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess'; +import { localize } from 'vs/nls'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { matchesFuzzy } from 'vs/base/common/filters'; +import { StartAction } from 'vs/workbench/contrib/debug/browser/debugActions'; +import { withNullAsUndefined } from 'vs/base/common/types'; + +export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider { + + static PREFIX = 'debug '; + + constructor( + @IDebugService private readonly debugService: IDebugService, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @ICommandService private readonly commandService: ICommandService, + @INotificationService private readonly notificationService: INotificationService + ) { + super(StartDebugQuickAccessProvider.PREFIX, { + noResultsPick: { + label: localize('noDebugResults', "No matching launch configurations") + } + }); + } + + protected getPicks(filter: string): (IQuickPickSeparator | IPickerQuickAccessItem)[] { + const picks: Array = []; + + const configManager = this.debugService.getConfigurationManager(); + + // Entries: configs + let lastGroup: string | undefined; + for (let config of configManager.getAllConfigurations()) { + const highlights = matchesFuzzy(filter, config.name, true); + if (highlights) { + + // Separator + if (lastGroup !== config.presentation?.group) { + picks.push({ type: 'separator' }); + lastGroup = config.presentation?.group; + } + + // Launch entry + picks.push({ + label: config.name, + description: this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? config.launch.name : '', + highlights: { label: highlights }, + buttons: [{ + iconClass: 'codicon-gear', + tooltip: localize('customizeLaunchConfig', "Configure Launch Configuration") + }], + trigger: () => { + config.launch.openConfigFile(false, false); + + return TriggerAction.CLOSE_PICKER; + }, + accept: async () => { + if (StartAction.isEnabled(this.debugService)) { + this.debugService.getConfigurationManager().selectConfiguration(config.launch, config.name); + try { + await this.debugService.startDebugging(config.launch); + } catch (error) { + this.notificationService.error(error); + } + } + } + }); + } + } + + // Entries: launches + const visibleLaunches = configManager.getLaunches().filter(launch => !launch.hidden); + + // Separator + if (visibleLaunches.length > 0) { + picks.push({ type: 'separator' }); + } + + for (const launch of visibleLaunches) { + const label = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? + localize("addConfigTo", "Add Config ({0})...", launch.name) : + localize('addConfiguration', "Add Configuration..."); + + // Add Config entry + picks.push({ + label, + description: this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? launch.name : '', + highlights: { label: withNullAsUndefined(matchesFuzzy(filter, label, true)) }, + accept: () => this.commandService.executeCommand('debug.addConfiguration', launch.uri.toString()) + }); + } + + return picks; + } +} diff --git a/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts b/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts deleted file mode 100644 index f9df27bfe5e..00000000000 --- a/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts +++ /dev/null @@ -1,145 +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 nls from 'vs/nls'; -import { IDebugService, ILaunch } from 'vs/workbench/contrib/debug/common/debug'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { ICommandService } from 'vs/platform/commands/common/commands'; -import { StartAction } from 'vs/workbench/contrib/debug/browser/debugActions'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { QuickOpenEntry, QuickOpenModel, QuickOpenEntryGroup, IHighlight } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { Mode, IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen'; -import { QuickOpenHandler } from 'vs/workbench/browser/quickopen'; -import { matchesFuzzy } from 'vs/base/common/filters'; - -class AddConfigEntry extends QuickOpenEntry { - - constructor(private label: string, private launch: ILaunch, private commandService: ICommandService, private contextService: IWorkspaceContextService, highlights: IHighlight[] = []) { - super(highlights); - } - - getLabel(): string { - return this.label; - } - - getDescription(): string { - return this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? this.launch.name : ''; - } - - getAriaLabel(): string { - return nls.localize('entryAriaLabel', "{0}, debug", this.getLabel()); - } - - run(mode: Mode): boolean { - if (mode === Mode.PREVIEW) { - return false; - } - this.commandService.executeCommand('debug.addConfiguration', this.launch.uri.toString()); - - return true; - } -} - -class StartDebugEntry extends QuickOpenEntry { - - constructor(private debugService: IDebugService, private contextService: IWorkspaceContextService, private notificationService: INotificationService, private launch: ILaunch, private configurationName: string, highlights: IHighlight[] = []) { - super(highlights); - } - - getLabel(): string { - return this.configurationName; - } - - getDescription(): string { - return this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? this.launch.name : ''; - } - - getAriaLabel(): string { - return nls.localize('entryAriaLabel', "{0}, debug", this.getLabel()); - } - - run(mode: Mode): boolean { - if (mode === Mode.PREVIEW || !StartAction.isEnabled(this.debugService)) { - return false; - } - // Run selected debug configuration - this.debugService.getConfigurationManager().selectConfiguration(this.launch, this.configurationName); - this.debugService.startDebugging(this.launch).then(undefined, e => this.notificationService.error(e)); - - return true; - } -} - -export class DebugQuickOpenHandler extends QuickOpenHandler { - - static readonly ID = 'workbench.picker.launch'; - - private autoFocusIndex: number | undefined; - - constructor( - @IDebugService private readonly debugService: IDebugService, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @ICommandService private readonly commandService: ICommandService, - @INotificationService private readonly notificationService: INotificationService - ) { - super(); - } - - getAriaLabel(): string { - return nls.localize('debugAriaLabel', "Type a name of a launch configuration to run."); - } - - getResults(input: string, token: CancellationToken): Promise { - const configurations: QuickOpenEntry[] = []; - - const configManager = this.debugService.getConfigurationManager(); - const allConfigurations = configManager.getAllConfigurations(); - let lastGroup: string | undefined; - for (let config of allConfigurations) { - const highlights = matchesFuzzy(input, config.name, true); - if (highlights) { - if (config.launch === configManager.selectedConfiguration.launch && config.name === configManager.selectedConfiguration.name) { - this.autoFocusIndex = configurations.length; - } - let entry: QuickOpenEntry = new StartDebugEntry(this.debugService, this.contextService, this.notificationService, config.launch, config.name, highlights); - if (lastGroup !== config.presentation?.group) { - entry = new QuickOpenEntryGroup(entry, undefined, true); - lastGroup = config.presentation?.group; - } - configurations.push(entry); - } - } - - configManager.getLaunches().filter(l => !l.hidden).forEach((l, index) => { - - const label = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? nls.localize("addConfigTo", "Add Config ({0})...", l.name) : nls.localize('addConfiguration', "Add Configuration..."); - const entry = new AddConfigEntry(label, l, this.commandService, this.contextService, matchesFuzzy(input, label, true) || undefined); - if (index === 0) { - configurations.push(new QuickOpenEntryGroup(entry, undefined, true)); - } else { - configurations.push(entry); - } - - }); - - return Promise.resolve(new QuickOpenModel(configurations)); - } - - getAutoFocus(input: string): IAutoFocus { - return { - autoFocusFirstEntry: !!input, - autoFocusIndex: this.autoFocusIndex - }; - } - - getEmptyLabel(searchString: string): string { - if (searchString.length > 0) { - return nls.localize('noConfigurationsMatching', "No debug configurations matching"); - } - - return nls.localize('noConfigurationsFound', "No debug configurations found. Please create a 'launch.json' file."); - } -} diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 7fb045f3b60..9a168614fcb 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -46,6 +46,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { TaskRunResult, DebugTaskRunner } from 'vs/workbench/contrib/debug/browser/debugTaskRunner'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IViewsService } from 'vs/workbench/common/views'; +import { generateUuid } from 'vs/base/common/uuid'; const DEBUG_BREAKPOINTS_KEY = 'debug.breakpoint'; const DEBUG_FUNCTION_BREAKPOINTS_KEY = 'debug.functionbreakpoint'; @@ -73,7 +74,7 @@ export class DebugService implements IDebugService { private breakpointsToSendOnResourceSaved: Set; private initializing = false; private previousState: State | undefined; - private initCancellationToken: CancellationTokenSource | undefined; + private sessionCancellationTokens = new Map(); private activity: IDisposable | undefined; constructor( @@ -206,24 +207,33 @@ export class DebugService implements IDebugService { return this.initializing ? State.Initializing : State.Inactive; } - private startInitializingState() { + private startInitializingState(): void { if (!this.initializing) { this.initializing = true; this.onStateChange(); } } - private endInitializingState() { - if (this.initCancellationToken) { - this.initCancellationToken.cancel(); - this.initCancellationToken = undefined; - } + private endInitializingState(): void { if (this.initializing) { this.initializing = false; this.onStateChange(); } } + private cancelTokens(id: string | undefined): void { + if (id) { + const token = this.sessionCancellationTokens.get(id); + if (token) { + token.cancel(); + this.sessionCancellationTokens.delete(id); + } + } else { + this.sessionCancellationTokens.forEach(t => t.cancel()); + this.sessionCancellationTokens.clear(); + } + } + private onStateChange(): void { const state = this.state; if (this.previousState !== state) { @@ -380,8 +390,11 @@ export class DebugService implements IDebugService { } } - this.initCancellationToken = new CancellationTokenSource(); - const configByProviders = await this.configurationManager.resolveConfigurationByProviders(launch && launch.workspace ? launch.workspace.uri : undefined, type, config!, this.initCancellationToken.token); + const initCancellationToken = new CancellationTokenSource(); + const sessionId = generateUuid(); + this.sessionCancellationTokens.set(sessionId, initCancellationToken); + + const configByProviders = await this.configurationManager.resolveConfigurationByProviders(launch && launch.workspace ? launch.workspace.uri : undefined, type, config!, initCancellationToken.token); // a falsy config indicates an aborted launch if (configByProviders && configByProviders.type) { try { @@ -391,15 +404,15 @@ export class DebugService implements IDebugService { return false; } - if (!this.initCancellationToken) { + if (initCancellationToken.token.isCancellationRequested) { // User cancelled, silently return return false; } - const cfg = await this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, type, resolvedConfig, this.initCancellationToken.token); + const cfg = await this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, type, resolvedConfig, initCancellationToken.token); if (!cfg) { - if (launch && type && cfg === null && this.initCancellationToken) { // show launch.json only for "config" being "null". - await launch.openConfigFile(false, true, type, this.initCancellationToken.token); + if (launch && type && cfg === null && !initCancellationToken.token.isCancellationRequested) { // show launch.json only for "config" being "null". + await launch.openConfigFile(false, true, type, initCancellationToken.token); } return false; } @@ -423,7 +436,7 @@ export class DebugService implements IDebugService { const workspace = launch?.workspace || this.contextService.getWorkspace(); const taskResult = await this.taskRunner.runTaskAndCheckErrors(workspace, resolvedConfig.preLaunchTask, (msg, actions) => this.showError(msg, actions)); if (taskResult === TaskRunResult.Success) { - return this.doCreateSession(launch?.workspace, { resolved: resolvedConfig, unresolved: unresolvedConfig }, options); + return this.doCreateSession(sessionId, launch?.workspace, { resolved: resolvedConfig, unresolved: unresolvedConfig }, options); } return false; } catch (err) { @@ -432,16 +445,16 @@ export class DebugService implements IDebugService { } else if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { await this.showError(nls.localize('noFolderWorkspaceDebugError', "The active file can not be debugged. Make sure it is saved and that you have a debug extension installed for that file type.")); } - if (launch && this.initCancellationToken) { - await launch.openConfigFile(false, true, undefined, this.initCancellationToken.token); + if (launch && !initCancellationToken.token.isCancellationRequested) { + await launch.openConfigFile(false, true, undefined, initCancellationToken.token); } return false; } } - if (launch && type && configByProviders === null && this.initCancellationToken) { // show launch.json only for "config" being "null". - await launch.openConfigFile(false, true, type, this.initCancellationToken.token); + if (launch && type && configByProviders === null && !initCancellationToken.token.isCancellationRequested) { // show launch.json only for "config" being "null". + await launch.openConfigFile(false, true, type, initCancellationToken.token); } return false; @@ -450,9 +463,9 @@ export class DebugService implements IDebugService { /** * instantiates the new session, initializes the session, registers session listeners and reports telemetry */ - private async doCreateSession(root: IWorkspaceFolder | undefined, configuration: { resolved: IConfig, unresolved: IConfig | undefined }, options?: IDebugSessionOptions): Promise { + private async doCreateSession(sessionId: string, root: IWorkspaceFolder | undefined, configuration: { resolved: IConfig, unresolved: IConfig | undefined }, options?: IDebugSessionOptions): Promise { - const session = this.instantiationService.createInstance(DebugSession, configuration, root, this.model, options); + const session = this.instantiationService.createInstance(DebugSession, sessionId, configuration, root, this.model, options); this.model.addSession(session); // register listeners as the very first thing! this.registerSessionListeners(session); @@ -566,6 +579,7 @@ export class DebugService implements IDebugService { } } this.endInitializingState(); + this.cancelTokens(session.getId()); this._onDidEndSession.fire(session); const focusedSession = this.viewModel.focusedSession; @@ -656,12 +670,13 @@ export class DebugService implements IDebugService { let resolved: IConfig | undefined | null = session.configuration; if (launch && needsToSubstitute && unresolved) { - this.initCancellationToken = new CancellationTokenSource(); - const resolvedByProviders = await this.configurationManager.resolveConfigurationByProviders(launch.workspace ? launch.workspace.uri : undefined, unresolved.type, unresolved, this.initCancellationToken.token); + const initCancellationToken = new CancellationTokenSource(); + this.sessionCancellationTokens.set(session.getId(), initCancellationToken); + const resolvedByProviders = await this.configurationManager.resolveConfigurationByProviders(launch.workspace ? launch.workspace.uri : undefined, unresolved.type, unresolved, initCancellationToken.token); if (resolvedByProviders) { resolved = await this.substituteVariables(launch, resolvedByProviders); - if (resolved && this.initCancellationToken) { - resolved = await this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, unresolved.type, resolved, this.initCancellationToken.token); + if (resolved && !initCancellationToken.token.isCancellationRequested) { + resolved = await this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, unresolved.type, resolved, initCancellationToken.token); } } else { resolved = resolvedByProviders; @@ -695,6 +710,7 @@ export class DebugService implements IDebugService { if (sessions.length === 0) { this.taskRunner.cancel(); this.endInitializingState(); + this.cancelTokens(undefined); } return Promise.all(sessions.map(s => s.terminate())); @@ -744,8 +760,9 @@ export class DebugService implements IDebugService { const control = editor.getControl(); if (stackFrame && isCodeEditor(control) && control.hasModel()) { const model = control.getModel(); - if (stackFrame.range.startLineNumber <= model.getLineCount()) { - const lineContent = control.getModel().getLineContent(stackFrame.range.startLineNumber); + const lineNumber = stackFrame.range.startLineNumber; + if (lineNumber >= 1 && lineNumber <= model.getLineCount()) { + const lineContent = control.getModel().getLineContent(lineNumber); aria.alert(nls.localize('debuggingPaused', "Debugging paused {0}, {1} {2} {3}", thread && thread.stoppedDetails ? `, reason ${thread.stoppedDetails.reason}` : '', stackFrame.source ? stackFrame.source.name : '', stackFrame.range.startLineNumber, lineContent)); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 214d3763448..1abe31223a9 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -5,7 +5,6 @@ import { URI } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; -import * as nls from 'vs/nls'; import * as platform from 'vs/base/common/platform'; import severity from 'vs/base/common/severity'; import { Event, Emitter } from 'vs/base/common/event'; @@ -34,10 +33,11 @@ import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cance import { distinct } from 'vs/base/common/arrays'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { localize } from 'vs/nls'; +import { canceled } from 'vs/base/common/errors'; export class DebugSession implements IDebugSession { - private id: string; private _subId: string | undefined; private raw: RawDebugSession | undefined; private initialized = false; @@ -55,6 +55,9 @@ export class DebugSession implements IDebugSession { private readonly _onDidLoadedSource = new Emitter(); private readonly _onDidCustomEvent = new Emitter(); + private readonly _onDidProgressStart = new Emitter(); + private readonly _onDidProgressUpdate = new Emitter(); + private readonly _onDidProgressEnd = new Emitter(); private readonly _onDidChangeREPLElements = new Emitter(); @@ -62,6 +65,7 @@ export class DebugSession implements IDebugSession { private readonly _onDidChangeName = new Emitter(); constructor( + private id: string, private _configuration: { resolved: IConfig, unresolved: IConfig | undefined }, public root: IWorkspaceFolder | undefined, private model: DebugModel, @@ -78,7 +82,6 @@ export class DebugSession implements IDebugSession { @INotificationService private readonly notificationService: INotificationService, @ILifecycleService lifecycleService: ILifecycleService ) { - this.id = generateUuid(); this._options = options || {}; if (this.hasSeparateRepl()) { this.repl = new ReplModel(); @@ -185,6 +188,18 @@ export class DebugSession implements IDebugSession { return this._onDidLoadedSource.event; } + get onDidProgressStart(): Event { + return this._onDidProgressStart.event; + } + + get onDidProgressUpdate(): Event { + return this._onDidProgressUpdate.event; + } + + get onDidProgressEnd(): Event { + return this._onDidProgressEnd.event; + } + //---- DAP requests /** @@ -214,12 +229,13 @@ export class DebugSession implements IDebugSession { supportsVariableType: true, // #8858 supportsVariablePaging: true, // #9537 supportsRunInTerminalRequest: true, // #10574 - locale: platform.locale + locale: platform.locale, + supportsProgressReporting: true // #92253 }); this.initialized = true; this._onDidChangeState.fire(); - this.model.setExceptionBreakpoints(this.raw!.capabilities.exceptionBreakpointFilters || []); + this.model.setExceptionBreakpoints((this.raw && this.raw.capabilities.exceptionBreakpointFilters) || []); } catch (err) { this.initialized = true; this._onDidChangeState.fire(); @@ -233,7 +249,10 @@ export class DebugSession implements IDebugSession { */ async launchOrAttach(config: IConfig): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'launch or attach')); + } + if (this.parentSession && this.parentSession.state === State.Inactive) { + throw canceled(); } // __sessionID only used for EH debugging (but we add it always for now...) @@ -251,7 +270,7 @@ export class DebugSession implements IDebugSession { */ async terminate(restart = false): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'terminate')); } this.cancelAllRequests(); @@ -267,7 +286,7 @@ export class DebugSession implements IDebugSession { */ async disconnect(restart = false): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'disconnect')); } this.cancelAllRequests(); @@ -279,7 +298,7 @@ export class DebugSession implements IDebugSession { */ async restart(): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'restart')); } this.cancelAllRequests(); @@ -288,7 +307,7 @@ export class DebugSession implements IDebugSession { async sendBreakpoints(modelUri: URI, breakpointsToSend: IBreakpoint[], sourceModified: boolean): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'breakpoints')); } if (!this.raw.readyForBreakpoints) { @@ -322,7 +341,7 @@ export class DebugSession implements IDebugSession { async sendFunctionBreakpoints(fbpts: IFunctionBreakpoint[]): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'function breakpoints')); } if (this.raw.readyForBreakpoints) { @@ -339,7 +358,7 @@ export class DebugSession implements IDebugSession { async sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'exception breakpoints')); } if (this.raw.readyForBreakpoints) { @@ -349,10 +368,10 @@ export class DebugSession implements IDebugSession { async dataBreakpointInfo(name: string, variablesReference?: number): Promise<{ dataId: string | null, description: string, canPersist?: boolean }> { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'data breakpoints info')); } if (!this.raw.readyForBreakpoints) { - throw new Error(nls.localize('sessionNotReadyForBreakpoints', "Session is not ready for breakpoints")); + throw new Error(localize('sessionNotReadyForBreakpoints', "Session is not ready for breakpoints")); } const response = await this.raw.dataBreakpointInfo({ name, variablesReference }); @@ -361,7 +380,7 @@ export class DebugSession implements IDebugSession { async sendDataBreakpoints(dataBreakpoints: IDataBreakpoint[]): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'data breakpoints')); } if (this.raw.readyForBreakpoints) { @@ -378,7 +397,7 @@ export class DebugSession implements IDebugSession { async breakpointsLocations(uri: URI, lineNumber: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'breakpoints locations')); } const source = this.getRawSource(uri); @@ -394,7 +413,7 @@ export class DebugSession implements IDebugSession { customRequest(request: string, args: any): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", request)); } return this.raw.custom(request, args); @@ -402,7 +421,7 @@ export class DebugSession implements IDebugSession { stackTrace(threadId: number, startFrame: number, levels: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'stackTrace')); } const token = this.getNewCancellationToken(threadId); @@ -411,7 +430,7 @@ export class DebugSession implements IDebugSession { async exceptionInfo(threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'exceptionInfo')); } const response = await this.raw.exceptionInfo({ threadId }); @@ -429,7 +448,7 @@ export class DebugSession implements IDebugSession { scopes(frameId: number, threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'scopes')); } const token = this.getNewCancellationToken(threadId); @@ -438,7 +457,7 @@ export class DebugSession implements IDebugSession { variables(variablesReference: number, threadId: number | undefined, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'variables')); } const token = threadId ? this.getNewCancellationToken(threadId) : undefined; @@ -447,7 +466,7 @@ export class DebugSession implements IDebugSession { evaluate(expression: string, frameId: number, context?: string): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'evaluate')); } return this.raw.evaluate({ expression, frameId, context }); @@ -455,7 +474,7 @@ export class DebugSession implements IDebugSession { async restartFrame(frameId: number, threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'restartFrame')); } await this.raw.restartFrame({ frameId }, threadId); @@ -463,7 +482,7 @@ export class DebugSession implements IDebugSession { async next(threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'next')); } await this.raw.next({ threadId }); @@ -471,7 +490,7 @@ export class DebugSession implements IDebugSession { async stepIn(threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'stepIn')); } await this.raw.stepIn({ threadId }); @@ -479,7 +498,7 @@ export class DebugSession implements IDebugSession { async stepOut(threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'stepOut')); } await this.raw.stepOut({ threadId }); @@ -487,7 +506,7 @@ export class DebugSession implements IDebugSession { async stepBack(threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'stepBack')); } await this.raw.stepBack({ threadId }); @@ -495,7 +514,7 @@ export class DebugSession implements IDebugSession { async continue(threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'continue')); } await this.raw.continue({ threadId }); @@ -503,7 +522,7 @@ export class DebugSession implements IDebugSession { async reverseContinue(threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'reverse continue')); } await this.raw.reverseContinue({ threadId }); @@ -511,7 +530,7 @@ export class DebugSession implements IDebugSession { async pause(threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'pause')); } await this.raw.pause({ threadId }); @@ -519,7 +538,7 @@ export class DebugSession implements IDebugSession { async terminateThreads(threadIds?: number[]): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'terminateThreads')); } await this.raw.terminateThreads({ threadIds }); @@ -527,7 +546,7 @@ export class DebugSession implements IDebugSession { setVariable(variablesReference: number, name: string, value: string): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'setVariable')); } return this.raw.setVariable({ variablesReference, name, value }); @@ -535,7 +554,7 @@ export class DebugSession implements IDebugSession { gotoTargets(source: DebugProtocol.Source, line: number, column?: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'gotoTargets')); } return this.raw.gotoTargets({ source, line, column }); @@ -543,7 +562,7 @@ export class DebugSession implements IDebugSession { goto(threadId: number, targetId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'goto')); } return this.raw.goto({ threadId, targetId }); @@ -551,7 +570,7 @@ export class DebugSession implements IDebugSession { loadSource(resource: URI): Promise { if (!this.raw) { - return Promise.reject(new Error('no debug adapter')); + return Promise.reject(new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'loadSource'))); } const source = this.getSourceForUri(resource); @@ -569,7 +588,7 @@ export class DebugSession implements IDebugSession { async getLoadedSources(): Promise { if (!this.raw) { - return Promise.reject(new Error('no debug adapter')); + return Promise.reject(new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'getLoadedSources'))); } const response = await this.raw.loadedSources({}); @@ -582,7 +601,7 @@ export class DebugSession implements IDebugSession { async completions(frameId: number | undefined, text: string, position: Position, overwriteBefore: number, token: CancellationToken): Promise { if (!this.raw) { - return Promise.reject(new Error('no debug adapter')); + return Promise.reject(new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'completions'))); } return this.raw.completions({ @@ -593,6 +612,14 @@ export class DebugSession implements IDebugSession { }, token); } + async cancel(progressId: string): Promise { + if (!this.raw) { + return Promise.reject(new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'cancel'))); + } + + return this.raw.cancel({ progressId }); + } + //---- threads getThread(threadId: number): Thread | undefined { @@ -701,7 +728,7 @@ export class DebugSession implements IDebugSession { } this.rawListeners.push(this.raw.onDidInitialize(async () => { - aria.status(nls.localize('debuggingStarted', "Debugging started.")); + aria.status(localize('debuggingStarted', "Debugging started.")); const sendConfigurationDone = async () => { if (this.raw && this.raw.capabilities.supportsConfigurationDoneRequest) { try { @@ -783,7 +810,7 @@ export class DebugSession implements IDebugSession { })); this.rawListeners.push(this.raw.onDidTerminateDebugee(async event => { - aria.status(nls.localize('debuggingStopped', "Debugging stopped.")); + aria.status(localize('debuggingStopped', "Debugging stopped.")); if (event.body && event.body.restart) { await this.debugService.restartSession(this, event.body.restart); } else if (this.raw) { @@ -914,6 +941,16 @@ export class DebugSession implements IDebugSession { this._onDidCustomEvent.fire(event); })); + this.rawListeners.push(this.raw.onDidProgressStart(event => { + this._onDidProgressStart.fire(event); + })); + this.rawListeners.push(this.raw.onDidProgressUpdate(event => { + this._onDidProgressUpdate.fire(event); + })); + this.rawListeners.push(this.raw.onDidProgressEnd(event => { + this._onDidProgressEnd.fire(event); + })); + this.rawListeners.push(this.raw.onDidExitAdapter(event => { this.initialized = true; this.model.setBreakpointSessionData(this.getId(), this.capabilities, undefined); diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index fe8e8a13582..7238760c3c2 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -18,8 +18,7 @@ import { FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/d import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { Themable } from 'vs/workbench/common/theme'; -import { registerThemingParticipant, IThemeService } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { registerColor, contrastBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { localize } from 'vs/nls'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -67,7 +66,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { super(themeService); this.$el = dom.$('div.debug-toolbar'); - this.$el.style.top = `${layoutService.getTitleBarOffset()}px`; + this.$el.style.top = `${layoutService.offset?.top ?? 0}px`; this.dragArea = dom.append(this.$el, dom.$('div.drag-area.codicon.codicon-gripper')); @@ -121,6 +120,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { this._register(this.debugService.getViewModel().onDidFocusSession(() => this.updateScheduler.schedule())); this._register(this.debugService.onDidNewSession(() => this.updateScheduler.schedule())); this._register(this.configurationService.onDidChangeConfiguration(e => this.onDidConfigurationChange(e))); + this._register(this.debugToolBarMenu.onDidChange(() => this.updateScheduler.schedule())); this._register(this.actionBar.actionRunner.onDidRun((e: IRunEvent) => { // check for error if (e.error && !errors.isPromiseCanceledError(e.error)) { @@ -152,7 +152,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { // Prevent default to stop editor selecting text #8524 mouseMoveEvent.preventDefault(); // Reduce x by width of drag handle to reduce jarring #16604 - this.setCoordinates(mouseMoveEvent.posx - 14, mouseMoveEvent.posy - this.layoutService.getTitleBarOffset()); + this.setCoordinates(mouseMoveEvent.posx - 14, mouseMoveEvent.posy - (this.layoutService.offset?.top ?? 0)); }); const mouseUpListener = dom.addDisposableGenericMouseUpListner(window, (e: MouseEvent) => { @@ -198,7 +198,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { } private setYCoordinate(y = this.yCoordinate): void { - const titlebarOffset = this.layoutService.getTitleBarOffset(); + const titlebarOffset = this.layoutService.offset?.top ?? 0; this.$el.style.top = `${titlebarOffset + y}px`; this.yCoordinate = y; } @@ -240,7 +240,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { } if (!this.isBuilt) { this.isBuilt = true; - this.layoutService.getWorkbenchElement().appendChild(this.$el); + this.layoutService.container.appendChild(this.$el); } this.isVisible = true; diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index 7011327d8da..e80f6f0670b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -8,7 +8,7 @@ import * as nls from 'vs/nls'; import { IAction } from 'vs/base/common/actions'; import * as DOM from 'vs/base/browser/dom'; import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IDebugService, VIEWLET_ID, State, BREAKPOINTS_VIEW_ID, IDebugConfiguration, DEBUG_PANEL_ID, CONTEXT_DEBUG_UX, CONTEXT_DEBUG_UX_KEY } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, VIEWLET_ID, State, BREAKPOINTS_VIEW_ID, IDebugConfiguration, CONTEXT_DEBUG_UX, CONTEXT_DEBUG_UX_KEY, REPL_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; import { StartAction, ConfigureAction, SelectAndStartAction, FocusSessionAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { StartDebugActionViewItem, FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -30,10 +30,9 @@ import { IMenu, MenuId, IMenuService, MenuItemAction } from 'vs/platform/actions import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { TogglePanelAction } from 'vs/workbench/browser/panel'; -import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { StartView } from 'vs/workbench/contrib/debug/browser/startView'; -import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; +import { WelcomeView } from 'vs/workbench/contrib/debug/browser/welcomeView'; +import { ToggleViewAction } from 'vs/workbench/browser/actions/layoutActions'; export class DebugViewPaneContainer extends ViewPaneContainer { @@ -92,7 +91,7 @@ export class DebugViewPaneContainer extends ViewPaneContainer { if (this.startDebugActionViewItem) { this.startDebugActionViewItem.focus(); } else { - this.focusView(StartView.ID); + this.focusView(WelcomeView.ID); } } @@ -107,8 +106,8 @@ export class DebugViewPaneContainer extends ViewPaneContainer { } @memoize - private get toggleReplAction(): OpenDebugPanelAction { - return this._register(this.instantiationService.createInstance(OpenDebugPanelAction, OpenDebugPanelAction.ID, OpenDebugPanelAction.LABEL)); + private get toggleReplAction(): OpenDebugConsoleAction { + return this._register(this.instantiationService.createInstance(OpenDebugConsoleAction, OpenDebugConsoleAction.ID, OpenDebugConsoleAction.LABEL)); } @memoize @@ -120,6 +119,7 @@ export class DebugViewPaneContainer extends ViewPaneContainer { if (CONTEXT_DEBUG_UX.getValue(this.contextKeyService) === 'simple') { return []; } + if (!this.showInitialDebugActions) { if (!this.debugToolBarMenu) { @@ -185,7 +185,7 @@ export class DebugViewPaneContainer extends ViewPaneContainer { } if (state === State.Initializing) { - this.progressService.withProgress({ location: VIEWLET_ID }, _progress => { + this.progressService.withProgress({ location: VIEWLET_ID, }, _progress => { return new Promise(resolve => this.progressResolve = resolve); }); } @@ -230,16 +230,18 @@ export class DebugViewPaneContainer extends ViewPaneContainer { } } -export class OpenDebugPanelAction extends TogglePanelAction { +export class OpenDebugConsoleAction extends ToggleViewAction { public static readonly ID = 'workbench.debug.action.toggleRepl'; public static readonly LABEL = nls.localize('toggleDebugPanel', "Debug Console"); constructor( id: string, label: string, - @IPanelService panelService: IPanelService, + @IViewsService viewsService: IViewsService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IContextKeyService contextKeyService: IContextKeyService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService ) { - super(id, label, DEBUG_PANEL_ID, panelService, layoutService, 'codicon-repl'); + super(id, label, REPL_VIEW_ID, viewsService, viewDescriptorService, contextKeyService, layoutService, 'codicon-debug-console'); } } diff --git a/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts b/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts index 7c84987a84f..93f702fa33c 100644 --- a/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts @@ -10,7 +10,7 @@ import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IExceptionInfo, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -35,8 +35,8 @@ export class ExceptionWidget extends ZoneWidget { this._backgroundColor = Color.white; - this._applyTheme(themeService.getTheme()); - this._disposables.add(themeService.onThemeChange(this._applyTheme.bind(this))); + this._applyTheme(themeService.getColorTheme()); + this._disposables.add(themeService.onDidColorThemeChange(this._applyTheme.bind(this))); this.create(); const onDidLayoutChangeScheduler = new RunOnceScheduler(() => this._doLayout(undefined, undefined), 50); @@ -44,7 +44,7 @@ export class ExceptionWidget extends ZoneWidget { this._disposables.add(onDidLayoutChangeScheduler); } - private _applyTheme(theme: ITheme): void { + private _applyTheme(theme: IColorTheme): void { this._backgroundColor = theme.getColor(debugExceptionWidgetBackground); const frameColor = theme.getColor(debugExceptionWidgetBorder); this.style({ diff --git a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts index 79c3eb05810..5b0ab1a43f3 100644 --- a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts +++ b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts @@ -13,7 +13,6 @@ import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService' import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { mapToSerializable } from 'vs/base/common/map'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkspaceProvider, IWorkspace } from 'vs/workbench/services/host/browser/browserHostService'; import { IProcessEnvironment } from 'vs/base/common/platform'; @@ -107,8 +106,8 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i // Open debug window as new window. Pass ParsedArgs over. return this.workspaceProvider.open(debugWorkspace, { - reuse: false, // debugging always requires a new window - payload: mapToSerializable(environment) // mandatory properties to enable debugging + reuse: false, // debugging always requires a new window + payload: Array.from(environment.entries()) // mandatory properties to enable debugging }); } diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index af223d0a938..5df627acdb2 100644 --- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { normalize, isAbsolute, posix } from 'vs/base/common/path'; -import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -29,7 +29,7 @@ import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeNode, ITreeFilter, TreeVisibility, TreeFilterResult, ITreeElement } from 'vs/base/browser/ui/tree/tree'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { TreeResourceNavigator, WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService'; +import { ResourceNavigator, WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService'; import { dispose } from 'vs/base/common/lifecycle'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; import { DebugContentProvider } from 'vs/workbench/contrib/debug/common/debugContentProvider'; @@ -430,7 +430,7 @@ export class LoadedScriptsView extends ViewPane { @IThemeService themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('loadedScriptsSection', "Loaded Scripts Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.loadedScriptsItemType = CONTEXT_LOADED_SCRIPTS_ITEM_TYPE.bindTo(contextKeyService); } @@ -491,7 +491,7 @@ export class LoadedScriptsView extends ViewPane { }, 300); this._register(this.changeScheduler); - const loadedScriptsNavigator = new TreeResourceNavigator(this.tree); + const loadedScriptsNavigator = ResourceNavigator.createTreeResourceNavigator(this.tree); this._register(loadedScriptsNavigator); this._register(loadedScriptsNavigator.onDidOpenResource(e => { if (e.element instanceof BaseTreeItem) { diff --git a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css index 2fb7b93bd76..fc93ad15e0b 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -125,7 +125,8 @@ font-style: italic; } -.monaco-workbench .monaco-list-row .expression .error { +.monaco-workbench .monaco-list-row .expression .error, +.monaco-workbench .debug-pane .debug-variables .scope .error { color: #e51400; } @@ -145,7 +146,8 @@ color: rgba(204, 204, 204, 0.6); } -.vs-dark .monaco-workbench .monaco-list-row .expression .error { +.vs-dark .monaco-workbench .monaco-list-row .expression .error, +.vs-dark .monaco-workbench .debug-pane .debug-variables .scope .error { color: #f48771; } @@ -173,7 +175,8 @@ color: #ce9178; } -.hc-black .monaco-workbench .monaco-list-row .expression .error { +.hc-black .monaco-workbench .monaco-list-row .expression .error, +.hc-black .monaco-workbench .debug-pane .debug-variables .scope .error { color: #f48771; } diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index 8a1dd5969f9..33ada77a166 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -83,6 +83,7 @@ .debug-pane .disabled { opacity: 0.65; + cursor: initial; } /* Call stack */ @@ -153,15 +154,22 @@ .debug-pane .debug-call-stack .monaco-list-row .monaco-action-bar { display: none; + flex-shrink: 0; } .debug-pane .debug-call-stack .monaco-list-row:hover .monaco-action-bar { display: initial; } +.debug-pane .debug-call-stack .session .codicon { + line-height: 22px; + margin-right: 2px; +} + .monaco-workbench .debug-pane .debug-call-stack .monaco-action-bar .action-item > .action-label { width: 16px; height: 100%; + line-height: 22px; margin-right: 8px; vertical-align: text-top; } @@ -231,13 +239,12 @@ } .debug-pane .debug-call-stack .load-more { - font-style: italic; text-align: center; } .debug-pane .debug-call-stack .show-more { - font-style: italic; opacity: 0.35; + text-align: center; } .debug-pane .debug-call-stack .error { @@ -315,6 +322,14 @@ animation-name: debugViewletValueChanged; } +.debug-pane .debug-variables .scope .error { + font-style: italic; + text-overflow: ellipsis; + overflow: hidden; + font-family: var(--monaco-monospace-font); + font-weight: normal; +} + /* Breakpoints */ .debug-pane .monaco-list-row { diff --git a/src/vs/workbench/contrib/debug/browser/media/repl.css b/src/vs/workbench/contrib/debug/browser/media/repl.css index ba2bbf5bccc..f92f9f2b0af 100644 --- a/src/vs/workbench/contrib/debug/browser/media/repl.css +++ b/src/vs/workbench/contrib/debug/browser/media/repl.css @@ -53,6 +53,10 @@ margin-right: 8px; cursor: pointer; text-decoration: underline; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 150px; } .repl .repl-tree .monaco-list-row { diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index 139e872b6c2..e925e77850e 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -56,20 +56,23 @@ export class RawDebugSession implements IDisposable { private didReceiveStoppedEvent = false; // DAP events - private readonly _onDidInitialize: Emitter; - private readonly _onDidStop: Emitter; - private readonly _onDidContinued: Emitter; - private readonly _onDidTerminateDebugee: Emitter; - private readonly _onDidExitDebugee: Emitter; - private readonly _onDidThread: Emitter; - private readonly _onDidOutput: Emitter; - private readonly _onDidBreakpoint: Emitter; - private readonly _onDidLoadedSource: Emitter; - private readonly _onDidCustomEvent: Emitter; - private readonly _onDidEvent: Emitter; + private readonly _onDidInitialize = new Emitter(); + private readonly _onDidStop = new Emitter(); + private readonly _onDidContinued = new Emitter(); + private readonly _onDidTerminateDebugee = new Emitter(); + private readonly _onDidExitDebugee = new Emitter(); + private readonly _onDidThread = new Emitter(); + private readonly _onDidOutput = new Emitter(); + private readonly _onDidBreakpoint = new Emitter(); + private readonly _onDidLoadedSource = new Emitter(); + private readonly _onDidProgressStart = new Emitter(); + private readonly _onDidProgressUpdate = new Emitter(); + private readonly _onDidProgressEnd = new Emitter(); + private readonly _onDidCustomEvent = new Emitter(); + private readonly _onDidEvent = new Emitter(); // DA events - private readonly _onDidExitAdapter: Emitter; + private readonly _onDidExitAdapter = new Emitter(); private debugAdapter: IDebugAdapter | null; private toDispose: IDisposable[] = []; @@ -86,20 +89,6 @@ export class RawDebugSession implements IDisposable { this.debugAdapter = debugAdapter; this._capabilities = Object.create(null); - this._onDidInitialize = new Emitter(); - this._onDidStop = new Emitter(); - this._onDidContinued = new Emitter(); - this._onDidTerminateDebugee = new Emitter(); - this._onDidExitDebugee = new Emitter(); - this._onDidThread = new Emitter(); - this._onDidOutput = new Emitter(); - this._onDidBreakpoint = new Emitter(); - this._onDidLoadedSource = new Emitter(); - this._onDidCustomEvent = new Emitter(); - this._onDidEvent = new Emitter(); - - this._onDidExitAdapter = new Emitter(); - this.toDispose.push(this.debugAdapter.onError(err => { this.shutdown(err); })); @@ -151,6 +140,15 @@ export class RawDebugSession implements IDisposable { case 'exit': this._onDidExitDebugee.fire(event); break; + case 'progressStart': + this._onDidProgressStart.fire(event as DebugProtocol.ProgressStartEvent); + break; + case 'progressUpdate': + this._onDidProgressUpdate.fire(event as DebugProtocol.ProgressUpdateEvent); + break; + case 'progressEnd': + this._onDidProgressEnd.fire(event as DebugProtocol.ProgressEndEvent); + break; default: this._onDidCustomEvent.fire(event); break; @@ -219,6 +217,18 @@ export class RawDebugSession implements IDisposable { return this._onDidCustomEvent.event; } + get onDidProgressStart(): Event { + return this._onDidProgressStart.event; + } + + get onDidProgressUpdate(): Event { + return this._onDidProgressUpdate.event; + } + + get onDidProgressEnd(): Event { + return this._onDidProgressEnd.event; + } + get onDidEvent(): Event { return this._onDidEvent.event; } @@ -230,7 +240,7 @@ export class RawDebugSession implements IDisposable { */ async start(): Promise { if (!this.debugAdapter) { - return Promise.reject(new Error('no debug adapter')); + return Promise.reject(new Error(nls.localize('noDebugAdapterStart', "No debug adapter, can not start debug session."))); } await this.debugAdapter.startSession(); @@ -270,7 +280,7 @@ export class RawDebugSession implements IDisposable { if (this.capabilities.supportsTerminateRequest) { if (!this.terminated) { this.terminated = true; - return this.send('terminate', { restart }, undefined, 500); + return this.send('terminate', { restart }, undefined, 1000); } return this.disconnect(restart); } @@ -481,7 +491,7 @@ export class RawDebugSession implements IDisposable { this.inShutdown = true; if (this.debugAdapter) { try { - await this.send('disconnect', { restart }, undefined, 500); + await this.send('disconnect', { restart }, undefined, 1000); } finally { this.stopAdapter(error); } @@ -601,9 +611,15 @@ export class RawDebugSession implements IDisposable { private send(command: string, args: any, token?: CancellationToken, timeout?: number): Promise { return new Promise((completeDispatch, errorDispatch) => { if (!this.debugAdapter) { - errorDispatch(new Error(nls.localize('noDebugAdapter', "No debug adapter found. Can not send '{0}'.", command))); + if (this.inShutdown) { + // We are in shutdown silently complete + completeDispatch(); + } else { + errorDispatch(new Error(nls.localize('noDebugAdapter', "No debug adapter found. Can not send '{0}'.", command))); + } return; } + let cancelationListener: IDisposable; const requestId = this.debugAdapter.sendRequest(command, args, (response: DebugProtocol.Response) => { if (cancelationListener) { @@ -654,7 +670,7 @@ export class RawDebugSession implements IDisposable { }); } if (error && error.format && error.showUser) { - this.notificationService.error(error.format); + this.notificationService.error(userMessage); } return new Error(userMessage); diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index b803f4ce468..627b26bc6fd 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -36,7 +36,7 @@ import { IDecorationOptions } from 'vs/editor/common/editorCommon'; import { transparent, editorForeground } from 'vs/platform/theme/common/colorRegistry'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems'; -import { CompletionContext, CompletionList, CompletionProviderRegistry, CompletionItem, completionKindFromString, CompletionItemKind } from 'vs/editor/common/modes'; +import { CompletionContext, CompletionList, CompletionProviderRegistry, CompletionItem, completionKindFromString, CompletionItemKind, CompletionItemInsertTextRule } from 'vs/editor/common/modes'; import { first } from 'vs/base/common/arrays'; import { ITreeNode, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -50,7 +50,6 @@ import { ITextResourcePropertiesService } from 'vs/editor/common/services/textRe import { RunOnceScheduler } from 'vs/base/common/async'; import { FuzzyScore } from 'vs/base/common/filters'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; import { ReplDelegate, ReplVariablesRenderer, ReplSimpleElementsRenderer, ReplEvaluationInputsRenderer, ReplEvaluationResultsRenderer, ReplRawObjectsRenderer, ReplDataSource, ReplAccessibilityProvider, ReplGroupRenderer } from 'vs/workbench/contrib/debug/browser/replViewer'; import { localize } from 'vs/nls'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; @@ -77,6 +76,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { private static readonly REFRESH_DELAY = 100; // delay in ms to refresh the repl for new elements to show private static readonly REPL_INPUT_LINE_HEIGHT = 19; + private static readonly URI = uri.parse(`${DEBUG_SCHEME}:replinput`); private history: HistoryNavigator; private tree!: WorkbenchAsyncDataTree; @@ -113,7 +113,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { @IOpenerService openerService: IOpenerService, @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), id: REPL_VIEW_ID, ariaHeaderLabel: localize('debugConsole', "Debug Console") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.history = new HistoryNavigator(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')), 50); codeEditorService.registerDecorationType(DECORATION_KEY, {}); @@ -148,13 +148,24 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { if (response && response.body && response.body.targets) { response.body.targets.forEach(item => { if (item && item.label) { + let insertTextRules: CompletionItemInsertTextRule | undefined = undefined; + let insertText = item.text || item.label; + if (typeof item.selectionStart === 'number') { + // If a debug completion item sets a selection we need to use snippets to make sure the selection is selected #90974 + insertTextRules = CompletionItemInsertTextRule.InsertAsSnippet; + const selectionLength = typeof item.selectionLength === 'number' ? item.selectionLength : 0; + const placeholder = selectionLength > 0 ? '${1:' + insertText.substr(item.selectionStart, selectionLength) + '}$0' : '$0'; + insertText = insertText.substr(0, item.selectionStart) + placeholder + insertText.substr(item.selectionStart + selectionLength); + } + suggestions.push({ label: item.label, - insertText: item.text || item.label, + insertText, kind: completionKindFromString(item.type || 'property'), filterText: (item.start && item.length) ? text.substr(item.start, item.length).concat(item.label) : undefined, range: computeRange(item.length || overwriteBefore), - sortText: item.sortText + sortText: item.sortText, + insertTextRules }); } }); @@ -190,7 +201,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { } this.updateActions(); })); - this._register(this.themeService.onThemeChange(() => { + this._register(this.themeService.onDidColorThemeChange(() => { this.refreshReplElements(false); if (this.isVisible()) { this.updateInputDecoration(); @@ -200,7 +211,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { if (!visible) { dispose(this.model); } else { - this.model = this.modelService.createModel('', null, uri.parse(`${DEBUG_SCHEME}:replinput`), true); + this.model = this.modelService.getModel(Repl.URI) || this.modelService.createModel('', null, Repl.URI, true); this.setMode(); this.replInput.setModel(this.model); this.updateInputDecoration(); @@ -209,9 +220,20 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { })); this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('debug.console.lineHeight') || e.affectsConfiguration('debug.console.fontSize') || e.affectsConfiguration('debug.console.fontFamily')) { - this.onDidFontChange(); + this.onDidStyleChange(); } })); + + this._register(this.themeService.onDidColorThemeChange(e => { + this.onDidStyleChange(); + })); + + this._register(this.viewDescriptorService.onDidChangeLocation(e => { + if (e.views.some(v => v.id === this.id)) { + this.onDidStyleChange(); + } + })); + this._register(this.editorService.onDidActiveEditorChange(() => { this.setMode(); })); @@ -244,24 +266,25 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { return; } - const activeEditor = this.editorService.activeTextEditorWidget; - if (isCodeEditor(activeEditor)) { + const activeEditorControl = this.editorService.activeTextEditorControl; + if (isCodeEditor(activeEditorControl)) { this.modelChangeListener.dispose(); - this.modelChangeListener = activeEditor.onDidChangeModelLanguage(() => this.setMode()); - if (activeEditor.hasModel()) { - this.model.setMode(activeEditor.getModel().getLanguageIdentifier()); + this.modelChangeListener = activeEditorControl.onDidChangeModelLanguage(() => this.setMode()); + if (activeEditorControl.hasModel()) { + this.model.setMode(activeEditorControl.getModel().getLanguageIdentifier()); } } } - private onDidFontChange(): void { + private onDidStyleChange(): void { if (this.styleElement) { const debugConsole = this.configurationService.getValue('debug').console; const fontSize = debugConsole.fontSize; const fontFamily = debugConsole.fontFamily === 'default' ? 'var(--monaco-monospace-font)' : debugConsole.fontFamily; const lineHeight = debugConsole.lineHeight ? `${debugConsole.lineHeight}px` : '1.4em'; + const backgroundColor = this.themeService.getColorTheme().getColor(this.getBackgroundColor()); - // Set the font size, font family, line height and align the twistie to be centered + // Set the font size, font family, line height and align the twistie to be centered, and input theme color this.styleElement.innerHTML = ` .repl .repl-tree .expression { font-size: ${fontSize}px; @@ -275,6 +298,10 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { .repl .repl-tree .monaco-tl-twistie { background-position-y: calc(100% - ${fontSize * 1.4 / 2 - 8}px); } + + .repl .repl-input-wrapper .monaco-editor .lines-content { + background-color: ${backgroundColor}; + } `; this.tree.rerender(); @@ -421,6 +448,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { @memoize private get refreshScheduler(): RunOnceScheduler { + const autoExpanded = new Set(); return new RunOnceScheduler(async () => { if (!this.tree.getInput()) { return; @@ -431,11 +459,22 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { const session = this.tree.getInput(); if (session) { - const replElements = session.getReplElements(); - const lastElement = replElements.length ? replElements[replElements.length - 1] : undefined; - if (lastElement instanceof ReplGroup && lastElement.autoExpand) { - await this.tree.expand(lastElement); - } + // Automatically expand repl group elements when specified + const autoExpandElements = async (elements: IReplElement[]) => { + for (let element of elements) { + if (element instanceof ReplGroup) { + if (element.autoExpand && !autoExpanded.has(element.getId())) { + autoExpanded.add(element.getId()); + await this.tree.expand(element); + } + if (!this.tree.isCollapsed(element)) { + // Repl groups can have children which are repl groups thus we might need to expand those as well + await autoExpandElements(element.getChildren()); + } + } + } + }; + await autoExpandElements(session.getReplElements()); } if (lastElementVisible) { @@ -483,7 +522,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { setRowLineHeight: false, supportDynamicHeights: wordWrap, overrideStyles: { - listBackground: PANEL_BACKGROUND + listBackground: this.getBackgroundColor() } }); this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); @@ -499,7 +538,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { // Make sure to select the session if debugging is already active this.selectSession(); this.styleElement = dom.createStyleSheet(this.container); - this.onDidFontChange(); + this.onDidStyleChange(); } private createReplInput(container: HTMLElement): void { @@ -582,7 +621,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { const decorations: IDecorationOptions[] = []; if (this.isReadonly && this.replInput.hasTextFocus() && !this.replInput.getValue()) { - const transparentForeground = transparent(editorForeground, 0.4)(this.themeService.getTheme()); + const transparentForeground = transparent(editorForeground, 0.4)(this.themeService.getColorTheme()); decorations.push({ range: { startLineNumber: 0, diff --git a/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts b/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts index 3d4c198e3eb..9316b53c8f4 100644 --- a/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts +++ b/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { localize } from 'vs/nls'; import { registerColor, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { IDebugService, State, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_BACKGROUND, Themable, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_BORDER, STATUS_BAR_BORDER } from 'vs/workbench/common/theme'; +import { STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_BACKGROUND, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_BORDER, STATUS_BAR_BORDER } from 'vs/workbench/common/theme'; import { addClass, removeClass, createStyleSheet } from 'vs/base/browser/dom'; import { assertIsDefined } from 'vs/base/common/types'; diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 2c40b45a4e2..78f6bdfcbe0 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -9,7 +9,7 @@ import * as dom from 'vs/base/browser/dom'; import { CollapseAction } from 'vs/workbench/browser/viewlet'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IDebugService, IExpression, IScope, CONTEXT_VARIABLES_FOCUSED, IViewModel } from 'vs/workbench/contrib/debug/common/debug'; -import { Variable, Scope } from 'vs/workbench/contrib/debug/common/debugModel'; +import { Variable, Scope, ErrorScope } from 'vs/workbench/contrib/debug/common/debugModel'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { renderViewTree, renderVariable, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView'; @@ -17,7 +17,7 @@ import { IAction, Action } from 'vs/base/common/actions'; import { CopyValueAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, ITreeMouseEvent, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; @@ -61,7 +61,7 @@ export class VariablesView extends ViewPane { @IThemeService themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); // Use scheduler to prevent unnecessary flashing this.onFocusStackFrameScheduler = new RunOnceScheduler(async () => { @@ -80,8 +80,11 @@ export class VariablesView extends ViewPane { if (stackFrame) { const scopes = await stackFrame.getScopes(); // Expand the first scope if it is not expensive and if there is no expansion state (all are collapsed) - if (scopes.every(s => this.tree.getNode(s).collapsed) && scopes.length > 0 && !scopes[0].expensive) { - this.tree.expand(scopes[0]); + if (scopes.every(s => this.tree.getNode(s).collapsed) && scopes.length > 0) { + const toExpand = scopes.filter(s => !s.expensive).shift(); + if (toExpand) { + this.tree.expand(toExpand); + } } } @@ -97,7 +100,7 @@ export class VariablesView extends ViewPane { const treeContainer = renderViewTree(container); this.tree = >this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'VariablesView', treeContainer, new VariablesDelegate(), - [this.instantiationService.createInstance(VariablesRenderer), new ScopesRenderer()], + [this.instantiationService.createInstance(VariablesRenderer), new ScopesRenderer(), new ScopeErrorRenderer()], new VariablesDataSource(), { ariaLabel: nls.localize('variablesAriaTreeLabel', "Debug Variables"), accessibilityProvider: new VariablesAccessibilityProvider(), @@ -217,7 +220,7 @@ function isViewModel(obj: any): obj is IViewModel { export class VariablesDataSource implements IAsyncDataSource { hasChildren(element: IViewModel | IExpression | IScope): boolean { - if (isViewModel(element) || element instanceof Scope) { + if (isViewModel(element)) { return true; } @@ -246,6 +249,10 @@ class VariablesDelegate implements IListVirtualDelegate { } getTemplateId(element: IExpression | IScope): string { + if (element instanceof ErrorScope) { + return ScopeErrorRenderer.ID; + } + if (element instanceof Scope) { return ScopesRenderer.ID; } @@ -278,6 +285,33 @@ class ScopesRenderer implements ITreeRenderer { + + static readonly ID = 'scopeError'; + + get templateId(): string { + return ScopeErrorRenderer.ID; + } + + renderTemplate(container: HTMLElement): IScopeErrorTemplateData { + const wrapper = dom.append(container, $('.scope')); + const error = dom.append(wrapper, $('.error')); + return { error }; + } + + renderElement(element: ITreeNode, index: number, templateData: IScopeErrorTemplateData): void { + templateData.error.innerText = element.element.name; + } + + disposeTemplate(): void { + // noop + } +} + export class VariablesRenderer extends AbstractExpressionsRenderer { static readonly ID = 'variable'; diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 1be23a91b3e..02cdac7f1f9 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -18,7 +18,7 @@ import { IAction, Action } from 'vs/base/common/actions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { renderExpressionValue, renderViewTree, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; @@ -58,7 +58,7 @@ export class WatchExpressionsView extends ViewPane { @IThemeService themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('watchExpressionsSection', "Watch Expressions Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.onWatchExpressionsUpdatedScheduler = new RunOnceScheduler(() => { this.needsRefresh = false; @@ -190,9 +190,7 @@ export class WatchExpressionsView extends ViewPane { this.debugService.getViewModel().setSelectedExpression(expression); return Promise.resolve(); })); - if (!expression.hasChildren) { - actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, expression.value, 'watch')); - } + actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, expression.value, 'watch')); actions.push(new Separator()); actions.push(new Action('debug.removeWatchExpression', nls.localize('removeWatchExpression', "Remove Expression"), undefined, true, () => { @@ -204,9 +202,7 @@ export class WatchExpressionsView extends ViewPane { actions.push(new AddWatchExpressionAction(AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL, this.debugService, this.keybindingService)); if (element instanceof Variable) { const variable = element as Variable; - if (!variable.hasChildren) { - actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, variable, 'watch')); - } + actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, variable, 'watch')); actions.push(new Separator()); } actions.push(new RemoveAllWatchExpressionsAction(RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL, this.debugService, this.keybindingService)); diff --git a/src/vs/workbench/contrib/debug/browser/startView.ts b/src/vs/workbench/contrib/debug/browser/welcomeView.ts similarity index 70% rename from src/vs/workbench/contrib/debug/browser/startView.ts rename to src/vs/workbench/contrib/debug/browser/welcomeView.ts index 38ef45e83b5..74501598045 100644 --- a/src/vs/workbench/contrib/debug/browser/startView.ts +++ b/src/vs/workbench/contrib/debug/browser/welcomeView.ts @@ -13,7 +13,7 @@ import { localize } from 'vs/nls'; import { StartAction, ConfigureAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IViewDescriptorService, IViewsRegistry, Extensions } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -29,10 +29,10 @@ const debugStartLanguageKey = 'debugStartLanguage'; const CONTEXT_DEBUG_START_LANGUAGE = new RawContextKey(debugStartLanguageKey, undefined); const CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR = new RawContextKey('debuggerInterestedInActiveEditor', false); -export class StartView extends ViewPane { +export class WelcomeView extends ViewPane { - static ID = 'workbench.debug.startView'; - static LABEL = localize('start', "Start"); + static ID = 'workbench.debug.welcome'; + static LABEL = localize('run', "Run"); private debugStartLanguageContext: IContextKey; private debuggerInterestedContext: IContextKey; @@ -52,7 +52,7 @@ export class StartView extends ViewPane { @IStorageService storageSevice: IStorageService, @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: localize('debugStart', "Debug Start Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.debugStartLanguageContext = CONTEXT_DEBUG_START_LANGUAGE.bindTo(contextKeyService); this.debuggerInterestedContext = CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR.bindTo(contextKeyService); @@ -60,9 +60,9 @@ export class StartView extends ViewPane { this.debugStartLanguageContext.set(lastSetLanguage); const setContextKey = () => { - const editor = this.editorService.activeTextEditorWidget; - if (isCodeEditor(editor)) { - const model = editor.getModel(); + const editorControl = this.editorService.activeTextEditorControl; + if (isCodeEditor(editorControl)) { + const model = editorControl.getModel(); const language = model ? model.getLanguageIdentifier().language : undefined; if (language && this.debugService.getConfigurationManager().isDebuggerInterestedInLanguage(language)) { this.debugStartLanguageContext.set(language); @@ -75,6 +75,11 @@ export class StartView extends ViewPane { }; this._register(editorService.onDidActiveEditorChange(setContextKey)); this._register(this.debugService.getConfigurationManager().onDidRegisterDebugger(setContextKey)); + this._register(this.onDidChangeBodyVisibility(visible => { + if (visible) { + setContextKey(); + } + })); setContextKey(); const debugKeybinding = this.keybindingService.lookupKeybinding(StartAction.ID); @@ -87,23 +92,27 @@ export class StartView extends ViewPane { } const viewsRegistry = Registry.as(Extensions.ViewsRegistry); -viewsRegistry.registerViewWelcomeContent(StartView.ID, { - content: localize('openAFileWhichCanBeDebugged', "[Open a file](command:{0}) which can be debugged or run.", isMacintosh ? OpenFileFolderAction.ID : OpenFileAction.ID), +viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { + content: localize({ key: 'openAFileWhichCanBeDebugged', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + "[Open a file](command:{0}) which can be debugged or run.", isMacintosh ? OpenFileFolderAction.ID : OpenFileAction.ID), when: CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR.toNegated() }); let debugKeybindingLabel = ''; -viewsRegistry.registerViewWelcomeContent(StartView.ID, { - content: localize('runAndDebugAction', "[Run and Debug{0}](command:{1})", debugKeybindingLabel, StartAction.ID), +viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { + content: localize({ key: 'runAndDebugAction', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + "[Run and Debug{0}](command:{1})", debugKeybindingLabel, StartAction.ID), preconditions: [CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR] }); -viewsRegistry.registerViewWelcomeContent(StartView.ID, { - content: localize('customizeRunAndDebug', "To customize Run and Debug [create a launch.json file](command:{0}).", ConfigureAction.ID), +viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { + content: localize({ key: 'customizeRunAndDebug', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + "To customize Run and Debug [create a launch.json file](command:{0}).", ConfigureAction.ID), when: WorkbenchStateContext.notEqualsTo('empty') }); -viewsRegistry.registerViewWelcomeContent(StartView.ID, { - content: localize('customizeRunAndDebugOpenFolder', "To customize Run and Debug, [open a folder](command:{0}) and create a launch.json file.", isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID), +viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { + content: localize({ key: 'customizeRunAndDebugOpenFolder', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + "To customize Run and Debug, [open a folder](command:{0}) and create a launch.json file.", isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID), when: WorkbenchStateContext.isEqualTo('empty') }); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index d753ab2f888..89e1ff8f568 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -11,7 +11,7 @@ import { IJSONSchemaSnippet } from 'vs/base/common/jsonSchema'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { ITextModel as EditorIModel } from 'vs/editor/common/model'; -import { IEditor, ITextEditor } from 'vs/workbench/common/editor'; +import { IEditorPane, ITextEditorPane } from 'vs/workbench/common/editor'; import { Position, IPosition } from 'vs/editor/common/core/position'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { Range, IRange } from 'vs/editor/common/core/range'; @@ -199,6 +199,9 @@ export interface IDebugSession extends ITreeElement { readonly onDidLoadedSource: Event; readonly onDidCustomEvent: Event; + readonly onDidProgressStart: Event; + readonly onDidProgressUpdate: Event; + readonly onDidProgressEnd: Event; // DAP request @@ -221,6 +224,7 @@ export interface IDebugSession extends ITreeElement { variables(variablesReference: number, threadId: number | undefined, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise; evaluate(expression: string, frameId?: number, context?: string): Promise; customRequest(request: string, args: any): Promise; + cancel(progressId: string): Promise; restartFrame(frameId: number, threadId: number): Promise; next(threadId: number): Promise; @@ -301,6 +305,7 @@ export interface IScope extends IExpressionContainer { readonly name: string; readonly expensive: boolean; readonly range?: IRange; + readonly hasChildren: boolean; } export interface IStackFrame extends ITreeElement { @@ -316,7 +321,7 @@ export interface IStackFrame extends ITreeElement { forgetScopes(): void; restart(): Promise; toString(): string; - openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean): Promise; + openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean): Promise; equals(other: IStackFrame): boolean; } @@ -658,6 +663,7 @@ export interface IConfigurationManager { resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: any, token: CancellationToken): Promise; getDebugAdapterDescriptor(session: IDebugSession): Promise; + getDebuggerLabel(session: IDebugSession): string | undefined; registerDebugAdapterFactory(debugTypes: string[], debugAdapterFactory: IDebugAdapterFactory): IDisposable; createDebugAdapter(session: IDebugSession): IDebugAdapter | undefined; @@ -704,12 +710,12 @@ export interface ILaunch { * Returns the names of all configurations and compounds. * Ignores configurations which are invalid. */ - getConfigurationNames(includeCompounds?: boolean): string[]; + getConfigurationNames(ignoreCompoundsAndPresentation?: boolean): string[]; /** * Opens the launch.json file. Creates if it does not exist. */ - openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditor | null, created: boolean }>; + openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditorPane | null, created: boolean }>; } // Debug service interfaces diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 6995887eefe..e06b75b2d7a 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -21,7 +21,7 @@ import { commonSuffixLength } from 'vs/base/common/strings'; import { posix } from 'vs/base/common/path'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { ITextEditor } from 'vs/workbench/common/editor'; +import { ITextEditorPane } from 'vs/workbench/common/editor'; import { mixin } from 'vs/base/common/objects'; export class ExpressionContainer implements IExpressionContainer { @@ -269,6 +269,21 @@ export class Scope extends ExpressionContainer implements IScope { } } +export class ErrorScope extends Scope { + + constructor( + stackFrame: IStackFrame, + index: number, + message: string, + ) { + super(stackFrame, index, message, 0, false); + } + + toString(): string { + return this.name; + } +} + export class StackFrame implements IStackFrame { private scopes: Promise | undefined; @@ -290,10 +305,20 @@ export class StackFrame implements IStackFrame { getScopes(): Promise { if (!this.scopes) { this.scopes = this.thread.session.scopes(this.frameId, this.thread.threadId).then(response => { - return response && response.body && response.body.scopes ? - response.body.scopes.map((rs, index) => new Scope(this, index, rs.name, rs.variablesReference, rs.expensive, rs.namedVariables, rs.indexedVariables, - rs.line && rs.column && rs.endLine && rs.endColumn ? new Range(rs.line, rs.column, rs.endLine, rs.endColumn) : undefined)) : []; - }, err => []); + if (!response || !response.body || !response.body.scopes) { + return []; + } + + const scopeNameIndexes = new Map(); + return response.body.scopes.map(rs => { + const previousIndex = scopeNameIndexes.get(rs.name); + const index = typeof previousIndex === 'number' ? previousIndex + 1 : 0; + scopeNameIndexes.set(rs.name, index); + return new Scope(this, index, rs.name, rs.variablesReference, rs.expensive, rs.namedVariables, rs.indexedVariables, + rs.line && rs.column && rs.endLine && rs.endColumn ? new Range(rs.line, rs.column, rs.endLine, rs.endColumn) : undefined); + + }); + }, err => [new ErrorScope(this, 0, err.message)]); } return this.scopes; @@ -347,7 +372,7 @@ export class StackFrame implements IStackFrame { return sourceToString === UNKNOWN_SOURCE_LABEL ? this.name : `${this.name} (${sourceToString})`; } - async openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { + async openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { if (this.source.available) { return this.source.openInEditor(editorService, this.range, preserveFocus, sideBySide, pinned); } diff --git a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts index 5807fa4a3b6..4307295e632 100644 --- a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts +++ b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts @@ -70,13 +70,17 @@ declare module DebugProtocol { } /** Cancel request; value of command field is 'cancel'. - The 'cancel' request is used by the frontend to indicate that it is no longer interested in the result produced by a specific request issued earlier. + The 'cancel' request is used by the frontend in two situations: + - to indicate that it is no longer interested in the result produced by a specific request issued earlier + - to cancel a progress sequence. Clients should only call this request if the capability 'supportsCancelRequest' is true. This request has a hint characteristic: a debug adapter can only be expected to make a 'best effort' in honouring this request but there are no guarantees. The 'cancel' request may return an error if it could not cancel an operation but a frontend should refrain from presenting this error to end users. A frontend client should only call this request if the capability 'supportsCancelRequest' is true. - The request that got canceled still needs to send a response back. - This can either be a normal result ('success' attribute true) or an error response ('success' attribute false and the 'message' set to 'cancelled'). + The request that got canceled still needs to send a response back. This can either be a normal result ('success' attribute true) + or an error response ('success' attribute false and the 'message' set to 'cancelled'). Returning partial results from a cancelled request is possible but please note that a frontend client has no generic way for detecting that a response is partial or not. + The progress that got cancelled still needs to send a 'progressEnd' event back. + A client should not assume that progress just got cancelled after sending the 'cancel' request. */ export interface CancelRequest extends Request { // command: 'cancel'; @@ -85,8 +89,14 @@ declare module DebugProtocol { /** Arguments for 'cancel' request. */ export interface CancelArguments { - /** The ID (attribute 'seq') of the request to cancel. */ + /** The ID (attribute 'seq') of the request to cancel. If missing no request is cancelled. + Both a 'requestId' and a 'progressId' can be specified in one request. + */ requestId?: number; + /** The ID (attribute 'progressId') of the progress to cancel. If missing no progress is cancelled. + Both a 'requestId' and a 'progressId' can be specified in one request. + */ + progressId?: string; } /** Response to 'cancel' request. This is just an acknowledgement, so no body field is required. */ @@ -302,8 +312,73 @@ declare module DebugProtocol { }; } + /** Event message for 'progressStart' event type. + The event signals that a long running operation is about to start and + provides additional information for the client to set up a corresponding progress and cancellation UI. + The client is free to delay the showing of the UI in order to reduce flicker. + This event should only be sent if the client has passed the value true for the 'supportsProgressReporting' capability of the 'initialize' request. + */ + export interface ProgressStartEvent extends Event { + // event: 'progressStart'; + body: { + /** An ID that must be used in subsequent 'progressUpdate' and 'progressEnd' events to make them refer to the same progress reporting. + IDs must be unique within a debug session. + */ + progressId: string; + /** Mandatory (short) title of the progress reporting. Shown in the UI to describe the long running operation. */ + title: string; + /** The request ID that this progress report is related to. If specified a debug adapter is expected to emit + progress events for the long running request until the request has been either completed or cancelled. + If the request ID is omitted, the progress report is assumed to be related to some general activity of the debug adapter. + */ + requestId?: number; + /** If true, the request that reports progress may be canceled with a 'cancel' request. + So this property basically controls whether the client should use UX that supports cancellation. + Clients that don't support cancellation are allowed to ignore the setting. + */ + cancellable?: boolean; + /** Optional, more detailed progress message. */ + message?: string; + /** Optional progress percentage to display (value range: 0 to 100). If omitted no percentage will be shown. */ + percentage?: number; + }; + } + + /** Event message for 'progressUpdate' event type. + The event signals that the progress reporting needs to updated with a new message and/or percentage. + The client does not have to update the UI immediately, but the clients needs to keep track of the message and/or percentage values. + This event should only be sent if the client has passed the value true for the 'supportsProgressReporting' capability of the 'initialize' request. + */ + export interface ProgressUpdateEvent extends Event { + // event: 'progressUpdate'; + body: { + /** The ID that was introduced in the initial 'progressStart' event. */ + progressId: string; + /** Optional, more detailed progress message. If omitted, the previous message (if any) is used. */ + message?: string; + /** Optional progress percentage to display (value range: 0 to 100). If omitted no percentage will be shown. */ + percentage?: number; + }; + } + + /** Event message for 'progressEnd' event type. + The event signals the end of the progress reporting with an optional final message. + This event should only be sent if the client has passed the value true for the 'supportsProgressReporting' capability of the 'initialize' request. + */ + export interface ProgressEndEvent extends Event { + // event: 'progressEnd'; + body: { + /** The ID that was introduced in the initial 'ProgressStartEvent'. */ + progressId: string; + /** Optional, more detailed progress message. If omitted, the previous message (if any) is used. */ + message?: string; + }; + } + /** RunInTerminal request; value of command field is 'runInTerminal'. - This request is sent from the debug adapter to the client to run a command in a terminal. This is typically used to launch the debuggee in a terminal provided by the client. + This optional request is sent from the debug adapter to the client to run a command in a terminal. + This is typically used to launch the debuggee in a terminal provided by the client. + This request should only be called if the client has passed the value true for the 'supportsRunInTerminalRequest' capability of the 'initialize' request. */ export interface RunInTerminalRequest extends Request { // command: 'runInTerminal'; @@ -335,8 +410,10 @@ declare module DebugProtocol { } /** Initialize request; value of command field is 'initialize'. - The 'initialize' request is sent as the first request from the client to the debug adapter in order to configure it with client capabilities and to retrieve capabilities from the debug adapter. - Until the debug adapter has responded to with an 'initialize' response, the client must not send any additional requests or events to the debug adapter. In addition the debug adapter is not allowed to send any requests or events to the client until it has responded with an 'initialize' response. + The 'initialize' request is sent as the first request from the client to the debug adapter + in order to configure it with client capabilities and to retrieve capabilities from the debug adapter. + Until the debug adapter has responded to with an 'initialize' response, the client must not send any additional requests or events to the debug adapter. + In addition the debug adapter is not allowed to send any requests or events to the client until it has responded with an 'initialize' response. The 'initialize' request may only be sent once. */ export interface InitializeRequest extends Request { @@ -370,6 +447,8 @@ declare module DebugProtocol { supportsRunInTerminalRequest?: boolean; /** Client supports memory references. */ supportsMemoryReferences?: boolean; + /** Client supports progress reporting. */ + supportsProgressReporting?: boolean; } /** Response to 'initialize' request. */ @@ -379,7 +458,9 @@ declare module DebugProtocol { } /** ConfigurationDone request; value of command field is 'configurationDone'. - The client of the debug protocol must send this request at the end of the sequence of configuration requests (which was started by the 'initialized' event). + This optional request indicates that the client has finished initialization of the debug adapter. + So it is the last request in the sequence of configuration requests (which was started by the 'initialized' event). + Clients should only call this request if the capability 'supportsConfigurationDoneRequest' is true. */ export interface ConfigurationDoneRequest extends Request { // command: 'configurationDone'; @@ -395,7 +476,8 @@ declare module DebugProtocol { } /** Launch request; value of command field is 'launch'. - The launch request is sent from the client to the debug adapter to start the debuggee with or without debugging (if 'noDebug' is true). Since launching is debugger/runtime specific, the arguments for this request are not part of this specification. + This launch request is sent from the client to the debug adapter to start the debuggee with or without debugging (if 'noDebug' is true). + Since launching is debugger/runtime specific, the arguments for this request are not part of this specification. */ export interface LaunchRequest extends Request { // command: 'launch'; @@ -418,7 +500,8 @@ declare module DebugProtocol { } /** Attach request; value of command field is 'attach'. - The attach request is sent from the client to the debug adapter to attach to a debuggee that is already running. Since attaching is debugger/runtime specific, the arguments for this request are not part of this specification. + The attach request is sent from the client to the debug adapter to attach to a debuggee that is already running. + Since attaching is debugger/runtime specific, the arguments for this request are not part of this specification. */ export interface AttachRequest extends Request { // command: 'attach'; @@ -439,10 +522,8 @@ declare module DebugProtocol { } /** Restart request; value of command field is 'restart'. - Restarts a debug session. If the capability 'supportsRestartRequest' is missing or has the value false, - the client will implement 'restart' by terminating the debug adapter first and then launching it anew. - A debug adapter can override this default behaviour by implementing a restart request - and setting the capability 'supportsRestartRequest' to true. + Restarts a debug session. Clients should only call this request if the capability 'supportsRestartRequest' is true. + If the capability is missing or has the value false, a typical client will emulate 'restart' by terminating the debug adapter first and then launching it anew. */ export interface RestartRequest extends Request { // command: 'restart'; @@ -458,7 +539,11 @@ declare module DebugProtocol { } /** Disconnect request; value of command field is 'disconnect'. - The 'disconnect' request is sent from the client to the debug adapter in order to stop debugging. It asks the debug adapter to disconnect from the debuggee and to terminate the debug adapter. If the debuggee has been started with the 'launch' request, the 'disconnect' request terminates the debuggee. If the 'attach' request was used to connect to the debuggee, 'disconnect' does not terminate the debuggee. This behavior can be controlled with the 'terminateDebuggee' argument (if supported by the debug adapter). + The 'disconnect' request is sent from the client to the debug adapter in order to stop debugging. + It asks the debug adapter to disconnect from the debuggee and to terminate the debug adapter. + If the debuggee has been started with the 'launch' request, the 'disconnect' request terminates the debuggee. + If the 'attach' request was used to connect to the debuggee, 'disconnect' does not terminate the debuggee. + This behavior can be controlled with the 'terminateDebuggee' argument (if supported by the debug adapter). */ export interface DisconnectRequest extends Request { // command: 'disconnect'; @@ -471,7 +556,7 @@ declare module DebugProtocol { restart?: boolean; /** Indicates whether the debuggee should be terminated when the debugger is disconnected. If unspecified, the debug adapter is free to do whatever it thinks is best. - A client can only rely on this attribute being properly honored if a debug adapter returns true for the 'supportTerminateDebuggee' capability. + The attribute is only honored by a debug adapter if the capability 'supportTerminateDebuggee' is true. */ terminateDebuggee?: boolean; } @@ -482,6 +567,7 @@ declare module DebugProtocol { /** Terminate request; value of command field is 'terminate'. The 'terminate' request is sent from the client to the debug adapter in order to give the debuggee a chance for terminating itself. + Clients should only call this request if the capability 'supportsTerminateRequest' is true. */ export interface TerminateRequest extends Request { // command: 'terminate'; @@ -500,6 +586,7 @@ declare module DebugProtocol { /** BreakpointLocations request; value of command field is 'breakpointLocations'. The 'breakpointLocations' request returns all possible locations for source breakpoints in a given range. + Clients should only call this request if the capability 'supportsBreakpointLocationsRequest' is true. */ export interface BreakpointLocationsRequest extends Request { // command: 'breakpointLocations'; @@ -560,7 +647,9 @@ declare module DebugProtocol { */ export interface SetBreakpointsResponse extends Response { body: { - /** Information about the breakpoints. The array elements are in the same order as the elements of the 'breakpoints' (or the deprecated 'lines') array in the arguments. */ + /** Information about the breakpoints. + The array elements are in the same order as the elements of the 'breakpoints' (or the deprecated 'lines') array in the arguments. + */ breakpoints: Breakpoint[]; }; } @@ -569,6 +658,7 @@ declare module DebugProtocol { Replaces all existing function breakpoints with new function breakpoints. To clear all function breakpoints, specify an empty array. When a function breakpoint is hit, a 'stopped' event (with reason 'function breakpoint') is generated. + Clients should only call this request if the capability 'supportsFunctionBreakpoints' is true. */ export interface SetFunctionBreakpointsRequest extends Request { // command: 'setFunctionBreakpoints'; @@ -592,7 +682,9 @@ declare module DebugProtocol { } /** SetExceptionBreakpoints request; value of command field is 'setExceptionBreakpoints'. - The request configures the debuggers response to thrown exceptions. If an exception is configured to break, a 'stopped' event is fired (with reason 'exception'). + The request configures the debuggers response to thrown exceptions. + If an exception is configured to break, a 'stopped' event is fired (with reason 'exception'). + Clients should only call this request if the capability 'exceptionBreakpointFilters' returns one or more filters. */ export interface SetExceptionBreakpointsRequest extends Request { // command: 'setExceptionBreakpoints'; @@ -603,7 +695,9 @@ declare module DebugProtocol { export interface SetExceptionBreakpointsArguments { /** IDs of checked exception options. The set of IDs is returned via the 'exceptionBreakpointFilters' capability. */ filters: string[]; - /** Configuration options for selected exceptions. */ + /** Configuration options for selected exceptions. + The attribute is only honored by a debug adapter if the capability 'supportsExceptionOptions' is true. + */ exceptionOptions?: ExceptionOptions[]; } @@ -613,6 +707,7 @@ declare module DebugProtocol { /** DataBreakpointInfo request; value of command field is 'dataBreakpointInfo'. Obtains information on a possible data breakpoint that could be set on an expression or variable. + Clients should only call this request if the capability 'supportsDataBreakpoints' is true. */ export interface DataBreakpointInfoRequest extends Request { // command: 'dataBreakpointInfo'; @@ -623,7 +718,9 @@ declare module DebugProtocol { export interface DataBreakpointInfoArguments { /** Reference to the Variable container if the data breakpoint is requested for a child of the container. */ variablesReference?: number; - /** The name of the Variable's child to obtain data breakpoint information for. If variableReference isn’t provided, this can be an expression. */ + /** The name of the Variable's child to obtain data breakpoint information for. + If variableReference isn’t provided, this can be an expression. + */ name: string; } @@ -645,6 +742,7 @@ declare module DebugProtocol { Replaces all existing data breakpoints with new data breakpoints. To clear all data breakpoints, specify an empty array. When a data breakpoint is hit, a 'stopped' event (with reason 'data breakpoint') is generated. + Clients should only call this request if the capability 'supportsDataBreakpoints' is true. */ export interface SetDataBreakpointsRequest extends Request { // command: 'setDataBreakpoints'; @@ -677,14 +775,18 @@ declare module DebugProtocol { /** Arguments for 'continue' request. */ export interface ContinueArguments { - /** Continue execution for the specified thread (if possible). If the backend cannot continue on a single thread but will continue on all threads, it should set the 'allThreadsContinued' attribute in the response to true. */ + /** Continue execution for the specified thread (if possible). + If the backend cannot continue on a single thread but will continue on all threads, it should set the 'allThreadsContinued' attribute in the response to true. + */ threadId: number; } /** Response to 'continue' request. */ export interface ContinueResponse extends Response { body: { - /** If true, the 'continue' request has ignored the specified thread and continued all threads instead. If this attribute is missing a value of 'true' is assumed for backward compatibility. */ + /** If true, the 'continue' request has ignored the specified thread and continued all threads instead. + If this attribute is missing a value of 'true' is assumed for backward compatibility. + */ allThreadsContinued?: boolean; }; } @@ -754,7 +856,8 @@ declare module DebugProtocol { /** StepBack request; value of command field is 'stepBack'. The request starts the debuggee to run one step backwards. - The debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed. Clients should only call this request if the capability 'supportsStepBack' is true. + The debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed. + Clients should only call this request if the capability 'supportsStepBack' is true. */ export interface StepBackRequest extends Request { // command: 'stepBack'; @@ -772,7 +875,8 @@ declare module DebugProtocol { } /** ReverseContinue request; value of command field is 'reverseContinue'. - The request starts the debuggee to run backward. Clients should only call this request if the capability 'supportsStepBack' is true. + The request starts the debuggee to run backward. + Clients should only call this request if the capability 'supportsStepBack' is true. */ export interface ReverseContinueRequest extends Request { // command: 'reverseContinue'; @@ -792,6 +896,7 @@ declare module DebugProtocol { /** RestartFrame request; value of command field is 'restartFrame'. The request restarts execution of the specified stackframe. The debug adapter first sends the response and then a 'stopped' event (with reason 'restart') after the restart has completed. + Clients should only call this request if the capability 'supportsRestartFrame' is true. */ export interface RestartFrameRequest extends Request { // command: 'restartFrame'; @@ -813,6 +918,7 @@ declare module DebugProtocol { This makes it possible to skip the execution of code or to executed code again. The code between the current location and the goto target is not executed but skipped. The debug adapter first sends the response and then a 'stopped' event with reason 'goto'. + Clients should only call this request if the capability 'supportsGotoTargetsRequest' is true (because only then goto targets exist that can be passed as arguments). */ export interface GotoRequest extends Request { // command: 'goto'; @@ -866,7 +972,9 @@ declare module DebugProtocol { startFrame?: number; /** The maximum number of frames to return. If levels is not specified or 0, all frames are returned. */ levels?: number; - /** Specifies details on how to format the stack frames. */ + /** Specifies details on how to format the stack frames. + The attribute is only honored by a debug adapter if the capability 'supportsValueFormattingOptions' is true. + */ format?: StackFrameFormat; } @@ -923,7 +1031,9 @@ declare module DebugProtocol { start?: number; /** The number of variables to return. If count is missing or 0, all variables are returned. */ count?: number; - /** Specifies details on how to format the Variable values. */ + /** Specifies details on how to format the Variable values. + The attribute is only honored by a debug adapter if the capability 'supportsValueFormattingOptions' is true. + */ format?: ValueFormat; } @@ -936,7 +1046,7 @@ declare module DebugProtocol { } /** SetVariable request; value of command field is 'setVariable'. - Set the variable with the given name in the variable container to a new value. + Set the variable with the given name in the variable container to a new value. Clients should only call this request if the capability 'supportsSetVariable' is true. */ export interface SetVariableRequest extends Request { // command: 'setVariable'; @@ -962,14 +1072,18 @@ declare module DebugProtocol { value: string; /** The type of the new value. Typically shown in the UI when hovering over the value. */ type?: string; - /** If variablesReference is > 0, the new value is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. The value should be less than or equal to 2147483647 (2^31 - 1). */ + /** If variablesReference is > 0, the new value is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. + The value should be less than or equal to 2147483647 (2^31 - 1). + */ variablesReference?: number; /** The number of named child variables. - The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. + The value should be less than or equal to 2147483647 (2^31 - 1). */ namedVariables?: number; /** The number of indexed child variables. - The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. + The value should be less than or equal to 2147483647 (2^31 - 1). */ indexedVariables?: number; }; @@ -987,7 +1101,9 @@ declare module DebugProtocol { export interface SourceArguments { /** Specifies the source content to load. Either source.path or source.sourceReference must be specified. */ source?: Source; - /** The reference to the source. This is the same as source.sourceReference. This is provided for backward compatibility since old backends do not understand the 'source' attribute. */ + /** The reference to the source. This is the same as source.sourceReference. + This is provided for backward compatibility since old backends do not understand the 'source' attribute. + */ sourceReference: number; } @@ -1018,6 +1134,7 @@ declare module DebugProtocol { /** TerminateThreads request; value of command field is 'terminateThreads'. The request terminates the threads with the given ids. + Clients should only call this request if the capability 'supportsTerminateThreadsRequest' is true. */ export interface TerminateThreadsRequest extends Request { // command: 'terminateThreads'; @@ -1035,7 +1152,8 @@ declare module DebugProtocol { } /** Modules request; value of command field is 'modules'. - Modules can be retrieved from the debug adapter with the ModulesRequest which can either return all modules or a range of modules to support paging. + Modules can be retrieved from the debug adapter with this request which can either return all modules or a range of modules to support paging. + Clients should only call this request if the capability 'supportsModulesRequest' is true. */ export interface ModulesRequest extends Request { // command: 'modules'; @@ -1062,6 +1180,7 @@ declare module DebugProtocol { /** LoadedSources request; value of command field is 'loadedSources'. Retrieves the set of all sources currently loaded by the debugged process. + Clients should only call this request if the capability 'supportsLoadedSourcesRequest' is true. */ export interface LoadedSourcesRequest extends Request { // command: 'loadedSources'; @@ -1100,10 +1219,14 @@ declare module DebugProtocol { 'watch': evaluate is run in a watch. 'repl': evaluate is run from REPL console. 'hover': evaluate is run from a data hover. + 'clipboard': evaluate is run to generate the value that will be stored in the clipboard. + The attribute is only honored by a debug adapter if the capability 'supportsClipboardContext' is true. etc. */ context?: string; - /** Specifies details on how to format the Evaluate result. */ + /** Specifies details on how to format the Evaluate result. + The attribute is only honored by a debug adapter if the capability 'supportsValueFormattingOptions' is true. + */ format?: ValueFormat; } @@ -1112,21 +1235,30 @@ declare module DebugProtocol { body: { /** The result of the evaluate request. */ result: string; - /** The optional type of the evaluate result. */ + /** The optional type of the evaluate result. + This attribute should only be returned by a debug adapter if the client has passed the value true for the 'supportsVariableType' capability of the 'initialize' request. + */ type?: string; /** Properties of a evaluate result that can be used to determine how to render the result in the UI. */ presentationHint?: VariablePresentationHint; - /** If variablesReference is > 0, the evaluate result is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. The value should be less than or equal to 2147483647 (2^31 - 1). */ + /** If variablesReference is > 0, the evaluate result is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. + The value should be less than or equal to 2147483647 (2^31 - 1). + */ variablesReference: number; /** The number of named child variables. - The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. + The value should be less than or equal to 2147483647 (2^31 - 1). */ namedVariables?: number; /** The number of indexed child variables. - The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. + The value should be less than or equal to 2147483647 (2^31 - 1). */ indexedVariables?: number; - /** Memory reference to a location appropriate for this result. For pointer type eval results, this is generally a reference to the memory address contained in the pointer. */ + /** Optional memory reference to a location appropriate for this result. + For pointer type eval results, this is generally a reference to the memory address contained in the pointer. + This attribute should be returned by a debug adapter if the client has passed the value true for the 'supportsMemoryReferences' capability of the 'initialize' request. + */ memoryReference?: string; }; } @@ -1134,6 +1266,7 @@ declare module DebugProtocol { /** SetExpression request; value of command field is 'setExpression'. Evaluates the given 'value' expression and assigns it to the 'expression' which must be a modifiable l-value. The expressions have access to any variables and arguments that are in scope of the specified frame. + Clients should only call this request if the capability 'supportsSetExpression' is true. */ export interface SetExpressionRequest extends Request { // command: 'setExpression'; @@ -1157,18 +1290,24 @@ declare module DebugProtocol { body: { /** The new value of the expression. */ value: string; - /** The optional type of the value. */ + /** The optional type of the value. + This attribute should only be returned by a debug adapter if the client has passed the value true for the 'supportsVariableType' capability of the 'initialize' request. + */ type?: string; /** Properties of a value that can be used to determine how to render the result in the UI. */ presentationHint?: VariablePresentationHint; - /** If variablesReference is > 0, the value is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. The value should be less than or equal to 2147483647 (2^31 - 1). */ + /** If variablesReference is > 0, the value is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. + The value should be less than or equal to 2147483647 (2^31 - 1). + */ variablesReference?: number; /** The number of named child variables. - The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. + The value should be less than or equal to 2147483647 (2^31 - 1). */ namedVariables?: number; /** The number of indexed child variables. - The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. + The value should be less than or equal to 2147483647 (2^31 - 1). */ indexedVariables?: number; }; @@ -1178,6 +1317,7 @@ declare module DebugProtocol { This request retrieves the possible stepIn targets for the specified stack frame. These targets can be used in the 'stepIn' request. The StepInTargets may only be called if the 'supportsStepInTargetsRequest' capability exists and is true. + Clients should only call this request if the capability 'supportsStepInTargetsRequest' is true. */ export interface StepInTargetsRequest extends Request { // command: 'stepInTargets'; @@ -1201,7 +1341,7 @@ declare module DebugProtocol { /** GotoTargets request; value of command field is 'gotoTargets'. This request retrieves the possible goto targets for the specified source location. These targets can be used in the 'goto' request. - The GotoTargets request may only be called if the 'supportsGotoTargetsRequest' capability exists and is true. + Clients should only call this request if the capability 'supportsGotoTargetsRequest' is true. */ export interface GotoTargetsRequest extends Request { // command: 'gotoTargets'; @@ -1228,7 +1368,7 @@ declare module DebugProtocol { /** Completions request; value of command field is 'completions'. Returns a list of possible completions for a given caret position and text. - The CompletionsRequest may only be called if the 'supportsCompletionsRequest' capability exists and is true. + Clients should only call this request if the capability 'supportsCompletionsRequest' is true. */ export interface CompletionsRequest extends Request { // command: 'completions'; @@ -1257,6 +1397,7 @@ declare module DebugProtocol { /** ExceptionInfo request; value of command field is 'exceptionInfo'. Retrieves the details of the exception that caused this event to be raised. + Clients should only call this request if the capability 'supportsExceptionInfoRequest' is true. */ export interface ExceptionInfoRequest extends Request { // command: 'exceptionInfo'; @@ -1285,6 +1426,7 @@ declare module DebugProtocol { /** ReadMemory request; value of command field is 'readMemory'. Reads bytes from memory at the provided location. + Clients should only call this request if the capability 'supportsReadMemoryRequest' is true. */ export interface ReadMemoryRequest extends Request { // command: 'readMemory'; @@ -1304,9 +1446,13 @@ declare module DebugProtocol { /** Response to 'readMemory' request. */ export interface ReadMemoryResponse extends Response { body?: { - /** The address of the first byte of data returned. Treated as a hex value if prefixed with '0x', or as a decimal value otherwise. */ + /** The address of the first byte of data returned. + Treated as a hex value if prefixed with '0x', or as a decimal value otherwise. + */ address: string; - /** The number of unreadable bytes encountered after the last successfully read byte. This can be used to determine the number of bytes that must be skipped before a subsequent 'readMemory' request will succeed. */ + /** The number of unreadable bytes encountered after the last successfully read byte. + This can be used to determine the number of bytes that must be skipped before a subsequent 'readMemory' request will succeed. + */ unreadableBytes?: number; /** The bytes read from memory, encoded using base64. */ data?: string; @@ -1315,6 +1461,7 @@ declare module DebugProtocol { /** Disassemble request; value of command field is 'disassemble'. Disassembles code stored at the provided location. + Clients should only call this request if the capability 'supportsDisassembleRequest' is true. */ export interface DisassembleRequest extends Request { // command: 'disassemble'; @@ -1329,7 +1476,9 @@ declare module DebugProtocol { offset?: number; /** Optional offset (in instructions) to be applied after the byte offset (if any) before disassembling. Can be negative. */ instructionOffset?: number; - /** Number of instructions to disassemble starting at the specified location and offset. An adapter must return exactly this number of instructions - any unavailable instructions should be replaced with an implementation-defined 'invalid instruction' value. */ + /** Number of instructions to disassemble starting at the specified location and offset. + An adapter must return exactly this number of instructions - any unavailable instructions should be replaced with an implementation-defined 'invalid instruction' value. + */ instructionCount: number; /** If true, the adapter should attempt to resolve memory addresses and other values to symbolic names. */ resolveSymbols?: boolean; @@ -1409,6 +1558,8 @@ declare module DebugProtocol { supportsCancelRequest?: boolean; /** The debug adapter supports the 'breakpointLocations' request. */ supportsBreakpointLocationsRequest?: boolean; + /** The debug adapter supports the 'clipboard' context value in the 'evaluate' request. */ + supportsClipboardContext?: boolean; } /** An ExceptionBreakpointsFilter is shown in the UI as an option for configuring how exceptions are dealt with. */ @@ -1477,7 +1628,8 @@ declare module DebugProtocol { addressRange?: string; } - /** A ColumnDescriptor specifies what module attribute to show in a column of the ModulesView, how to format it, and what the column's label should be. + /** A ColumnDescriptor specifies what module attribute to show in a column of the ModulesView, how to format it, + and what the column's label should be. It is only used if the underlying UI actually supports this level of customization. */ export interface ColumnDescriptor { @@ -1508,21 +1660,34 @@ declare module DebugProtocol { name: string; } - /** A Source is a descriptor for source code. It is returned from the debug adapter as part of a StackFrame and it is used by clients when specifying breakpoints. */ + /** A Source is a descriptor for source code. + It is returned from the debug adapter as part of a StackFrame and it is used by clients when specifying breakpoints. + */ export interface Source { - /** The short name of the source. Every source returned from the debug adapter has a name. When sending a source to the debug adapter this name is optional. */ + /** The short name of the source. Every source returned from the debug adapter has a name. + When sending a source to the debug adapter this name is optional. + */ name?: string; - /** The path of the source to be shown in the UI. It is only used to locate and load the content of the source if no sourceReference is specified (or its value is 0). */ + /** The path of the source to be shown in the UI. + It is only used to locate and load the content of the source if no sourceReference is specified (or its value is 0). + */ path?: string; - /** If sourceReference > 0 the contents of the source must be retrieved through the SourceRequest (even if a path is specified). A sourceReference is only valid for a session, so it must not be used to persist a source. The value should be less than or equal to 2147483647 (2^31 - 1). */ + /** If sourceReference > 0 the contents of the source must be retrieved through the SourceRequest (even if a path is specified). + A sourceReference is only valid for a session, so it must not be used to persist a source. + The value should be less than or equal to 2147483647 (2^31 - 1). + */ sourceReference?: number; - /** An optional hint for how to present the source in the UI. A value of 'deemphasize' can be used to indicate that the source is not available or that it is skipped on stepping. */ + /** An optional hint for how to present the source in the UI. + A value of 'deemphasize' can be used to indicate that the source is not available or that it is skipped on stepping. + */ presentationHint?: 'normal' | 'emphasize' | 'deemphasize'; /** The (optional) origin of this source: possible values 'internal module', 'inlined content from source map', etc. */ origin?: string; /** An optional list of sources that are related to this source. These may be the source that generated this source. */ sources?: Source[]; - /** Optional data that a debug adapter might want to loop through the client. The client should leave the data intact and persist it across sessions. The client should not interpret the data. */ + /** Optional data that a debug adapter might want to loop through the client. + The client should leave the data intact and persist it across sessions. The client should not interpret the data. + */ adapterData?: any; /** The checksums associated with this file. */ checksums?: Checksum[]; @@ -1530,7 +1695,9 @@ declare module DebugProtocol { /** A Stackframe contains the source location. */ export interface StackFrame { - /** An identifier for the stack frame. It must be unique across all threads. This id can be used to retrieve the scopes of the frame with the 'scopesRequest' or to restart the execution of a stackframe. */ + /** An identifier for the stack frame. It must be unique across all threads. + This id can be used to retrieve the scopes of the frame with the 'scopesRequest' or to restart the execution of a stackframe. + */ id: number; /** The name of the stack frame, typically a method name. */ name: string; @@ -1548,7 +1715,9 @@ declare module DebugProtocol { instructionPointerReference?: string; /** The module associated with this frame, if any. */ moduleId?: number | string; - /** An optional hint for how to present this frame in the UI. A value of 'label' can be used to indicate that the frame is an artificial frame that is used as a visual label or separator. A value of 'subtle' can be used to change the appearance of a frame in a 'subtle' way. */ + /** An optional hint for how to present this frame in the UI. + A value of 'label' can be used to indicate that the frame is an artificial frame that is used as a visual label or separator. A value of 'subtle' can be used to change the appearance of a frame in a 'subtle' way. + */ presentationHint?: 'normal' | 'label' | 'subtle'; } @@ -1600,7 +1769,9 @@ declare module DebugProtocol { name: string; /** The variable's value. This can be a multi-line text, e.g. for a function the body of a function. */ value: string; - /** The type of the variable's value. Typically shown in the UI when hovering over the value. */ + /** The type of the variable's value. Typically shown in the UI when hovering over the value. + This attribute should only be returned by a debug adapter if the client has passed the value true for the 'supportsVariableType' capability of the 'initialize' request. + */ type?: string; /** Properties of a variable that can be used to determine how to render the variable in the UI. */ presentationHint?: VariablePresentationHint; @@ -1616,7 +1787,9 @@ declare module DebugProtocol { The client can use this optional information to present the children in a paged UI and fetch them in chunks. */ indexedVariables?: number; - /** Optional memory reference for the variable if the variable represents executable code, such as a function pointer. */ + /** Optional memory reference for the variable if the variable represents executable code, such as a function pointer. + This attribute is only required if the client has passed the value true for the 'supportsMemoryReferences' capability of the 'initialize' request. + */ memoryReference?: string; } @@ -1633,7 +1806,8 @@ declare module DebugProtocol { 'innerClass': Indicates that the object is an inner class. 'interface': Indicates that the object is an interface. 'mostDerivedClass': Indicates that the object is the most derived class. - 'virtual': Indicates that the object is virtual, that means it is a synthetic object introduced by the adapter for rendering purposes, e.g. an index range for large arrays. + 'virtual': Indicates that the object is virtual, that means it is a synthetic object introducedby the + adapter for rendering purposes, e.g. an index range for large arrays. 'dataBreakpoint': Indicates that a data breakpoint is registered for the object. etc. */ @@ -1674,11 +1848,19 @@ declare module DebugProtocol { line: number; /** An optional source column of the breakpoint. */ column?: number; - /** An optional expression for conditional breakpoints. */ + /** An optional expression for conditional breakpoints. + It is only honored by a debug adapter if the capability 'supportsConditionalBreakpoints' is true. + */ condition?: string; - /** An optional expression that controls how many hits of the breakpoint are ignored. The backend is expected to interpret the expression as needed. */ + /** An optional expression that controls how many hits of the breakpoint are ignored. + The backend is expected to interpret the expression as needed. + The attribute is only honored by a debug adapter if the capability 'supportsHitConditionalBreakpoints' is true. + */ hitCondition?: string; - /** If this attribute exists and is non-empty, the backend must not 'break' (stop) but log the message instead. Expressions within {} are interpolated. */ + /** If this attribute exists and is non-empty, the backend must not 'break' (stop) + but log the message instead. Expressions within {} are interpolated. + The attribute is only honored by a debug adapter if the capability 'supportsLogPoints' is true. + */ logMessage?: string; } @@ -1686,9 +1868,14 @@ declare module DebugProtocol { export interface FunctionBreakpoint { /** The name of the function. */ name: string; - /** An optional expression for conditional breakpoints. */ + /** An optional expression for conditional breakpoints. + It is only honored by a debug adapter if the capability 'supportsConditionalBreakpoints' is true. + */ condition?: string; - /** An optional expression that controls how many hits of the breakpoint are ignored. The backend is expected to interpret the expression as needed. */ + /** An optional expression that controls how many hits of the breakpoint are ignored. + The backend is expected to interpret the expression as needed. + The attribute is only honored by a debug adapter if the capability 'supportsHitConditionalBreakpoints' is true. + */ hitCondition?: string; } @@ -1703,7 +1890,9 @@ declare module DebugProtocol { accessType?: DataBreakpointAccessType; /** An optional expression for conditional breakpoints. */ condition?: string; - /** An optional expression that controls how many hits of the breakpoint are ignored. The backend is expected to interpret the expression as needed. */ + /** An optional expression that controls how many hits of the breakpoint are ignored. + The backend is expected to interpret the expression as needed. + */ hitCondition?: string; } @@ -1713,7 +1902,9 @@ declare module DebugProtocol { id?: number; /** If true breakpoint could be set (but not necessarily at the desired location). */ verified: boolean; - /** An optional message about the state of the breakpoint. This is shown to the user and can be used to explain why a breakpoint could not be verified. */ + /** An optional message about the state of the breakpoint. + This is shown to the user and can be used to explain why a breakpoint could not be verified. + */ message?: string; /** The source where the breakpoint is located. */ source?: Source; @@ -1723,7 +1914,9 @@ declare module DebugProtocol { column?: number; /** An optional end line of the actual range covered by the breakpoint. */ endLine?: number; - /** An optional end column of the actual range covered by the breakpoint. If no end line is given, then the end column is assumed to be in the start line. */ + /** An optional end column of the actual range covered by the breakpoint. + If no end line is given, then the end column is assumed to be in the start line. + */ endColumn?: number; } @@ -1825,7 +2018,9 @@ declare module DebugProtocol { /** An ExceptionOptions assigns configuration options to a set of exceptions. */ export interface ExceptionOptions { - /** A path that selects a single or multiple exceptions in a tree. If 'path' is missing, the whole tree is selected. By convention the first segment of the path is a category that is used to group exceptions in the UI. */ + /** A path that selects a single or multiple exceptions in a tree. If 'path' is missing, the whole tree is selected. + By convention the first segment of the path is a category that is used to group exceptions in the UI. + */ path?: ExceptionPathSegment[]; /** Condition when a thrown exception should result in a break. */ breakMode: ExceptionBreakMode; @@ -1839,7 +2034,10 @@ declare module DebugProtocol { */ export type ExceptionBreakMode = 'never' | 'always' | 'unhandled' | 'userUnhandled'; - /** An ExceptionPathSegment represents a segment in a path that is used to match leafs or nodes in a tree of exceptions. If a segment consists of more than one name, it matches the names provided if 'negate' is false or missing or it matches anything except the names provided if 'negate' is true. */ + /** An ExceptionPathSegment represents a segment in a path that is used to match leafs or nodes in a tree of exceptions. + If a segment consists of more than one name, it matches the names provided if 'negate' is false or missing or + it matches anything except the names provided if 'negate' is true. + */ export interface ExceptionPathSegment { /** If false or missing this segment matches the names provided, otherwise it matches anything except the names provided. */ negate?: boolean; @@ -1873,7 +2071,10 @@ declare module DebugProtocol { instruction: string; /** Name of the symbol that corresponds with the location of this instruction, if any. */ symbol?: string; - /** Source location that corresponds to this instruction, if any. Should always be set (if available) on the first instruction returned, but can be omitted afterwards if this instruction maps to the same source file as the previous instruction. */ + /** Source location that corresponds to this instruction, if any. + Should always be set (if available) on the first instruction returned, + but can be omitted afterwards if this instruction maps to the same source file as the previous instruction. + */ location?: Source; /** The line within the source location that corresponds to this instruction, if any. */ line?: number; diff --git a/src/vs/workbench/contrib/debug/common/debugSource.ts b/src/vs/workbench/contrib/debug/common/debugSource.ts index ca090ad6399..c16cf5295ee 100644 --- a/src/vs/workbench/contrib/debug/common/debugSource.ts +++ b/src/vs/workbench/contrib/debug/common/debugSource.ts @@ -12,7 +12,7 @@ import { IRange } from 'vs/editor/common/core/range'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { Schemas } from 'vs/base/common/network'; import { isUri } from 'vs/workbench/contrib/debug/common/debugUtils'; -import { ITextEditor } from 'vs/workbench/common/editor'; +import { ITextEditorPane } from 'vs/workbench/common/editor'; import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; export const UNKNOWN_SOURCE_LABEL = nls.localize('unknownSource', "Unknown Source"); @@ -71,7 +71,7 @@ export class Source { return this.uri.scheme === DEBUG_SCHEME; } - openInEditor(editorService: IEditorService, selection: IRange, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { + openInEditor(editorService: IEditorService, selection: IRange, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { return !this.available ? Promise.resolve(undefined) : editorService.openEditor({ resource: this.uri, description: this.origin, diff --git a/src/vs/workbench/contrib/debug/common/debugUtils.ts b/src/vs/workbench/contrib/debug/common/debugUtils.ts index 2c25261bd41..e899fb7bd90 100644 --- a/src/vs/workbench/contrib/debug/common/debugUtils.ts +++ b/src/vs/workbench/contrib/debug/common/debugUtils.ts @@ -128,10 +128,12 @@ function stringToUri(source: PathContainer): string | undefined { function uriToString(source: PathContainer): string | undefined { if (typeof source.path === 'object') { const u = uri.revive(source.path); - if (u.scheme === 'file') { - return u.fsPath; - } else { - return u.toString(); + if (u) { + if (u.scheme === 'file') { + return u.fsPath; + } else { + return u.toString(); + } } } return source.path; @@ -245,6 +247,9 @@ function convertPaths(msg: DebugProtocol.ProtocolMessage, fixSourcePath: (toDA: export function getVisibleAndSorted(array: T[]): T[] { return array.filter(config => !config.presentation?.hidden).sort((first, second) => { if (!first.presentation) { + if (!second.presentation) { + return 0; + } return 1; } if (!second.presentation) { @@ -269,6 +274,10 @@ export function getVisibleAndSorted { if (!externalTerminalService) { if (env.isWindows) { externalTerminalService = new WindowsExternalTerminalService(undefined); @@ -20,12 +20,12 @@ export function runInExternalTerminal(args: DebugProtocol.RunInTerminalRequestAr externalTerminalService = new MacExternalTerminalService(undefined); } else if (env.isLinux) { externalTerminalService = new LinuxExternalTerminalService(undefined); + } else { + throw new Error('external terminals not supported on this platform'); } } - if (externalTerminalService) { - const config = configProvider.getConfiguration('terminal'); - externalTerminalService.runInTerminal(args.title!, args.cwd, args.args, args.env || {}, config.external || {}); - } + const config = configProvider.getConfiguration('terminal'); + return externalTerminalService.runInTerminal(args.title!, args.cwd, args.args, args.env || {}, config.external || {}); } function spawnAsPromised(command: string, args: string[]): Promise { @@ -164,7 +164,7 @@ export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments case ShellType.bash: quote = (s: string) => { - s = s.replace(/([\"\\])/g, '\\$1'); + s = s.replace(/(["';\\])/g, '\\$1'); return (s.indexOf(' ') >= 0 || s.length === 0) ? `"${s}"` : s; }; diff --git a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts index 83b69ec0ed7..732e78847c8 100644 --- a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts @@ -18,9 +18,10 @@ import { createBreakpointDecorations } from 'vs/workbench/contrib/debug/browser/ import { OverviewRulerLane } from 'vs/editor/common/model'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; +import { generateUuid } from 'vs/base/common/uuid'; function createMockSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession { - return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); + return new DebugSession(generateUuid(), { resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); } function addBreakpointsAndCheckEvents(model: DebugModel, uri: uri, data: IBreakpointData[]): void { diff --git a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts index 84b31372c66..608c0eac4f9 100644 --- a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts @@ -16,9 +16,10 @@ import { createDecorationsForStackFrame } from 'vs/workbench/contrib/debug/brows import { Constants } from 'vs/base/common/uint'; import { getContext, getContextForContributedActions } from 'vs/workbench/contrib/debug/browser/callStackView'; import { getStackFrameThreadAndSessionToFocus } from 'vs/workbench/contrib/debug/browser/debugService'; +import { generateUuid } from 'vs/base/common/uuid'; export function createMockSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession { - return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); + return new DebugSession(generateUuid(), { resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); } function createTwoStackFrames(session: DebugSession): { firstStackFrame: StackFrame, secondStackFrame: StackFrame } { @@ -363,7 +364,7 @@ suite('Debug - CallStack', () => { get state(): State { return State.Stopped; } - }({ resolved: { name: 'stoppedSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); + }(generateUuid(), { resolved: { name: 'stoppedSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); const runningSession = createMockSession(model); model.addSession(runningSession); diff --git a/src/vs/workbench/contrib/debug/test/common/debugUtils.test.ts b/src/vs/workbench/contrib/debug/test/common/debugUtils.test.ts index b61e16b4ebe..c588b76cf0e 100644 --- a/src/vs/workbench/contrib/debug/test/common/debugUtils.test.ts +++ b/src/vs/workbench/contrib/debug/test/common/debugUtils.test.ts @@ -17,6 +17,7 @@ suite('Debug - Utils', () => { assert.strictEqual(formatPII('Foo {0} Bar {1}{2}', false, { '0': 'yes', '1': 'undefined' }), 'Foo yes Bar undefined{2}'); assert.strictEqual(formatPII('Foo {_key0} Bar {key1}{key2}', true, { '_key0': 'yes', 'key1': '5', 'key2': 'false' }), 'Foo yes Bar {key1}{key2}'); assert.strictEqual(formatPII('Foo {_key0} Bar {key1}{key2}', false, { '_key0': 'yes', 'key1': '5', 'key2': 'false' }), 'Foo yes Bar 5false'); + assert.strictEqual(formatPII('Unable to display threads:"{e}"', false, { 'e': 'detached from process' }), 'Unable to display threads:"detached from process"'); }); test('getExactExpressionStartAndEnd', () => { diff --git a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts index 13f7b1a7e38..12e47d63cde 100644 --- a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts @@ -134,6 +134,10 @@ export class MockDebugService implements IDebugService { export class MockSession implements IDebugSession { + cancel(_progressId: string): Promise { + throw new Error('Method not implemented.'); + } + breakpointsLocations(uri: uri, lineNumber: number): Promise { throw new Error('Method not implemented.'); } @@ -222,6 +226,18 @@ export class MockSession implements IDebugSession { throw new Error('not implemented'); } + get onDidProgressStart(): Event { + throw new Error('not implemented'); + } + + get onDidProgressUpdate(): Event { + throw new Error('not implemented'); + } + + get onDidProgressEnd(): Event { + throw new Error('not implemented'); + } + setConfiguration(configuration: { resolved: IConfig, unresolved: IConfig }) { } getAllThreads(): IThread[] { diff --git a/src/vs/workbench/contrib/debug/test/electron-browser/debugANSIHandling.test.ts b/src/vs/workbench/contrib/debug/test/electron-browser/debugANSIHandling.test.ts index c7eba121141..2433e63552a 100644 --- a/src/vs/workbench/contrib/debug/test/electron-browser/debugANSIHandling.test.ts +++ b/src/vs/workbench/contrib/debug/test/electron-browser/debugANSIHandling.test.ts @@ -12,7 +12,7 @@ import { workbenchInstantiationService } from 'vs/workbench/test/browser/workben import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { Color, RGBA } from 'vs/base/common/color'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { TestThemeService, TestTheme } from 'vs/platform/theme/test/common/testThemeService'; +import { TestThemeService, TestColorTheme } from 'vs/platform/theme/test/common/testThemeService'; import { ansiColorMap } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { DebugModel } from 'vs/workbench/contrib/debug/common/debugModel'; import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession'; @@ -30,7 +30,7 @@ suite('Debug - ANSI Handling', () => { */ setup(() => { model = new DebugModel([], [], [], [], [], { isDirty: (e: any) => false }); - session = new DebugSession({ resolved: { name: 'test', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); + session = new DebugSession(generateUuid(), { resolved: { name: 'test', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); const instantiationService: TestInstantiationService = workbenchInstantiationService(); linkDetector = instantiationService.createInstance(LinkDetector); @@ -39,7 +39,7 @@ suite('Debug - ANSI Handling', () => { for (let color in ansiColorMap) { colors[color] = ansiColorMap[color].defaults.dark; } - const testTheme = new TestTheme(colors); + const testTheme = new TestColorTheme(colors); themeService = new TestThemeService(testTheme); }); diff --git a/src/vs/workbench/contrib/emmet/browser/actions/showEmmetCommands.ts b/src/vs/workbench/contrib/emmet/browser/actions/showEmmetCommands.ts index 1fe29bd1d93..1d931423323 100644 --- a/src/vs/workbench/contrib/emmet/browser/actions/showEmmetCommands.ts +++ b/src/vs/workbench/contrib/emmet/browser/actions/showEmmetCommands.ts @@ -6,10 +6,10 @@ import * as nls from 'vs/nls'; import { registerEditorAction, EditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { MenuId } from 'vs/platform/actions/common/actions'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; const EMMET_COMMANDS_PREFIX = '>Emmet: '; @@ -30,10 +30,9 @@ class ShowEmmetCommandsAction extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { - const quickOpenService = accessor.get(IQuickOpenService); - quickOpenService.show(EMMET_COMMANDS_PREFIX); - return Promise.resolve(undefined); + public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { + const quickInputService = accessor.get(IQuickInputService); + quickInputService.quickAccess.show(EMMET_COMMANDS_PREFIX); } } diff --git a/src/vs/workbench/contrib/experiments/browser/experimentalPrompt.ts b/src/vs/workbench/contrib/experiments/browser/experimentalPrompt.ts index 627af58fb9c..5b03888f831 100644 --- a/src/vs/workbench/contrib/experiments/browser/experimentalPrompt.ts +++ b/src/vs/workbench/contrib/experiments/browser/experimentalPrompt.ts @@ -13,6 +13,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { language } from 'vs/base/common/platform'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; +import { ICommandService } from 'vs/platform/commands/common/commands'; export class ExperimentalPrompts extends Disposable implements IWorkbenchContribution { @@ -21,7 +22,8 @@ export class ExperimentalPrompts extends Disposable implements IWorkbenchContrib @IViewletService private readonly viewletService: IViewletService, @INotificationService private readonly notificationService: INotificationService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IOpenerService private readonly openerService: IOpenerService + @IOpenerService private readonly openerService: IOpenerService, + @ICommandService private readonly commandService: ICommandService ) { super(); @@ -77,6 +79,8 @@ export class ExperimentalPrompts extends Disposable implements IWorkbenchContrib viewlet.search('curated:' + command.curatedExtensionsKey); } }); + } else if (command.codeCommand) { + this.commandService.executeCommand(command.codeCommand.id, ...command.codeCommand.arguments); } this.experimentService.markAsCompleted(experiment.id); @@ -93,7 +97,7 @@ export class ExperimentalPrompts extends Disposable implements IWorkbenchContrib }); } - static getLocalizedText(text: string | { [key: string]: string }, displayLanguage: string): string { + static getLocalizedText(text: string | { [key: string]: string; }, displayLanguage: string): string { if (typeof text === 'string') { return text; } diff --git a/src/vs/workbench/contrib/experiments/common/experimentService.ts b/src/vs/workbench/contrib/experiments/common/experimentService.ts index ba517275e98..4bb02853442 100644 --- a/src/vs/workbench/contrib/experiments/common/experimentService.ts +++ b/src/vs/workbench/contrib/experiments/common/experimentService.ts @@ -55,11 +55,16 @@ export interface IExperimentActionPromptCommand { externalLink?: string; curatedExtensionsKey?: string; curatedExtensionsList?: string[]; + codeCommand?: { + id: string; + arguments: unknown[]; + }; } export interface IExperiment { id: string; enabled: boolean; + raw: IRawExperiment | undefined; state: ExperimentState; action?: IExperimentAction; } @@ -88,7 +93,7 @@ interface IExperimentStorageState { * be incremented when adding a condition, otherwise experiments might activate * on older versions of VS Code where not intended. */ -export const currentSchemaVersion = 1; +export const currentSchemaVersion = 3; interface IRawExperiment { id: string; @@ -123,6 +128,7 @@ interface IRawExperiment { userProbability?: number; }; action?: IExperimentAction; + action2?: IExperimentAction; } interface IActivationEventRecord { @@ -228,7 +234,7 @@ export class ExperimentService extends Disposable implements IExperimentService if (context.res.statusCode !== 200) { return null; } - const result = await asJson<{ experiments?: IRawExperiment }>(context); + const result = await asJson<{ experiments?: IRawExperiment; }>(context); return result && Array.isArray(result.experiments) ? result.experiments : []; } catch (_e) { // Bad request or invalid JSON @@ -248,6 +254,7 @@ export class ExperimentService extends Disposable implements IExperimentService if (experimentState) { this._experiments.push({ id: experimentId, + raw: undefined, enabled: experimentState.enabled, state: experimentState.state }); @@ -286,57 +293,7 @@ export class ExperimentService extends Disposable implements IExperimentService })); } - const promises = rawExperiments.map(experiment => { - const processedExperiment: IExperiment = { - id: experiment.id, - enabled: !!experiment.enabled, - state: !!experiment.enabled ? ExperimentState.Evaluating : ExperimentState.NoRun - }; - - if (experiment.action) { - processedExperiment.action = { - type: ExperimentActionType[experiment.action.type] || ExperimentActionType.Custom, - properties: experiment.action.properties - }; - if (processedExperiment.action.type === ExperimentActionType.Prompt) { - ((processedExperiment.action.properties).commands || []).forEach(x => { - if (x.curatedExtensionsKey && Array.isArray(x.curatedExtensionsList)) { - this._curatedMapping[experiment.id] = x; - } - }); - } - if (!processedExperiment.action.properties) { - processedExperiment.action.properties = {}; - } - } - this._experiments.push(processedExperiment); - - if (!processedExperiment.enabled) { - return Promise.resolve(null); - } - - const storageKey = 'experiments.' + experiment.id; - const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {}); - if (!experimentState.hasOwnProperty('enabled')) { - experimentState.enabled = processedExperiment.enabled; - } - if (!experimentState.hasOwnProperty('state')) { - experimentState.state = processedExperiment.enabled ? ExperimentState.Evaluating : ExperimentState.NoRun; - } else { - processedExperiment.state = experimentState.state; - } - - return this.shouldRunExperiment(experiment, processedExperiment).then((state: ExperimentState) => { - experimentState.state = processedExperiment.state = state; - this.storageService.store(storageKey, JSON.stringify(experimentState), StorageScope.GLOBAL); - - if (state === ExperimentState.Run) { - this.fireRunExperiment(processedExperiment); - } - return Promise.resolve(null); - }); - - }); + const promises = rawExperiments.map(experiment => this.evaluateExperiment(experiment)); return Promise.all(promises).then(() => { type ExperimentsClassification = { experiments: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; }; @@ -346,6 +303,62 @@ export class ExperimentService extends Disposable implements IExperimentService }); } + private evaluateExperiment(experiment: IRawExperiment) { + const processedExperiment: IExperiment = { + id: experiment.id, + raw: experiment, + enabled: !!experiment.enabled, + state: !!experiment.enabled ? ExperimentState.Evaluating : ExperimentState.NoRun + }; + + const action = experiment.action2 || experiment.action; + if (action) { + processedExperiment.action = { + type: ExperimentActionType[action.type] || ExperimentActionType.Custom, + properties: action.properties + }; + if (processedExperiment.action.type === ExperimentActionType.Prompt) { + ((processedExperiment.action.properties).commands || []).forEach(x => { + if (x.curatedExtensionsKey && Array.isArray(x.curatedExtensionsList)) { + this._curatedMapping[experiment.id] = x; + } + }); + } + if (!processedExperiment.action.properties) { + processedExperiment.action.properties = {}; + } + } + + this._experiments = this._experiments.filter(e => e.id !== processedExperiment.id); + this._experiments.push(processedExperiment); + + if (!processedExperiment.enabled) { + return Promise.resolve(null); + } + + const storageKey = 'experiments.' + experiment.id; + const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {}); + if (!experimentState.hasOwnProperty('enabled')) { + experimentState.enabled = processedExperiment.enabled; + } + if (!experimentState.hasOwnProperty('state')) { + experimentState.state = processedExperiment.enabled ? ExperimentState.Evaluating : ExperimentState.NoRun; + } else { + processedExperiment.state = experimentState.state; + } + + return this.shouldRunExperiment(experiment, processedExperiment).then((state: ExperimentState) => { + experimentState.state = processedExperiment.state = state; + this.storageService.store(storageKey, JSON.stringify(experimentState), StorageScope.GLOBAL); + + if (state === ExperimentState.Run) { + this.fireRunExperiment(processedExperiment); + } + + return Promise.resolve(null); + }); + } + private fireRunExperiment(experiment: IExperiment) { this._onExperimentEnabled.fire(experiment); const runExperimentIdsFromStorage: string[] = safeParse(this.storageService.get('currentOrPreviouslyRunExperiments', StorageScope.GLOBAL), []); @@ -386,6 +399,10 @@ export class ExperimentService extends Disposable implements IExperimentService const record = getCurrentActivationRecord(safeParse(this.storageService.get(key, StorageScope.GLOBAL), undefined)); record.count[0]++; this.storageService.store(key, JSON.stringify(record), StorageScope.GLOBAL); + + this._experiments + .filter(e => e.state === ExperimentState.Evaluating && e.raw?.condition?.activationEvent?.event === event) + .forEach(e => this.evaluateExperiment(e.raw!)); } private checkActivationEventFrequency(experiment: IRawExperiment) { @@ -542,7 +559,7 @@ export class ExperimentService extends Disposable implements IExperimentService if (typeof latestExperimentState.editCount === 'number' && latestExperimentState.editCount >= fileEdits.minEditCount) { processedExperiment.state = latestExperimentState.state = (typeof condition.userProbability === 'number' && Math.random() < condition.userProbability && this.checkExperimentDependencies(experiment)) ? ExperimentState.Run : ExperimentState.NoRun; this.storageService.store(storageKey, JSON.stringify(latestExperimentState), StorageScope.GLOBAL); - if (latestExperimentState.state === ExperimentState.Run && experiment.action && ExperimentActionType[experiment.action.type] === ExperimentActionType.Prompt) { + if (latestExperimentState.state === ExperimentState.Run && processedExperiment.action && ExperimentActionType[processedExperiment.action.type] === ExperimentActionType.Prompt) { this.fireRunExperiment(processedExperiment); } } diff --git a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts index 9f9c72fd374..bcfb5090b7c 100644 --- a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts +++ b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; import { ExperimentActionType, ExperimentState, IExperiment, ExperimentService, getCurrentActivationRecord, currentSchemaVersion } from 'vs/workbench/contrib/experiments/common/experimentService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { TestLifecycleService, TestExtensionService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices'; import { IExtensionManagementService, DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -29,6 +29,8 @@ import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/ex import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { IProductService } from 'vs/platform/product/common/productService'; import { IWillActivateEvent, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { timeout } from 'vs/base/common/async'; +import { TestExtensionService } from 'vs/workbench/test/common/workbenchTestServices'; interface ExperimentSettings { enabled?: boolean; @@ -443,7 +445,7 @@ suite('Experiment Service', () => { condition: { activationEvent: { event: 'my:event', - minEvents: 5, + minEvents: 2, } } } @@ -467,12 +469,49 @@ suite('Experiment Service', () => { }); testObject = instantiationService.createInstance(TestExperimentService); - await testObject.getExperimentById('experiment1'); // ensure loaded + await testObject.getExperimentById('experiment1'); activationEvent.fire({ event: 'not our event', activation: Promise.resolve() }); activationEvent.fire({ event: 'my:event', activation: Promise.resolve() }); assert(didGetCall); }); + test('Activation events run experiments in realtime', async () => { + experimentData = { + experiments: [ + { + id: 'experiment1', + enabled: true, + condition: { + activationEvent: { + event: 'my:event', + minEvents: 2, + } + } + } + ] + }; + + let calls = 0; + instantiationService.stub(IStorageService, 'get', (a: string, b: StorageScope, c?: string) => { + return a === 'experimentEventRecord-my-event' + ? JSON.stringify({ count: [++calls, 0, 0, 0, 0, 0, 0], mostRecentBucket: Date.now() }) + : undefined; + }); + + const enabledListener = sinon.stub(); + testObject = instantiationService.createInstance(TestExperimentService); + testObject.onExperimentEnabled(enabledListener); + + assert.equal((await testObject.getExperimentById('experiment1')).state, ExperimentState.Evaluating); + assert.equal((await testObject.getExperimentById('experiment1')).state, ExperimentState.Evaluating); + assert.equal(enabledListener.callCount, 0); + + activationEvent.fire({ event: 'my:event', activation: Promise.resolve() }); + await timeout(1); + assert.equal(enabledListener.callCount, 1); + assert.equal((await testObject.getExperimentById('experiment1')).state, ExperimentState.Run); + }); + test('Experiment not matching user setting should be disabled', () => { experimentData = { experiments: [ @@ -730,6 +769,7 @@ suite('Experiment Service', () => { testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { assert.equal(result.enabled, false); + assert.equal(result.action?.type, 'Prompt'); assert.equal(result.state, ExperimentState.NoRun); return testObject.getCuratedExtensionsList(curatedExtensionsKey).then(curatedList => { assert.equal(curatedList.length, 0); @@ -737,6 +777,29 @@ suite('Experiment Service', () => { }); }); + test('Maps action2 to action.', () => { + experimentData = { + experiments: [ + { + id: 'experiment1', + enabled: false, + action2: { + type: 'Prompt', + properties: { + promptText: 'Hello world', + commands: [] + } + } + } + ] + }; + + testObject = instantiationService.createInstance(TestExperimentService); + return testObject.getExperimentById('experiment1').then(result => { + assert.equal(result.action?.type, 'Prompt'); + }); + }); + test('Experiment that is disabled or deleted should be removed from storage', () => { experimentData = { experiments: [ diff --git a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts index 5c0e4f90a94..474a88e30c5 100644 --- a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts +++ b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts @@ -16,19 +16,23 @@ import { ExperimentalPrompts } from 'vs/workbench/contrib/experiments/browser/ex import { ExperimentActionType, ExperimentState, IExperiment, IExperimentActionPromptProperties, IExperimentService, LocalizedPromptText } from 'vs/workbench/contrib/experiments/common/experimentService'; import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-browser/experimentService.test'; import { TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestCommandService } from 'vs/editor/test/browser/editorTestServices'; +import { ICommandService } from 'vs/platform/commands/common/commands'; suite('Experimental Prompts', () => { let instantiationService: TestInstantiationService; let experimentService: TestExperimentService; let experimentalPrompt: ExperimentalPrompts; + let commandService: TestCommandService; let onExperimentEnabledEvent: Emitter; - let storageData: { [key: string]: any } = {}; + let storageData: { [key: string]: any; } = {}; const promptText = 'Hello there! Can you see this?'; const experiment: IExperiment = { id: 'experiment1', enabled: true, + raw: undefined, state: ExperimentState.Run, action: { type: ExperimentActionType.Prompt, @@ -70,6 +74,8 @@ suite('Experimental Prompts', () => { experimentService = instantiationService.createInstance(TestExperimentService); experimentService.onExperimentEnabled = onExperimentEnabledEvent.event; instantiationService.stub(IExperimentService, experimentService); + commandService = instantiationService.createInstance(TestCommandService); + instantiationService.stub(ICommandService, commandService); }); teardown(() => { @@ -81,32 +87,6 @@ suite('Experimental Prompts', () => { } }); - - test('Show experimental prompt if experiment should be run. Choosing option with link should mark experiment as complete', () => { - - storageData = { - enabled: true, - state: ExperimentState.Run - }; - - instantiationService.stub(INotificationService, { - prompt: (a: Severity, b: string, c: IPromptChoice[], options: IPromptOptions) => { - assert.equal(b, promptText); - assert.equal(c.length, 2); - c[0].run(); - return undefined!; - } - }); - - experimentalPrompt = instantiationService.createInstance(ExperimentalPrompts); - onExperimentEnabledEvent.fire(experiment); - - return Promise.resolve(null).then(result => { - assert.equal(storageData['state'], ExperimentState.Complete); - }); - - }); - test('Show experimental prompt if experiment should be run. Choosing negative option should mark experiment as complete', () => { storageData = { @@ -132,6 +112,45 @@ suite('Experimental Prompts', () => { }); + test('runs experiment command', () => { + + storageData = { + enabled: true, + state: ExperimentState.Run + }; + + const stub = instantiationService.stub(ICommandService, 'executeCommand', () => undefined); + instantiationService.stub(INotificationService, { + prompt: (a: Severity, b: string, c: IPromptChoice[], options: IPromptOptions) => { + c[0].run(); + return undefined!; + } + }); + + experimentalPrompt = instantiationService.createInstance(ExperimentalPrompts); + onExperimentEnabledEvent.fire({ + ...experiment, + action: { + type: ExperimentActionType.Prompt, + properties: { + promptText, + commands: [ + { + text: 'Yes', + codeCommand: { id: 'greet', arguments: ['world'] } + } + ] + } + } + }); + + return Promise.resolve(null).then(result => { + assert.deepStrictEqual(stub.args[0], ['greet', 'world']); + assert.equal(storageData['state'], ExperimentState.Complete); + }); + + }); + test('Show experimental prompt if experiment should be run. Cancelling should mark experiment as complete', () => { storageData = { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 39ada55824f..84795fe5186 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -27,7 +27,7 @@ import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, import { RatingsWidget, InstallCountWidget, RemoteBadgeWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets'; import { EditorOptions } from 'vs/workbench/common/editor'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { CombinedInstallAction, UpdateAction, ExtensionEditorDropDownAction, ReloadAction, MaliciousStatusLabelAction, IgnoreExtensionRecommendationAction, UndoIgnoreExtensionRecommendationAction, EnableDropDownAction, DisableDropDownAction, StatusLabelAction, SetFileIconThemeAction, SetColorThemeAction, RemoteInstallAction, ExtensionToolTipAction, SystemDisabledWarningAction, LocalInstallAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { CombinedInstallAction, UpdateAction, ExtensionEditorDropDownAction, ReloadAction, MaliciousStatusLabelAction, IgnoreExtensionRecommendationAction, UndoIgnoreExtensionRecommendationAction, EnableDropDownAction, DisableDropDownAction, StatusLabelAction, SetFileIconThemeAction, SetColorThemeAction, RemoteInstallAction, ExtensionToolTipAction, SystemDisabledWarningAction, LocalInstallAction, SyncIgnoredIconAction, SetProductIconThemeAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener'; @@ -40,7 +40,7 @@ import { Color } from 'vs/base/common/color'; import { assign } from 'vs/base/common/objects'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { ExtensionsTree, ExtensionData } from 'vs/workbench/contrib/extensions/browser/extensionsViewer'; +import { ExtensionsTree, ExtensionData, ExtensionsGridView, getExtensions } from 'vs/workbench/contrib/extensions/browser/extensionsViewer'; import { ShowCurrentReleaseNotesActionId } from 'vs/workbench/contrib/update/common/update'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -83,7 +83,9 @@ class NavBar extends Disposable { private _onChange = this._register(new Emitter<{ id: string | null, focus: boolean }>()); get onChange(): Event<{ id: string | null, focus: boolean }> { return this._onChange.event; } - private currentId: string | null = null; + private _currentId: string | null = null; + get currentId(): string | null { return this._currentId; } + private actions: Action[]; private actionbar: ActionBar; @@ -113,11 +115,11 @@ class NavBar extends Disposable { } update(): void { - this._update(this.currentId); + this._update(this._currentId); } - _update(id: string | null = this.currentId, focus?: boolean): Promise { - this.currentId = id; + _update(id: string | null = this._currentId, focus?: boolean): Promise { + this._currentId = id; this._onChange.fire({ id, focus: !!focus }); this.actions.forEach(a => a.checked = a.id === id); return Promise.resolve(undefined); @@ -129,7 +131,6 @@ const NavbarSection = { Contributions: 'contributions', Changelog: 'changelog', Dependencies: 'dependencies', - ExtensionPack: 'extensionPack' }; interface ILayoutParticipant { @@ -204,6 +205,7 @@ export class ExtensionEditor extends BaseEditor { const root = append(parent, $('.extension-editor')); root.tabIndex = 0; // this is required for the focus tracker on the editor root.style.outline = 'none'; + root.setAttribute('role', 'document'); const header = append(root, $('.header')); const iconContainer = append(header, $('.icon-container')); @@ -308,15 +310,13 @@ export class ExtensionEditor extends BaseEditor { async setInput(input: ExtensionsInput, options: EditorOptions | undefined, token: CancellationToken): Promise { if (this.template) { - await this.updateTemplate(input, this.template); + await this.updateTemplate(input, this.template, !!options?.preserveFocus); } return super.setInput(input, options, token); } - private async updateTemplate(input: ExtensionsInput, template: IExtensionEditorTemplate): Promise { + private async updateTemplate(input: ExtensionsInput, template: IExtensionEditorTemplate, preserveFocus: boolean): Promise { const runningExtensions = await this.extensionService.getExtensions(); - const colorThemes = await this.workbenchThemeService.getColorThemes(); - const fileIconThemes = await this.workbenchThemeService.getFileIconThemes(); this.activeElement = null; this.editorLoadComplete = false; @@ -397,10 +397,13 @@ export class ExtensionEditor extends BaseEditor { const systemDisabledWarningAction = this.instantiationService.createInstance(SystemDisabledWarningAction); const actions = [ reloadAction, + this.instantiationService.createInstance(SyncIgnoredIconAction), this.instantiationService.createInstance(StatusLabelAction), this.instantiationService.createInstance(UpdateAction), - this.instantiationService.createInstance(SetColorThemeAction, colorThemes), - this.instantiationService.createInstance(SetFileIconThemeAction, fileIconThemes), + this.instantiationService.createInstance(SetColorThemeAction, await this.workbenchThemeService.getColorThemes()), + this.instantiationService.createInstance(SetFileIconThemeAction, await this.workbenchThemeService.getFileIconThemes()), + this.instantiationService.createInstance(SetProductIconThemeAction, await this.workbenchThemeService.getProductIconThemes()), + this.instantiationService.createInstance(EnableDropDownAction), this.instantiationService.createInstance(DisableDropDownAction, runningExtensions), this.instantiationService.createInstance(RemoteInstallAction), @@ -423,31 +426,31 @@ export class ExtensionEditor extends BaseEditor { template.content.innerHTML = ''; // Clear content before setting navbar actions. template.navbar.clear(); - template.navbar.onChange(e => this.onNavbarChange(extension, e, template), this, this.transientDisposables); if (extension.hasReadme()) { template.navbar.push(NavbarSection.Readme, localize('details', "Details"), localize('detailstooltip', "Extension details, rendered from the extension's 'README.md' file")); } - this.extensionManifest.get() - .promise - .then(manifest => { - if (manifest) { - combinedInstallAction.manifest = manifest; - } - if (extension.extensionPack.length) { - template.navbar.push(NavbarSection.ExtensionPack, localize('extensionPack', "Extension Pack"), localize('extensionsPack', "Set of extensions that can be installed together")); - } - if (manifest && manifest.contributes) { - template.navbar.push(NavbarSection.Contributions, localize('contributions', "Feature Contributions"), localize('contributionstooltip', "Lists contributions to VS Code by this extension")); - } - if (extension.hasChangelog()) { - template.navbar.push(NavbarSection.Changelog, localize('changelog', "Changelog"), localize('changelogtooltip', "Extension update history, rendered from the extension's 'CHANGELOG.md' file")); - } - if (extension.dependencies.length) { - template.navbar.push(NavbarSection.Dependencies, localize('dependencies', "Dependencies"), localize('dependenciestooltip', "Lists extensions this extension depends on")); - } - this.editorLoadComplete = true; - }); + + const manifest = await this.extensionManifest.get().promise; + if (manifest) { + combinedInstallAction.manifest = manifest; + } + if (manifest && manifest.contributes) { + template.navbar.push(NavbarSection.Contributions, localize('contributions', "Feature Contributions"), localize('contributionstooltip', "Lists contributions to VS Code by this extension")); + } + if (extension.hasChangelog()) { + template.navbar.push(NavbarSection.Changelog, localize('changelog', "Changelog"), localize('changelogtooltip', "Extension update history, rendered from the extension's 'CHANGELOG.md' file")); + } + if (extension.dependencies.length) { + template.navbar.push(NavbarSection.Dependencies, localize('dependencies', "Dependencies"), localize('dependenciestooltip', "Lists extensions this extension depends on")); + } + + if (template.navbar.currentId) { + this.onNavbarChange(extension, { id: template.navbar.currentId, focus: !preserveFocus }, template); + } + template.navbar.onChange(e => this.onNavbarChange(extension, e, template), this, this.transientDisposables); + + this.editorLoadComplete = true; } private setSubText(extension: IExtension, reloadAction: ReloadAction, template: IExtensionEditorTemplate): void { @@ -507,6 +510,7 @@ export class ExtensionEditor extends BaseEditor { if (e.enabled === false) { hide(template.subtextContainer); } + this.layout(); })); } @@ -568,7 +572,6 @@ export class ExtensionEditor extends BaseEditor { case NavbarSection.Contributions: return this.openContributions(template); case NavbarSection.Changelog: return this.openChangelog(template); case NavbarSection.Dependencies: return this.openDependencies(extension, template); - case NavbarSection.ExtensionPack: return this.openExtensionPack(extension, template); } return Promise.resolve(null); } @@ -577,18 +580,18 @@ export class ExtensionEditor extends BaseEditor { try { const body = await this.renderMarkdown(cacheResult, template); - const webviewElement = this.contentDisposables.add(this.webviewService.createWebviewEditorOverlay('extensionEditor', { + const webview = this.contentDisposables.add(this.webviewService.createWebviewOverlay('extensionEditor', { enableFindWidget: true, }, {})); - webviewElement.claim(this); - webviewElement.layoutWebviewOverElement(template.content); - webviewElement.html = body; + webview.claim(this); + webview.layoutWebviewOverElement(template.content); + webview.html = body; - this.contentDisposables.add(webviewElement.onDidFocus(() => this.fireOnDidFocus())); + this.contentDisposables.add(webview.onDidFocus(() => this.fireOnDidFocus())); const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout: () => { - webviewElement.layoutWebviewOverElement(template.content); + webview.layoutWebviewOverElement(template.content); } }); this.contentDisposables.add(toDisposable(removeLayoutParticipant)); @@ -596,15 +599,15 @@ export class ExtensionEditor extends BaseEditor { let isDisposed = false; this.contentDisposables.add(toDisposable(() => { isDisposed = true; })); - this.contentDisposables.add(this.themeService.onThemeChange(async () => { + this.contentDisposables.add(this.themeService.onDidColorThemeChange(async () => { // Render again since syntax highlighting of code blocks may have changed const body = await this.renderMarkdown(cacheResult, template); if (!isDisposed) { // Make sure we weren't disposed of in the meantime - webviewElement.html = body; + webview.html = body; } })); - this.contentDisposables.add(webviewElement.onDidClickLink(link => { + this.contentDisposables.add(webview.onDidClickLink(link => { if (!link) { return; } @@ -616,7 +619,7 @@ export class ExtensionEditor extends BaseEditor { } }, null, this.contentDisposables)); - return webviewElement; + return webview; } catch (e) { const p = append(template.content, $('p.nocontent')); p.textContent = noContentCopy; @@ -644,7 +647,7 @@ export class ExtensionEditor extends BaseEditor { body { padding: 10px 20px; line-height: 22px; - max-width: 780px; + max-width: 882px; margin: 0 auto; } @@ -823,10 +826,45 @@ export class ExtensionEditor extends BaseEditor { `; } - private openReadme(template: IExtensionEditorTemplate): Promise { + private async openReadme(template: IExtensionEditorTemplate): Promise { + const manifest = await this.extensionManifest!.get().promise; + if (manifest && manifest.extensionPack && manifest.extensionPack.length) { + return this.openExtensionPackReadme(manifest, template); + } return this.openMarkdown(this.extensionReadme!.get(), localize('noReadme', "No README available."), template); } + private async openExtensionPackReadme(manifest: IExtensionManifest, template: IExtensionEditorTemplate): Promise { + const extensionPackReadme = append(template.content, $('div', { class: 'extension-pack-readme' })); + extensionPackReadme.style.margin = '0 auto'; + extensionPackReadme.style.maxWidth = '882px'; + + const extensionPack = append(extensionPackReadme, $('div', { class: 'extension-pack' })); + if (manifest.extensionPack!.length <= 3) { + addClass(extensionPackReadme, 'one-row'); + } else if (manifest.extensionPack!.length <= 6) { + addClass(extensionPackReadme, 'two-rows'); + } else if (manifest.extensionPack!.length <= 9) { + addClass(extensionPackReadme, 'three-rows'); + } else { + addClass(extensionPackReadme, 'more-rows'); + } + + const extensionPackHeader = append(extensionPack, $('div.header')); + extensionPackHeader.textContent = localize('extension pack', "Extension Pack ({0})", manifest.extensionPack!.length); + const extensionPackContent = append(extensionPack, $('div', { class: 'extension-pack-content' })); + extensionPackContent.setAttribute('tabindex', '0'); + append(extensionPack, $('div.footer')); + const readmeContent = append(extensionPackReadme, $('div.readme-content')); + + await Promise.all([ + this.renderExtensionPack(manifest, extensionPackContent), + this.openMarkdown(this.extensionReadme!.get(), localize('noReadme', "No README available."), { ...template, ...{ content: readmeContent } }), + ]); + + return { focus: () => extensionPackContent.focus() }; + } + private openChangelog(template: IExtensionEditorTemplate): Promise { return this.openMarkdown(this.extensionChangelog!.get(), localize('noChangelog', "No Changelog available."), template); } @@ -908,28 +946,19 @@ export class ExtensionEditor extends BaseEditor { return Promise.resolve({ focus() { dependenciesTree.domFocus(); } }); } - private openExtensionPack(extension: IExtension, template: IExtensionEditorTemplate): Promise { + private async renderExtensionPack(manifest: IExtensionManifest, parent: HTMLElement): Promise { const content = $('div', { class: 'subcontent' }); - const scrollableContent = new DomScrollableElement(content, {}); - append(template.content, scrollableContent.getDomNode()); - this.contentDisposables.add(scrollableContent); + const scrollableContent = new DomScrollableElement(content, { useShadows: false }); + append(parent, scrollableContent.getDomNode()); - const extensionsPackTree = this.instantiationService.createInstance(ExtensionsTree, - new ExtensionData(extension, null, extension => extension.extensionPack || [], this.extensionsWorkbenchService), content, - { - listBackground: editorBackground - }); - const layout = () => { - scrollableContent.scanDomNode(); - const scrollDimensions = scrollableContent.getScrollDimensions(); - extensionsPackTree.layout(scrollDimensions.height); - }; - const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout }); - this.contentDisposables.add(toDisposable(removeLayoutParticipant)); - - this.contentDisposables.add(extensionsPackTree); + const extensionsGridView = this.instantiationService.createInstance(ExtensionsGridView, content); + const extensions: IExtension[] = await getExtensions(manifest.extensionPack!, this.extensionsWorkbenchService); + extensionsGridView.setExtensions(extensions); scrollableContent.scanDomNode(); - return Promise.resolve({ focus() { extensionsPackTree.domFocus(); } }); + + this.contentDisposables.add(scrollableContent); + this.contentDisposables.add(extensionsGridView); + this.contentDisposables.add(toDisposable(arrays.insert(this.layoutParticipants, { layout: () => scrollableContent.scanDomNode() }))); } private renderSettings(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { @@ -1137,7 +1166,7 @@ export class ExtensionEditor extends BaseEditor { } const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', { tabindex: '0' }, localize('iconThemes', "Icon Themes ({0})", contrib.length)), + $('summary', { tabindex: '0' }, localize('iconThemes', "File Icon Themes ({0})", contrib.length)), $('ul', undefined, ...contrib.map(theme => $('li', undefined, theme.label))) ); @@ -1202,7 +1231,7 @@ export class ExtensionEditor extends BaseEditor { $('th', undefined, localize('schema', "Schema")) ), ...contrib.map(v => $('tr', undefined, - $('td', undefined, $('code', undefined, v.fileMatch)), + $('td', undefined, $('code', undefined, Array.isArray(v.fileMatch) ? v.fileMatch.join(', ') : v.fileMatch)), $('td', undefined, v.url) )))); @@ -1462,9 +1491,9 @@ registerAction2(class StartExtensionEditorFindPreviousAction extends Action2 { }); function getExtensionEditor(accessor: ServicesAccessor): ExtensionEditor | null { - const activeControl = accessor.get(IEditorService).activeControl as ExtensionEditor; - if (activeControl instanceof ExtensionEditor) { - return activeControl; + const activeEditorPane = accessor.get(IEditorService).activeEditorPane; + if (activeEditorPane instanceof ExtensionEditor) { + return activeEditorPane; } return null; } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts b/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts index 913da6725e9..71128c942c9 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts @@ -131,6 +131,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe this.fetchCachedDynamicWorkspaceRecommendations(); this.fetchFileBasedRecommendations(); this.fetchExperimentalRecommendations(); + this.fetchImportantExeBasedRecommendation(); if (!this.configurationService.getValue(ShowRecommendationsOnlyOnDemandKey)) { this.fetchProactiveRecommendations(true); } @@ -505,6 +506,14 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe //#region important exe based extension + private async fetchImportantExeBasedRecommendation(): Promise { + // Executable based recommendations carry out a lot of file stats, delay the resolution so that the startup is not affected + // 3 secs for important + await timeout(3000); + await this.fetchExecutableRecommendations(true); + await this.promptForImportantExeBasedExtension(); + } + private async promptForImportantExeBasedExtension(): Promise { let recommendationsToSuggest = Object.keys(this._importantExeBasedRecommendations); @@ -527,6 +536,19 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe return false; } + for (const extensionId of recommendationsToSuggest) { + const tip = this._importantExeBasedRecommendations[extensionId]; + + /* __GDPR__ + "exeExtensionRecommendations:notInstalled" : { + "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }, + "exeName": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('exeExtensionRecommendations:notInstalled', { extensionId, exeName: basename(tip.windowsPath!) }); + } + + const storageKey = 'extensionsAssistant/workspaceRecommendationsIgnore'; const config = this.configurationService.getValue(ConfigurationKey); @@ -1008,10 +1030,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe // 10 sec for regular extensions // 3 secs for important - const importantExeBasedRecommendations = timeout(calledDuringStartup ? 3000 : 0).then(_ => this.fetchExecutableRecommendations(true)); - importantExeBasedRecommendations.then(_ => this.promptForImportantExeBasedExtension()); - - fetchPromise = timeout(calledDuringStartup ? 10000 : 0).then(_ => Promise.all([this.fetchDynamicWorkspaceRecommendations(), this.fetchExecutableRecommendations(false), importantExeBasedRecommendations])); + fetchPromise = timeout(calledDuringStartup ? 10000 : 0).then(_ => Promise.all([this.fetchDynamicWorkspaceRecommendations(), this.fetchExecutableRecommendations(false)])); } return fetchPromise; diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index cf17ac8d9c5..20c03bd7f3d 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -24,7 +24,6 @@ import { import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { ExtensionEditor } from 'vs/workbench/contrib/extensions/browser/extensionEditor'; import { StatusUpdater, MaliciousExtensionChecker, ExtensionsViewletViewsContribution, ExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/browser/extensionsViewlet'; -import { IQuickOpenRegistry, Extensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import * as jsonContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { ExtensionsConfigurationSchema, ExtensionsConfigurationSchemaId } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; @@ -32,7 +31,6 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeymapExtensions } from 'vs/workbench/contrib/extensions/common/extensionsUtils'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { GalleryExtensionsHandler, ExtensionsHandler } from 'vs/workbench/contrib/extensions/browser/extensionsQuickOpen'; import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; @@ -50,6 +48,8 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { CONTEXT_SYNC_ENABLEMENT } from 'vs/platform/userDataSync/common/userDataSync'; +import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; +import { InstallExtensionQuickAccessProvider, ManageExtensionsQuickAccessProvider } from 'vs/workbench/contrib/extensions/browser/extensionsQuickAccess'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); @@ -58,17 +58,13 @@ registerSingleton(IExtensionTipsService, ExtensionTipsService); Registry.as(OutputExtensions.OutputChannels) .registerChannel({ id: ExtensionsChannelId, label: ExtensionsLabel, log: false }); -// Quickopen -Registry.as(Extensions.Quickopen).registerQuickOpenHandler( - QuickOpenHandlerDescriptor.create( - ExtensionsHandler, - ExtensionsHandler.ID, - 'ext ', - undefined, - localize('extensionsCommands', "Manage Extensions"), - true - ) -); +// Quick Access +Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ + ctor: ManageExtensionsQuickAccessProvider, + prefix: ManageExtensionsQuickAccessProvider.PREFIX, + placeholder: localize('manageExtensionsQuickAccessPlaceholder', "Press Enter to manage extensions."), + helpEntries: [{ description: localize('manageExtensionsHelp', "Manage Extensions"), needsEditor: false }] +}); // Editor Registry.as(EditorExtensions.Editors).registerEditor( @@ -476,16 +472,12 @@ class ExtensionsContributions implements IWorkbenchContribution { const canManageExtensions = extensionManagementServerService.localExtensionManagementServer || extensionManagementServerService.remoteExtensionManagementServer; if (canManageExtensions) { - Registry.as(Extensions.Quickopen).registerQuickOpenHandler( - QuickOpenHandlerDescriptor.create( - GalleryExtensionsHandler, - GalleryExtensionsHandler.ID, - 'ext install ', - undefined, - localize('galleryExtensionsCommands', "Install Gallery Extensions"), - true - ) - ); + Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ + ctor: InstallExtensionQuickAccessProvider, + prefix: InstallExtensionQuickAccessProvider.PREFIX, + placeholder: localize('installExtensionQuickAccessPlaceholder', "Type the name of an extension to install or search."), + helpEntries: [{ description: localize('installExtensionQuickAccessHelp', "Install or Search Extensions"), needsEditor: false }] + }); } } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index e032e135584..f4d8006f5d5 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -15,7 +15,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { dispose, Disposable } from 'vs/base/common/lifecycle'; import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, AutoUpdateConfigurationKey, IExtensionContainer, EXTENSIONS_CONFIG, TOGGLE_IGNORE_EXTENSION_ACTION_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; -import { ExtensionsLabel, IGalleryExtension, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension, INSTALL_ERROR_NOT_SUPPORTED } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IGalleryExtension, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension, INSTALL_ERROR_NOT_SUPPORTED } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionTipsService, IExtensionRecommendation, IExtensionsConfigContent, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionType, ExtensionIdentifier, IExtensionDescription, IExtensionManifest, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; @@ -29,8 +29,8 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; -import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { buttonBackground, buttonForeground, buttonHoverBackground, contrastBorder, registerColor, foreground } from 'vs/platform/theme/common/colorRegistry'; import { Color } from 'vs/base/common/color'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; @@ -52,7 +52,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { coalesce } from 'vs/base/common/arrays'; -import { IWorkbenchThemeService, COLOR_THEME_SETTING, ICON_THEME_SETTING, IFileIconTheme, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService, IWorkbenchTheme, IWorkbenchColorTheme, IWorkbenchFileIconTheme, IWorkbenchProductIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ILabelService } from 'vs/platform/label/common/label'; import { prefersExecuteOnUI, prefersExecuteOnWorkspace } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; @@ -139,6 +139,10 @@ function getRelativeDateLabel(date: Date): string { } export abstract class ExtensionAction extends Action implements IExtensionContainer { + static readonly EXTENSION_ACTION_CLASS = 'extension-action'; + static readonly TEXT_ACTION_CLASS = `${ExtensionAction.EXTENSION_ACTION_CLASS} text`; + static readonly LABEL_ACTION_CLASS = `${ExtensionAction.EXTENSION_ACTION_CLASS} label`; + static readonly ICON_ACTION_CLASS = `${ExtensionAction.EXTENSION_ACTION_CLASS} icon`; private _extension: IExtension | null = null; get extension(): IExtension | null { return this._extension; } set extension(extension: IExtension | null) { this._extension = extension; this.update(); } @@ -150,9 +154,8 @@ export class InstallAction extends ExtensionAction { private static readonly INSTALL_LABEL = localize('install', "Install"); private static readonly INSTALLING_LABEL = localize('installing', "Installing"); - private static readonly Class = 'extension-action prominent install'; - private static readonly InstallingClass = 'extension-action install installing'; - + private static readonly Class = `${ExtensionAction.LABEL_ACTION_CLASS} prominent install`; + private static readonly InstallingClass = `${ExtensionAction.LABEL_ACTION_CLASS} install installing`; private _manifest: IExtensionManifest | null = null; set manifest(manifest: IExtensionManifest) { @@ -234,17 +237,15 @@ export class InstallAction extends ExtensionAction { if (extension && extension.local) { const runningExtension = await this.getRunningExtension(extension.local); if (runningExtension) { - const colorThemes = await this.workbenchThemeService.getColorThemes(); - const fileIconThemes = await this.workbenchThemeService.getFileIconThemes(); - if (SetColorThemeAction.getColorThemes(colorThemes, this.extension).length) { - const action = this.instantiationService.createInstance(SetColorThemeAction, colorThemes); - action.extension = extension; - return action.run({ showCurrentTheme: true, ignoreFocusLost: true }); - } - if (SetFileIconThemeAction.getFileIconThemes(fileIconThemes, this.extension).length) { - const action = this.instantiationService.createInstance(SetFileIconThemeAction, fileIconThemes); - action.extension = extension; - return action.run({ showCurrentTheme: true, ignoreFocusLost: true }); + let action = await SetColorThemeAction.create(this.workbenchThemeService, this.instantiationService, extension) + || await SetFileIconThemeAction.create(this.workbenchThemeService, this.instantiationService, extension) + || await SetProductIconThemeAction.create(this.workbenchThemeService, this.instantiationService, extension); + if (action) { + try { + return action.run({ showCurrentTheme: true, ignoreFocusLost: true }); + } finally { + action.dispose(); + } } } } @@ -289,8 +290,8 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { protected static readonly INSTALL_LABEL = localize('install', "Install"); protected static readonly INSTALLING_LABEL = localize('installing', "Installing"); - private static readonly Class = 'extension-action prominent install'; - private static readonly InstallingClass = 'extension-action install installing'; + private static readonly Class = `${ExtensionAction.LABEL_ACTION_CLASS} prominent install`; + private static readonly InstallingClass = `${ExtensionAction.LABEL_ACTION_CLASS} install installing`; updateWhenCounterExtensionChanges: boolean = true; @@ -382,8 +383,8 @@ export class UninstallAction extends ExtensionAction { private static readonly UninstallLabel = localize('uninstallAction', "Uninstall"); private static readonly UninstallingLabel = localize('Uninstalling', "Uninstalling"); - private static readonly UninstallClass = 'extension-action uninstall'; - private static readonly UnInstallingClass = 'extension-action uninstall uninstalling'; + private static readonly UninstallClass = `${ExtensionAction.LABEL_ACTION_CLASS} uninstall`; + private static readonly UnInstallingClass = `${ExtensionAction.LABEL_ACTION_CLASS} uninstall uninstalling`; constructor( @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService @@ -438,7 +439,7 @@ export class UninstallAction extends ExtensionAction { export class CombinedInstallAction extends ExtensionAction { - private static readonly NoExtensionClass = 'extension-action prominent install no-extension'; + private static readonly NoExtensionClass = `${ExtensionAction.LABEL_ACTION_CLASS} prominent install no-extension`; private installAction: InstallAction; private uninstallAction: UninstallAction; @@ -505,7 +506,7 @@ export class CombinedInstallAction extends ExtensionAction { export class UpdateAction extends ExtensionAction { - private static readonly EnabledClass = 'extension-action prominent update'; + private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} prominent update`; private static readonly DisabledClass = `${UpdateAction.EnabledClass} disabled`; constructor( @@ -663,6 +664,7 @@ export class DropDownMenuActionViewItem extends ExtensionActionViewItem { export function getContextMenuActions(menuService: IMenuService, contextKeyService: IContextKeyService, instantiationService: IInstantiationService, extension: IExtension | undefined | null): ExtensionAction[][] { const scopedContextKeyService = contextKeyService.createScoped(); if (extension) { + scopedContextKeyService.createKey('extension', extension.identifier.id); scopedContextKeyService.createKey('isBuiltinExtension', extension.type === ExtensionType.System); scopedContextKeyService.createKey('extensionHasConfiguration', extension.local && !!extension.local.manifest.contributes && !!extension.local.manifest.contributes.configuration); if (extension.state === ExtensionState.Installed) { @@ -681,7 +683,8 @@ export function getContextMenuActions(menuService: IMenuService, contextKeyServi export class ManageExtensionAction extends ExtensionDropDownAction { static readonly ID = 'extensions.manage'; - private static readonly Class = 'extension-action manage codicon-gear'; + + private static readonly Class = `${ExtensionAction.ICON_ACTION_CLASS} manage codicon-gear`; private static readonly HideManageExtensionClass = `${ManageExtensionAction.Class} hide`; constructor( @@ -699,19 +702,22 @@ export class ManageExtensionAction extends ExtensionDropDownAction { this.update(); } - getActionGroups(runningExtensions: IExtensionDescription[], colorThemes: IColorTheme[], fileIconThemes: IFileIconTheme[]): IAction[][] { + async getActionGroups(runningExtensions: IExtensionDescription[]): Promise { const groups: ExtensionAction[][] = []; if (this.extension) { - const extensionColorThemes = SetColorThemeAction.getColorThemes(colorThemes, this.extension); - const extensionFileIconThemes = SetFileIconThemeAction.getFileIconThemes(fileIconThemes, this.extension); - if (extensionColorThemes.length || extensionFileIconThemes.length) { - const themesGroup: ExtensionAction[] = []; - if (extensionColorThemes.length) { - themesGroup.push(this.instantiationService.createInstance(SetColorThemeAction, colorThemes)); - } - if (extensionFileIconThemes.length) { - themesGroup.push(this.instantiationService.createInstance(SetFileIconThemeAction, fileIconThemes)); + const actions = await Promise.all([ + SetColorThemeAction.create(this.workbenchThemeService, this.instantiationService, this.extension), + SetFileIconThemeAction.create(this.workbenchThemeService, this.instantiationService, this.extension), + SetProductIconThemeAction.create(this.workbenchThemeService, this.instantiationService, this.extension) + ]); + + const themesGroup: ExtensionAction[] = []; + for (let action of actions) { + if (action) { + themesGroup.push(action); } + } + if (themesGroup.length) { groups.push(themesGroup); } } @@ -735,9 +741,7 @@ export class ManageExtensionAction extends ExtensionDropDownAction { async run(): Promise { const runtimeExtensions = await this.extensionService.getExtensions(); - const colorThemes = await this.workbenchThemeService.getColorThemes(); - const fileIconThemes = await this.workbenchThemeService.getFileIconThemes(); - return super.run({ actionGroups: this.getActionGroups(runtimeExtensions, colorThemes, fileIconThemes), disposeActionsOnHide: true }); + return super.run({ actionGroups: await this.getActionGroups(runtimeExtensions), disposeActionsOnHide: true }); } update(): void { @@ -952,8 +956,8 @@ export class DisableGloballyAction extends ExtensionAction { export abstract class ExtensionEditorDropDownAction extends ExtensionDropDownAction { - private static readonly EnabledClass = 'extension-action extension-editor-dropdown-action'; - private static readonly EnabledDropDownClass = 'extension-action extension-editor-dropdown-action dropdown enable'; + private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} extension-editor-dropdown-action`; + private static readonly EnabledDropDownClass = `${ExtensionEditorDropDownAction.EnabledClass} dropdown enable`; private static readonly DisabledClass = `${ExtensionEditorDropDownAction.EnabledClass} disabled`; constructor( @@ -1161,7 +1165,7 @@ export class UpdateAllAction extends Action { export class ReloadAction extends ExtensionAction { - private static readonly EnabledClass = 'extension-action reload'; + private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} reload`; private static readonly DisabledClass = `${ReloadAction.EnabledClass} disabled`; updateWhenCounterExtensionChanges: boolean = true; @@ -1296,22 +1300,45 @@ export class ReloadAction extends ExtensionAction { } } +function isThemeFromExtension(theme: IWorkbenchTheme, extension: IExtension | undefined | null): boolean { + return !!(extension && theme.extensionData && ExtensionIdentifier.equals(theme.extensionData.extensionId, extension.identifier.id)); +} + +function getQuickPickEntries(themes: IWorkbenchTheme[], currentTheme: IWorkbenchTheme, extension: IExtension | null | undefined, showCurrentTheme: boolean): (IQuickPickItem | IQuickPickSeparator)[] { + const picks: (IQuickPickItem | IQuickPickSeparator)[] = []; + for (const theme of themes) { + if (isThemeFromExtension(theme, extension) && !(showCurrentTheme && theme === currentTheme)) { + picks.push({ label: theme.label, id: theme.id }); + } + } + if (showCurrentTheme) { + picks.push({ type: 'separator', label: localize('current', "Current") }); + picks.push({ label: currentTheme.label, id: currentTheme.id }); + } + return picks; +} + + export class SetColorThemeAction extends ExtensionAction { - static getColorThemes(colorThemes: IColorTheme[], extension: IExtension): IColorTheme[] { - return colorThemes.filter(c => c.extensionData && ExtensionIdentifier.equals(c.extensionData.extensionId, extension.identifier.id)); - } - - private static readonly EnabledClass = 'extension-action theme'; + private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`; private static readonly DisabledClass = `${SetColorThemeAction.EnabledClass} disabled`; + static async create(workbenchThemeService: IWorkbenchThemeService, instantiationService: IInstantiationService, extension: IExtension): Promise { + const themes = await workbenchThemeService.getColorThemes(); + if (themes.some(th => isThemeFromExtension(th, extension))) { + const action = instantiationService.createInstance(SetColorThemeAction, themes); + action.extension = extension; + return action; + } + return undefined; + } constructor( - private readonly colorThemes: IColorTheme[], + private colorThemes: IWorkbenchColorTheme[], @IExtensionService extensionService: IExtensionService, @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, @IQuickInputService private readonly quickInputService: IQuickInputService, - @IConfigurationService private readonly configurationService: IConfigurationService ) { super(`extensions.colorTheme`, localize('color theme', "Set Color Theme"), SetColorThemeAction.DisabledClass, false); this._register(Event.any(extensionService.onDidChangeExtensions, workbenchThemeService.onDidColorThemeChange)(() => this.update(), this)); @@ -1319,36 +1346,21 @@ export class SetColorThemeAction extends ExtensionAction { } update(): void { - this.enabled = false; - if (this.extension) { - const isInstalled = this.extension.state === ExtensionState.Installed; - if (isInstalled) { - const extensionThemes = SetColorThemeAction.getColorThemes(this.colorThemes, this.extension); - this.enabled = extensionThemes.length > 0; - } - } + this.enabled = !!this.extension && (this.extension.state === ExtensionState.Installed) && this.colorThemes.some(th => isThemeFromExtension(th, this.extension)); this.class = this.enabled ? SetColorThemeAction.EnabledClass : SetColorThemeAction.DisabledClass; } async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean, ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise { + this.colorThemes = await this.workbenchThemeService.getColorThemes(); + this.update(); if (!this.enabled) { return; } - let extensionThemes = SetColorThemeAction.getColorThemes(this.colorThemes, this.extension!); - const currentTheme = this.colorThemes.filter(t => t.settingsId === this.configurationService.getValue(COLOR_THEME_SETTING))[0] || this.workbenchThemeService.getColorTheme(); - showCurrentTheme = showCurrentTheme || extensionThemes.some(t => t.id === currentTheme.id); - if (showCurrentTheme) { - extensionThemes = extensionThemes.filter(t => t.id !== currentTheme.id); - } + const currentTheme = this.workbenchThemeService.getColorTheme(); const delayer = new Delayer(100); - const picks: (IQuickPickItem | IQuickPickSeparator)[] = []; - picks.push(...extensionThemes.map(theme => ({ label: theme.label, id: theme.id }))); - if (showCurrentTheme) { - picks.push({ type: 'separator', label: localize('current', "Current") }); - picks.push({ label: currentTheme.label, id: currentTheme.id }); - } + const picks = getQuickPickEntries(this.colorThemes, currentTheme, this.extension, showCurrentTheme); const pickedTheme = await this.quickInputService.pick( picks, { @@ -1356,28 +1368,30 @@ export class SetColorThemeAction extends ExtensionAction { onDidFocus: item => delayer.trigger(() => this.workbenchThemeService.setColorTheme(item.id, undefined)), ignoreFocusLost }); - let confValue = this.configurationService.inspect(COLOR_THEME_SETTING); - const target = typeof confValue.workspaceValue !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; - return this.workbenchThemeService.setColorTheme(pickedTheme ? pickedTheme.id : currentTheme.id, target); + return this.workbenchThemeService.setColorTheme(pickedTheme ? pickedTheme.id : currentTheme.id, 'auto'); } } export class SetFileIconThemeAction extends ExtensionAction { - private static readonly EnabledClass = 'extension-action theme'; + private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`; private static readonly DisabledClass = `${SetFileIconThemeAction.EnabledClass} disabled`; - - static getFileIconThemes(fileIconThemes: IFileIconTheme[], extension: IExtension): IFileIconTheme[] { - return fileIconThemes.filter(c => c.extensionData && ExtensionIdentifier.equals(c.extensionData.extensionId, extension.identifier.id)); + static async create(workbenchThemeService: IWorkbenchThemeService, instantiationService: IInstantiationService, extension: IExtension): Promise { + const themes = await workbenchThemeService.getFileIconThemes(); + if (themes.some(th => isThemeFromExtension(th, extension))) { + const action = instantiationService.createInstance(SetFileIconThemeAction, themes); + action.extension = extension; + return action; + } + return undefined; } constructor( - private readonly fileIconThemes: IFileIconTheme[], + private fileIconThemes: IWorkbenchFileIconTheme[], @IExtensionService extensionService: IExtensionService, @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, - @IQuickInputService private readonly quickInputService: IQuickInputService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IQuickInputService private readonly quickInputService: IQuickInputService ) { super(`extensions.fileIconTheme`, localize('file icon theme', "Set File Icon Theme"), SetFileIconThemeAction.DisabledClass, false); this._register(Event.any(extensionService.onDidChangeExtensions, workbenchThemeService.onDidFileIconThemeChange)(() => this.update(), this)); @@ -1385,36 +1399,20 @@ export class SetFileIconThemeAction extends ExtensionAction { } update(): void { - this.enabled = false; - if (this.extension) { - const isInstalled = this.extension.state === ExtensionState.Installed; - if (isInstalled) { - const extensionThemes = SetFileIconThemeAction.getFileIconThemes(this.fileIconThemes, this.extension); - this.enabled = extensionThemes.length > 0; - } - } + this.enabled = !!this.extension && (this.extension.state === ExtensionState.Installed) && this.fileIconThemes.some(th => isThemeFromExtension(th, this.extension)); this.class = this.enabled ? SetFileIconThemeAction.EnabledClass : SetFileIconThemeAction.DisabledClass; } async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean, ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise { - await this.update(); + this.fileIconThemes = await this.workbenchThemeService.getFileIconThemes(); + this.update(); if (!this.enabled) { return; } - let extensionThemes = SetFileIconThemeAction.getFileIconThemes(this.fileIconThemes, this.extension!); - const currentTheme = this.fileIconThemes.filter(t => t.settingsId === this.configurationService.getValue(ICON_THEME_SETTING))[0] || this.workbenchThemeService.getFileIconTheme(); - showCurrentTheme = showCurrentTheme || extensionThemes.some(t => t.id === currentTheme.id); - if (showCurrentTheme) { - extensionThemes = extensionThemes.filter(t => t.id !== currentTheme.id); - } + const currentTheme = this.workbenchThemeService.getFileIconTheme(); const delayer = new Delayer(100); - const picks: (IQuickPickItem | IQuickPickSeparator)[] = []; - picks.push(...extensionThemes.map(theme => ({ label: theme.label, id: theme.id }))); - if (showCurrentTheme && currentTheme.label) { - picks.push({ type: 'separator', label: localize('current', "Current") }); - picks.push({ label: currentTheme.label, id: currentTheme.id }); - } + const picks = getQuickPickEntries(this.fileIconThemes, currentTheme, this.extension, showCurrentTheme); const pickedTheme = await this.quickInputService.pick( picks, { @@ -1422,9 +1420,62 @@ export class SetFileIconThemeAction extends ExtensionAction { onDidFocus: item => delayer.trigger(() => this.workbenchThemeService.setFileIconTheme(item.id, undefined)), ignoreFocusLost }); - let confValue = this.configurationService.inspect(ICON_THEME_SETTING); - const target = typeof confValue.workspaceValue !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; - return this.workbenchThemeService.setFileIconTheme(pickedTheme ? pickedTheme.id : currentTheme.id, target); + return this.workbenchThemeService.setFileIconTheme(pickedTheme ? pickedTheme.id : currentTheme.id, 'auto'); + } +} + +export class SetProductIconThemeAction extends ExtensionAction { + + private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`; + private static readonly DisabledClass = `${SetProductIconThemeAction.EnabledClass} disabled`; + + static async create(workbenchThemeService: IWorkbenchThemeService, instantiationService: IInstantiationService, extension: IExtension): Promise { + const themes = await workbenchThemeService.getProductIconThemes(); + if (themes.some(th => isThemeFromExtension(th, extension))) { + const action = instantiationService.createInstance(SetProductIconThemeAction, themes); + action.extension = extension; + return action; + } + return undefined; + } + + constructor( + private productIconThemes: IWorkbenchProductIconTheme[], + @IExtensionService extensionService: IExtensionService, + @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, + @IQuickInputService private readonly quickInputService: IQuickInputService + ) { + super(`extensions.productIconTheme`, localize('product icon theme', "Set Product Icon Theme"), SetProductIconThemeAction.DisabledClass, false); + this._register(Event.any(extensionService.onDidChangeExtensions, workbenchThemeService.onDidProductIconThemeChange)(() => this.update(), this)); + this.enabled = true; // enabled by default + this.class = SetProductIconThemeAction.EnabledClass; + // this.update(); + } + + update(): void { + this.enabled = !!this.extension && (this.extension.state === ExtensionState.Installed) && this.productIconThemes.some(th => isThemeFromExtension(th, this.extension)); + this.class = this.enabled ? SetProductIconThemeAction.EnabledClass : SetProductIconThemeAction.DisabledClass; + } + + async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean, ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise { + this.productIconThemes = await this.workbenchThemeService.getProductIconThemes(); + this.update(); + if (!this.enabled) { + return; + } + + const currentTheme = this.workbenchThemeService.getProductIconTheme(); + + const delayer = new Delayer(100); + const picks = getQuickPickEntries(this.productIconThemes, currentTheme, this.extension, showCurrentTheme); + const pickedTheme = await this.quickInputService.pick( + picks, + { + placeHolder: localize('select product icon theme', "Select Product Icon Theme"), + onDidFocus: item => delayer.trigger(() => this.workbenchThemeService.setProductIconTheme(item.id, undefined)), + ignoreFocusLost + }); + return this.workbenchThemeService.setProductIconTheme(pickedTheme ? pickedTheme.id : currentTheme.id, 'auto'); } } @@ -1789,7 +1840,6 @@ export class UndoIgnoreExtensionRecommendationAction extends Action { } } - export class ShowRecommendedKeymapExtensionsAction extends Action { static readonly ID = 'workbench.extensions.action.showRecommendedKeymapExtensions'; @@ -1939,7 +1989,7 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: ConfigureWorkspaceRecommendedExtensionsAction.ID, - title: { value: `${ExtensionsLabel}: ${ConfigureWorkspaceRecommendedExtensionsAction.LABEL}`, original: 'Configure Recommended Extensions (Workspace)' }, + title: { value: ConfigureWorkspaceRecommendedExtensionsAction.LABEL, original: 'Configure Recommended Extensions (Workspace)' }, category: localize('extensions', "Extensions") }, when: this.workspaceContextKey @@ -1951,7 +2001,7 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, - title: { value: `${ExtensionsLabel}: ${ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL}`, original: 'Configure Recommended Extensions (Workspace Folder)' }, + title: { value: ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL, original: 'Configure Recommended Extensions (Workspace Folder)' }, category: localize('extensions', "Extensions") }, when: this.workspaceFolderContextKey @@ -1965,7 +2015,7 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: AddToWorkspaceRecommendationsAction.ADD_ID, - title: { value: `${ExtensionsLabel}: ${AddToWorkspaceRecommendationsAction.ADD_LABEL}`, original: 'Add to Recommended Extensions (Workspace)' }, + title: { value: AddToWorkspaceRecommendationsAction.ADD_LABEL, original: 'Add to Recommended Extensions (Workspace)' }, category: localize('extensions', "Extensions") }, when: this.addToWorkspaceRecommendationsContextKey @@ -1979,7 +2029,7 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: AddToWorkspaceFolderRecommendationsAction.ADD_ID, - title: { value: `${ExtensionsLabel}: ${AddToWorkspaceFolderRecommendationsAction.ADD_LABEL}`, original: 'Extensions: Add to Recommended Extensions (Workspace Folder)' }, + title: { value: AddToWorkspaceFolderRecommendationsAction.ADD_LABEL, original: 'Extensions: Add to Recommended Extensions (Workspace Folder)' }, category: localize('extensions', "Extensions") }, when: this.addToWorkspaceFolderRecommendationsContextKey @@ -1993,7 +2043,7 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: AddToWorkspaceRecommendationsAction.IGNORE_ID, - title: { value: `${ExtensionsLabel}: ${AddToWorkspaceRecommendationsAction.IGNORE_LABEL}`, original: 'Extensions: Ignore Recommended Extension (Workspace)' }, + title: { value: AddToWorkspaceRecommendationsAction.IGNORE_LABEL, original: 'Extensions: Ignore Recommended Extension (Workspace)' }, category: localize('extensions', "Extensions") }, when: this.addToWorkspaceRecommendationsContextKey @@ -2007,7 +2057,7 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: AddToWorkspaceFolderRecommendationsAction.IGNORE_ID, - title: { value: `${ExtensionsLabel}: ${AddToWorkspaceFolderRecommendationsAction.IGNORE_LABEL}`, original: 'Extensions: Ignore Recommended Extension (Workspace Folder)' }, + title: { value: AddToWorkspaceFolderRecommendationsAction.IGNORE_LABEL, original: 'Extensions: Ignore Recommended Extension (Workspace Folder)' }, category: localize('extensions', "Extensions") }, when: this.addToWorkspaceFolderRecommendationsContextKey @@ -2183,7 +2233,6 @@ export class ConfigureWorkspaceRecommendedExtensionsAction extends AbstractConfi static readonly ID = 'workbench.extensions.action.configureWorkspaceRecommendedExtensions'; static readonly LABEL = localize('configureWorkspaceRecommendedExtensions', "Configure Recommended Extensions (Workspace)"); - constructor( id: string, label: string, @@ -2219,7 +2268,6 @@ export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends Abstrac static readonly ID = 'workbench.extensions.action.configureWorkspaceFolderRecommendedExtensions'; static readonly LABEL = localize('configureWorkspaceFolderRecommendedExtensions', "Configure Recommended Extensions (Workspace Folder)"); - constructor( id: string, label: string, @@ -2410,7 +2458,7 @@ export class AddToWorkspaceRecommendationsAction extends AbstractConfigureRecomm export class StatusLabelAction extends Action implements IExtensionContainer { - private static readonly ENABLED_CLASS = 'extension-status-label'; + private static readonly ENABLED_CLASS = `${ExtensionAction.TEXT_ACTION_CLASS} extension-status-label`; private static readonly DISABLED_CLASS = `${StatusLabelAction.ENABLED_CLASS} hide`; private initialStatus: ExtensionState | null = null; @@ -2512,7 +2560,7 @@ export class StatusLabelAction extends Action implements IExtensionContainer { export class MaliciousStatusLabelAction extends ExtensionAction { - private static readonly Class = 'malicious-status'; + private static readonly Class = `${ExtensionAction.TEXT_ACTION_CLASS} malicious-status`; constructor(long: boolean) { const tooltip = localize('malicious tooltip', "This extension was reported to be problematic."); @@ -2534,9 +2582,38 @@ export class MaliciousStatusLabelAction extends ExtensionAction { } } +export class SyncIgnoredIconAction extends ExtensionAction { + + private static readonly ENABLE_CLASS = `${ExtensionAction.ICON_ACTION_CLASS} codicon-sync-ignored`; + private static readonly DISABLE_CLASS = `${SyncIgnoredIconAction.ENABLE_CLASS} hide`; + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService + ) { + super('extensions.syncignore', '', SyncIgnoredIconAction.DISABLE_CLASS, false); + this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectedKeys.includes('sync.ignoredExtensions'))(() => this.update())); + this.update(); + this.tooltip = localize('syncingore.label', "This extension is ignored during sync."); + } + + update(): void { + this.class = SyncIgnoredIconAction.DISABLE_CLASS; + if (this.extension) { + const ignoredExtensions = this.configurationService.getValue('sync.ignoredExtensions') || []; + if (ignoredExtensions.some(id => areSameExtensions({ id }, this.extension!.identifier))) { + this.class = SyncIgnoredIconAction.ENABLE_CLASS; + } + } + } + + run(): Promise { + return Promise.resolve(); + } +} + export class ExtensionToolTipAction extends ExtensionAction { - private static readonly Class = 'disable-status'; + private static readonly Class = `${ExtensionAction.TEXT_ACTION_CLASS} disable-status`; updateWhenCounterExtensionChanges: boolean = true; private _runningExtensions: IExtensionDescription[] | null = null; @@ -2613,7 +2690,7 @@ export class ExtensionToolTipAction extends ExtensionAction { export class SystemDisabledWarningAction extends ExtensionAction { - private static readonly CLASS = 'system-disable'; + private static readonly CLASS = `${ExtensionAction.ICON_ACTION_CLASS} system-disable`; private static readonly WARNING_CLASS = `${SystemDisabledWarningAction.CLASS} codicon-warning`; private static readonly INFO_CLASS = `${SystemDisabledWarningAction.CLASS} codicon-info`; @@ -2704,7 +2781,6 @@ export class DisableAllAction extends Action { static readonly ID = 'workbench.extensions.action.disableAll'; static readonly LABEL = localize('disableAll', "Disable All Installed Extensions"); - constructor( id: string = DisableAllAction.ID, label: string = DisableAllAction.LABEL, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @@ -2729,7 +2805,6 @@ export class DisableAllWorkspaceAction extends Action { static readonly ID = 'workbench.extensions.action.disableAllWorkspace'; static readonly LABEL = localize('disableAllWorkspace', "Disable All Installed Extensions for this Workspace"); - constructor( id: string = DisableAllWorkspaceAction.ID, label: string = DisableAllWorkspaceAction.LABEL, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @@ -2756,7 +2831,6 @@ export class EnableAllAction extends Action { static readonly ID = 'workbench.extensions.action.enableAll'; static readonly LABEL = localize('enableAll', "Enable All Extensions"); - constructor( id: string = EnableAllAction.ID, label: string = EnableAllAction.LABEL, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @@ -2781,7 +2855,6 @@ export class EnableAllWorkspaceAction extends Action { static readonly ID = 'workbench.extensions.action.enableAllWorkspace'; static readonly LABEL = localize('enableAllWorkspace', "Enable All Extensions for this Workspace"); - constructor( id: string = EnableAllWorkspaceAction.ID, label: string = EnableAllWorkspaceAction.LABEL, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @@ -3183,52 +3256,52 @@ export const extensionButtonProminentHoverBackground = registerColor('extensionB hc: null }, localize('extensionButtonProminentHoverBackground', "Button background hover color for actions extension that stand out (e.g. install button).")); -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const foregroundColor = theme.getColor(foreground); if (foregroundColor) { - collector.addRule(`.extension .monaco-action-bar .action-item .action-label.extension-action.built-in-status { border-color: ${foregroundColor}; }`); + collector.addRule(`.extension-list-item .monaco-action-bar .action-item .action-label.extension-action.built-in-status { border-color: ${foregroundColor}; }`); collector.addRule(`.extension-editor .monaco-action-bar .action-item .action-label.extension-action.built-in-status { border-color: ${foregroundColor}; }`); } const buttonBackgroundColor = theme.getColor(buttonBackground); if (buttonBackgroundColor) { - collector.addRule(`.extension .monaco-action-bar .action-item .action-label.extension-action { background-color: ${buttonBackgroundColor}; }`); - collector.addRule(`.extension-editor .monaco-action-bar .action-item .action-label.extension-action { background-color: ${buttonBackgroundColor}; }`); + collector.addRule(`.extension-list-item .monaco-action-bar .action-item .action-label.extension-action.label { background-color: ${buttonBackgroundColor}; }`); + collector.addRule(`.extension-editor .monaco-action-bar .action-item .action-label.extension-action.label { background-color: ${buttonBackgroundColor}; }`); } const buttonForegroundColor = theme.getColor(buttonForeground); if (buttonForegroundColor) { - collector.addRule(`.extension .monaco-action-bar .action-item .action-label.extension-action { color: ${buttonForegroundColor}; }`); - collector.addRule(`.extension-editor .monaco-action-bar .action-item .action-label.extension-action { color: ${buttonForegroundColor}; }`); + collector.addRule(`.extension-list-item .monaco-action-bar .action-item .action-label.extension-action.label { color: ${buttonForegroundColor}; }`); + collector.addRule(`.extension-editor .monaco-action-bar .action-item .action-label.extension-action.label { color: ${buttonForegroundColor}; }`); } const buttonHoverBackgroundColor = theme.getColor(buttonHoverBackground); if (buttonHoverBackgroundColor) { - collector.addRule(`.extension .monaco-action-bar .action-item:hover .action-label.extension-action { background-color: ${buttonHoverBackgroundColor}; }`); - collector.addRule(`.extension-editor .monaco-action-bar .action-item:hover .action-label.extension-action { background-color: ${buttonHoverBackgroundColor}; }`); - } - - const contrastBorderColor = theme.getColor(contrastBorder); - if (contrastBorderColor) { - collector.addRule(`.extension .monaco-action-bar .action-item .action-label.extension-action { border: 1px solid ${contrastBorderColor}; }`); - collector.addRule(`.extension-editor .monaco-action-bar .action-item .action-label.extension-action { border: 1px solid ${contrastBorderColor}; }`); + collector.addRule(`.extension-list-item .monaco-action-bar .action-item:hover .action-label.extension-action.label { background-color: ${buttonHoverBackgroundColor}; }`); + collector.addRule(`.extension-editor .monaco-action-bar .action-item:hover .action-label.extension-action.label { background-color: ${buttonHoverBackgroundColor}; }`); } const extensionButtonProminentBackgroundColor = theme.getColor(extensionButtonProminentBackground); if (extensionButtonProminentBackground) { - collector.addRule(`.extension .monaco-action-bar .action-item .action-label.extension-action.prominent { background-color: ${extensionButtonProminentBackgroundColor}; }`); - collector.addRule(`.extension-editor .monaco-action-bar .action-item .action-label.extension-action.prominent { background-color: ${extensionButtonProminentBackgroundColor}; }`); + collector.addRule(`.extension-list-item .monaco-action-bar .action-item .action-label.extension-action.label.prominent { background-color: ${extensionButtonProminentBackgroundColor}; }`); + collector.addRule(`.extension-editor .monaco-action-bar .action-item .action-label.extension-action.label.prominent { background-color: ${extensionButtonProminentBackgroundColor}; }`); } const extensionButtonProminentForegroundColor = theme.getColor(extensionButtonProminentForeground); if (extensionButtonProminentForeground) { - collector.addRule(`.extension .monaco-action-bar .action-item .action-label.extension-action.prominent { color: ${extensionButtonProminentForegroundColor}; }`); - collector.addRule(`.extension-editor .monaco-action-bar .action-item .action-label.extension-action.prominent { color: ${extensionButtonProminentForegroundColor}; }`); + collector.addRule(`.extension-list-item .monaco-action-bar .action-item .action-label.extension-action.label.prominent { color: ${extensionButtonProminentForegroundColor}; }`); + collector.addRule(`.extension-editor .monaco-action-bar .action-item .action-label.extension-action.label.prominent { color: ${extensionButtonProminentForegroundColor}; }`); } const extensionButtonProminentHoverBackgroundColor = theme.getColor(extensionButtonProminentHoverBackground); if (extensionButtonProminentHoverBackground) { - collector.addRule(`.extension .monaco-action-bar .action-item:hover .action-label.extension-action.prominent { background-color: ${extensionButtonProminentHoverBackgroundColor}; }`); - collector.addRule(`.extension-editor .monaco-action-bar .action-item:hover .action-label.extension-action.prominent { background-color: ${extensionButtonProminentHoverBackgroundColor}; }`); + collector.addRule(`.extension-list-item .monaco-action-bar .action-item:hover .action-label.extension-action.label.prominent { background-color: ${extensionButtonProminentHoverBackgroundColor}; }`); + collector.addRule(`.extension-editor .monaco-action-bar .action-item:hover .action-label.extension-action.label.prominent { background-color: ${extensionButtonProminentHoverBackgroundColor}; }`); + } + + const contrastBorderColor = theme.getColor(contrastBorder); + if (contrastBorderColor) { + collector.addRule(`.extension-list-item .monaco-action-bar .action-item .action-label.extension-action { border: 1px solid ${contrastBorderColor}; }`); + collector.addRule(`.extension-editor .monaco-action-bar .action-item .action-label.extension-action { border: 1px solid ${contrastBorderColor}; }`); } }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts index 8d9dd6f1b87..23187c381cd 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./media/extension'; import { append, $, addClass, removeClass, toggleClass } from 'vs/base/browser/dom'; import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle'; import { IAction } from 'vs/base/common/actions'; @@ -13,9 +14,9 @@ import { IPagedRenderer } from 'vs/base/browser/ui/list/listPaging'; import { Event } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; import { IExtension, ExtensionContainers, ExtensionState, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; -import { InstallAction, UpdateAction, ManageExtensionAction, ReloadAction, MaliciousStatusLabelAction, ExtensionActionViewItem, StatusLabelAction, RemoteInstallAction, SystemDisabledWarningAction, ExtensionToolTipAction, LocalInstallAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { InstallAction, UpdateAction, ManageExtensionAction, ReloadAction, MaliciousStatusLabelAction, ExtensionActionViewItem, StatusLabelAction, RemoteInstallAction, SystemDisabledWarningAction, ExtensionToolTipAction, LocalInstallAction, SyncIgnoredIconAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { Label, RatingsWidget, InstallCountWidget, RecommendationWidget, RemoteBadgeWidget, TooltipWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets'; +import { Label, RatingsWidget, InstallCountWidget, RecommendationWidget, RemoteBadgeWidget, TooltipWidget, ExtensionPackCountWidget as ExtensionPackBadgeWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -56,17 +57,18 @@ export class Renderer implements IPagedRenderer { @INotificationService private readonly notificationService: INotificationService, @IExtensionService private readonly extensionService: IExtensionService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, - @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, ) { } get templateId() { return 'extension'; } renderTemplate(root: HTMLElement): ITemplateData { - const recommendationWidget = this.instantiationService.createInstance(RecommendationWidget, root); - const element = append(root, $('.extension')); + const recommendationWidget = this.instantiationService.createInstance(RecommendationWidget, append(root, $('.extension-bookmark-container'))); + const element = append(root, $('.extension-list-item')); const iconContainer = append(element, $('.icon-container')); const icon = append(iconContainer, $('img.icon')); const iconRemoteBadgeWidget = this.instantiationService.createInstance(RemoteBadgeWidget, iconContainer, false); + const extensionPackBadgeWidget = this.instantiationService.createInstance(ExtensionPackBadgeWidget, iconContainer); const details = append(element, $('.details')); const headerContainer = append(details, $('.header-container')); const header = append(headerContainer, $('.header')); @@ -93,6 +95,7 @@ export class Renderer implements IPagedRenderer { const reloadAction = this.instantiationService.createInstance(ReloadAction); const actions = [ this.instantiationService.createInstance(StatusLabelAction), + this.instantiationService.createInstance(SyncIgnoredIconAction), this.instantiationService.createInstance(UpdateAction), reloadAction, this.instantiationService.createInstance(InstallAction), @@ -107,6 +110,7 @@ export class Renderer implements IPagedRenderer { const widgets = [ recommendationWidget, iconRemoteBadgeWidget, + extensionPackBadgeWidget, headerRemoteBadgeWidget, tooltipWidget, this.instantiationService.createInstance(Label, version, (e: IExtension) => e.version), @@ -197,6 +201,7 @@ export class Renderer implements IPagedRenderer { data.actionbar.viewItems.forEach(item => (item).setFocus(false)); } }, this, data.extensionDisposables); + } disposeTemplate(data: ITemplateData): void { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts b/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts new file mode 100644 index 00000000000..921231f3f86 --- /dev/null +++ b/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.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 { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { IPickerQuickAccessItem, PickerQuickAccessProvider } from 'vs/platform/quickinput/browser/pickerQuickAccess'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { localize } from 'vs/nls'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { VIEWLET_ID, IExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IExtensionGalleryService, IExtensionManagementService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { ILogService } from 'vs/platform/log/common/log'; +import { DisposableStore } from 'vs/base/common/lifecycle'; + +export class InstallExtensionQuickAccessProvider extends PickerQuickAccessProvider { + + static PREFIX = 'ext install '; + + constructor( + @IViewletService private readonly viewletService: IViewletService, + @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, + @IExtensionManagementService private readonly extensionsService: IExtensionManagementService, + @INotificationService private readonly notificationService: INotificationService, + @ILogService private readonly logService: ILogService + ) { + super(InstallExtensionQuickAccessProvider.PREFIX); + } + + protected getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Array | Promise> { + + // Nothing typed + if (!filter) { + return [{ + label: localize('type', "Type an extension name to install or search.") + }]; + } + + const genericSearchPickItem: IPickerQuickAccessItem = { + label: localize('searchFor', "Press Enter to search for extension '{0}'.", filter), + accept: () => this.searchExtension(filter) + }; + + // Extension ID typed: try to find it + if (/\./.test(filter)) { + return this.getPicksForExtensionId(filter, genericSearchPickItem, token); + } + + // Extension name typed: offer to search it + return [genericSearchPickItem]; + } + + private async getPicksForExtensionId(filter: string, fallback: IPickerQuickAccessItem, token: CancellationToken): Promise> { + try { + const galleryResult = await this.galleryService.query({ names: [filter], pageSize: 1 }, token); + if (token.isCancellationRequested) { + return []; // return early if canceled + } + + const galleryExtension = galleryResult.firstPage[0]; + if (!galleryExtension) { + return [fallback]; + } + + return [{ + label: localize('install', "Press Enter to install extension '{0}'.", filter), + accept: () => this.installExtension(galleryExtension, filter) + }]; + } catch (error) { + if (token.isCancellationRequested) { + return []; // expected error + } + + this.logService.error(error); + + return [fallback]; + } + } + + private async installExtension(extension: IGalleryExtension, name: string): Promise { + try { + await openExtensionsViewlet(this.viewletService, `@id:${name}`); + await this.extensionsService.installFromGallery(extension); + } catch (error) { + this.notificationService.error(error); + } + } + + private async searchExtension(name: string): Promise { + openExtensionsViewlet(this.viewletService, name); + } +} + +export class ManageExtensionsQuickAccessProvider extends PickerQuickAccessProvider { + + static PREFIX = 'ext '; + + constructor(@IViewletService private readonly viewletService: IViewletService) { + super(ManageExtensionsQuickAccessProvider.PREFIX); + } + + protected getPicks(): Array { + return [{ + label: localize('manage', "Press Enter to manage your extensions."), + accept: () => openExtensionsViewlet(this.viewletService) + }]; + } +} + +async function openExtensionsViewlet(viewletService: IViewletService, search = ''): Promise { + const viewlet = await viewletService.openViewlet(VIEWLET_ID, true); + const view = viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer | undefined; + view?.search(search); + view?.focus(); +} diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsQuickOpen.ts b/src/vs/workbench/contrib/extensions/browser/extensionsQuickOpen.ts deleted file mode 100644 index f6b1caa1900..00000000000 --- a/src/vs/workbench/contrib/extensions/browser/extensionsQuickOpen.ts +++ /dev/null @@ -1,139 +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 nls from 'vs/nls'; -import { IAutoFocus, Mode, IModel } from 'vs/base/parts/quickopen/common/quickOpen'; -import { QuickOpenEntry, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { QuickOpenHandler } from 'vs/workbench/browser/quickopen'; -import { IExtensionsViewPaneContainer, VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; -import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { CancellationToken } from 'vs/base/common/cancellation'; - -class SimpleEntry extends QuickOpenEntry { - - constructor(private label: string, private action: Function) { - super(); - } - - getLabel(): string { - return this.label; - } - - getAriaLabel(): string { - return this.label; - } - - run(mode: Mode): boolean { - if (mode === Mode.PREVIEW) { - return false; - } - - this.action(); - - return true; - } -} - -export class ExtensionsHandler extends QuickOpenHandler { - - public static readonly ID = 'workbench.picker.extensions'; - - constructor(@IViewletService private readonly viewletService: IViewletService) { - super(); - } - - getResults(text: string, token: CancellationToken): Promise> { - const label = nls.localize('manage', "Press Enter to manage your extensions."); - const action = () => { - this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) - .then(viewlet => { - viewlet.search(''); - viewlet.focus(); - }); - }; - - return Promise.resolve(new QuickOpenModel([new SimpleEntry(label, action)])); - } - - getEmptyLabel(input: string): string { - return ''; - } - - getAutoFocus(searchValue: string): IAutoFocus { - return { autoFocusFirstEntry: true }; - } -} - -export class GalleryExtensionsHandler extends QuickOpenHandler { - - public static readonly ID = 'workbench.picker.gallery'; - - constructor( - @IViewletService private readonly viewletService: IViewletService, - @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, - @IExtensionManagementService private readonly extensionsService: IExtensionManagementService, - @INotificationService private readonly notificationService: INotificationService - ) { - super(); - } - - getResults(text: string, token: CancellationToken): Promise> { - if (/\./.test(text)) { - return this.galleryService.query({ names: [text], pageSize: 1 }, token) - .then(galleryResult => { - const entries: SimpleEntry[] = []; - const galleryExtension = galleryResult.firstPage[0]; - - if (!galleryExtension) { - const label = nls.localize('notfound', "Extension '{0}' not found in the Marketplace.", text); - entries.push(new SimpleEntry(label, () => null)); - - } else { - const label = nls.localize('install', "Press Enter to install '{0}' from the Marketplace.", text); - const action = () => { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) - .then(viewlet => viewlet.search(`@id:${text}`)) - .then(() => this.extensionsService.installFromGallery(galleryExtension)) - .then(undefined, err => this.notificationService.error(err)); - }; - - entries.push(new SimpleEntry(label, action)); - } - - return new QuickOpenModel(entries); - }); - } - - const entries: SimpleEntry[] = []; - - if (text) { - const label = nls.localize('searchFor', "Press Enter to search for '{0}' in the Marketplace.", text); - const action = () => { - this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) - .then(viewlet => { - viewlet.search(text); - viewlet.focus(); - }); - }; - - entries.push(new SimpleEntry(label, action)); - } - - return Promise.resolve(new QuickOpenModel(entries)); - } - - getEmptyLabel(input: string): string { - return nls.localize('noExtensionsToInstall', "Type an extension name"); - } - - getAutoFocus(searchValue: string): IAutoFocus { - return { autoFocusFirstEntry: true }; - } -} diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts index 42704102383..5fda9210e7e 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts @@ -5,7 +5,7 @@ import * as dom from 'vs/base/browser/dom'; import { localize } from 'vs/nls'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { Action } from 'vs/base/common/actions'; import { IExtensionsWorkbenchService, IExtension } from 'vs/workbench/contrib/extensions/common/extensions'; import { Event } from 'vs/base/common/event'; @@ -14,7 +14,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IListService, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IAsyncDataSource, ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; @@ -22,6 +22,64 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { CancellationToken } from 'vs/base/common/cancellation'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { IColorMapping } from 'vs/platform/theme/common/styler'; +import { Renderer, Delegate } from 'vs/workbench/contrib/extensions/browser/extensionsList'; +import { listFocusForeground, listFocusBackground } from 'vs/platform/theme/common/colorRegistry'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { KeyCode } from 'vs/base/common/keyCodes'; + +export class ExtensionsGridView extends Disposable { + + readonly element: HTMLElement; + private readonly renderer: Renderer; + private readonly delegate: Delegate; + private readonly disposableStore: DisposableStore; + + constructor( + parent: HTMLElement, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { + super(); + this.element = dom.append(parent, dom.$('.extensions-grid-view')); + this.renderer = this.instantiationService.createInstance(Renderer, { onFocus: Event.None, onBlur: Event.None }); + this.delegate = new Delegate(); + this.disposableStore = new DisposableStore(); + } + + setExtensions(extensions: IExtension[]): void { + this.disposableStore.clear(); + extensions.forEach((e, index) => this.renderExtension(e, index)); + } + + private renderExtension(extension: IExtension, index: number): void { + const extensionContainer = dom.append(this.element, dom.$('.extension-container')); + extensionContainer.style.height = `${this.delegate.getHeight()}px`; + extensionContainer.style.width = `275px`; + extensionContainer.setAttribute('tabindex', '0'); + + const template = this.renderer.renderTemplate(extensionContainer); + this.disposableStore.add(toDisposable(() => this.renderer.disposeTemplate(template))); + + const openExtensionAction = this.instantiationService.createInstance(OpenExtensionAction); + openExtensionAction.extension = extension; + template.name.setAttribute('tabindex', '0'); + + const handleEvent = (e: StandardMouseEvent | StandardKeyboardEvent) => { + if (e instanceof StandardKeyboardEvent && e.keyCode !== KeyCode.Enter) { + return; + } + openExtensionAction.run(e.ctrlKey || e.metaKey); + e.stopPropagation(); + e.preventDefault(); + }; + + this.disposableStore.add(dom.addDisposableListener(template.name, dom.EventType.CLICK, (e: MouseEvent) => handleEvent(new StandardMouseEvent(e)))); + this.disposableStore.add(dom.addDisposableListener(template.name, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => handleEvent(new StandardKeyboardEvent(e)))); + this.disposableStore.add(dom.addDisposableListener(extensionContainer, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => handleEvent(new StandardKeyboardEvent(e)))); + + this.renderer.renderElement(extension, index, template); + } +} export interface IExtensionTemplateData { icon: HTMLImageElement; @@ -101,7 +159,7 @@ export class ExtensionRenderer implements IListRenderer { - if (this._extensionData) { - return this.extensionsWorkdbenchService.open(this._extensionData.extension, sideByside); + if (this._extension) { + return this.extensionsWorkdbenchService.open(this._extension, { sideByside }); } return Promise.resolve(); } @@ -218,7 +276,7 @@ export class ExtensionsTree extends WorkbenchAsyncDataTree { if (event.browserEvent && event.browserEvent instanceof KeyboardEvent) { - extensionsWorkdbenchService.open(event.elements[0].extension, false); + extensionsWorkdbenchService.open(event.elements[0].extension, { sideByside: false }); } })); } @@ -246,24 +304,40 @@ export class ExtensionData implements IExtensionData { async getChildren(): Promise { if (this.hasChildren) { - const localById = this.extensionsWorkbenchService.local.reduce((result, e) => { result.set(e.identifier.id.toLowerCase(), e); return result; }, new Map()); - const result: IExtension[] = []; - const toQuery: string[] = []; - for (const extensionId of this.childrenExtensionIds) { - const id = extensionId.toLowerCase(); - const local = localById.get(id); - if (local) { - result.push(local); - } else { - toQuery.push(id); - } - } - if (toQuery.length) { - const galleryResult = await this.extensionsWorkbenchService.queryGallery({ names: toQuery, pageSize: toQuery.length }, CancellationToken.None); - result.push(...galleryResult.firstPage); - } + const result: IExtension[] = await getExtensions(this.childrenExtensionIds, this.extensionsWorkbenchService); return result.map(extension => new ExtensionData(extension, this, this.getChildrenExtensionIds, this.extensionsWorkbenchService)); } return null; } } + +export async function getExtensions(extensions: string[], extensionsWorkbenchService: IExtensionsWorkbenchService): Promise { + const localById = extensionsWorkbenchService.local.reduce((result, e) => { result.set(e.identifier.id.toLowerCase(), e); return result; }, new Map()); + const result: IExtension[] = []; + const toQuery: string[] = []; + for (const extensionId of extensions) { + const id = extensionId.toLowerCase(); + const local = localById.get(id); + if (local) { + result.push(local); + } else { + toQuery.push(id); + } + } + if (toQuery.length) { + const galleryResult = await extensionsWorkbenchService.queryGallery({ names: toQuery, pageSize: toQuery.length }, CancellationToken.None); + result.push(...galleryResult.firstPage); + } + return result; +} + +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { + const focusBackground = theme.getColor(listFocusBackground); + if (focusBackground) { + collector.addRule(`.extensions-grid-view .extension-container:focus { background-color: ${focusBackground}; outline: none; }`); + } + const focusForeground = theme.getColor(listFocusForeground); + if (focusForeground) { + collector.addRule(`.extensions-grid-view .extension-container:focus { color: ${focusForeground}; }`); + } +}); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 97cc84812cb..0311e3ce1b1 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -10,7 +10,7 @@ import { isPromiseCanceledError } from 'vs/base/common/errors'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { Event as EventOf, Emitter } from 'vs/base/common/event'; -import { IAction } from 'vs/base/common/actions'; +import { IAction, Action } from 'vs/base/common/actions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -28,7 +28,6 @@ import { IExtensionManagementService } from 'vs/platform/extensionManagement/com import { IWorkbenchExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInExtensionsView, BuiltInThemesExtensionsView, BuiltInBasicsExtensionsView, ServerExtensionsView, DefaultRecommendedExtensionsView } from 'vs/workbench/contrib/extensions/browser/extensionsViews'; -import { OpenGlobalSettingsAction } from 'vs/workbench/contrib/preferences/browser/preferencesActions'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import Severity from 'vs/base/common/severity'; @@ -58,6 +57,7 @@ import { RemoteNameContext } from 'vs/workbench/browser/contextkeys'; import { ILabelService } from 'vs/platform/label/common/label'; import { MementoObject } from 'vs/workbench/common/memento'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; const NonEmptyWorkspaceContext = new RawContextKey('nonEmptyWorkspace', false); const DefaultViewsContext = new RawContextKey('defaultExtensionViews', true); @@ -356,7 +356,8 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE @IContextKeyService contextKeyService: IContextKeyService, @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IPreferencesService private readonly preferencesService: IPreferencesService ) { super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); @@ -606,7 +607,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE if (/ECONNREFUSED/.test(message)) { const error = createErrorWithActions(localize('suggestProxyError', "Marketplace returned 'ECONNREFUSED'. Please check the 'http.proxy' setting."), { actions: [ - this.instantiationService.createInstance(OpenGlobalSettingsAction, OpenGlobalSettingsAction.ID, OpenGlobalSettingsAction.LABEL) + new Action('open user settings', localize('open user settings', "Open User Settings"), undefined, true, () => this.preferencesService.openGlobalSettings()) ] }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 52378063e9c..e3cb96713c6 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -23,13 +23,12 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; -import { OpenGlobalSettingsAction } from 'vs/workbench/contrib/preferences/browser/preferencesActions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { InstallWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, ManageExtensionAction, InstallLocalExtensionsInRemoteAction, getContextMenuActions } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; -import { WorkbenchPagedList } from 'vs/platform/list/browser/listService'; +import { WorkbenchPagedList, ResourceNavigator } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; @@ -40,9 +39,8 @@ import { alert } from 'vs/base/browser/ui/aria/aria'; import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IAction } from 'vs/base/common/actions'; +import { IAction, Action } from 'vs/base/common/actions'; import { ExtensionType, ExtensionIdentifier, IExtensionDescription, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; -import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { IProductService } from 'vs/platform/product/common/productService'; import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; @@ -51,6 +49,7 @@ import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { IMenuService } from 'vs/platform/actions/common/actions'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; // Extensions that are automatically classified as Programming Language extensions, but should be Feature extensions const FORCE_FEATURE_EXTENSIONS = ['vscode.git', 'vscode.search-result']; @@ -106,15 +105,15 @@ export class ExtensionsListView extends ViewPane { @IConfigurationService configurationService: IConfigurationService, @IWorkspaceContextService protected contextService: IWorkspaceContextService, @IExperimentService private readonly experimentService: IExperimentService, - @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, @IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService, @IProductService protected readonly productService: IProductService, @IContextKeyService contextKeyService: IContextKeyService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IMenuService private readonly menuService: IMenuService, @IOpenerService openerService: IOpenerService, + @IPreferencesService private readonly preferencesService: IPreferencesService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title, showActionsAlways: true }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super({ ...(options as IViewPaneOptions), showActionsAlways: true }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.server = options.server; } @@ -146,14 +145,14 @@ export class ExtensionsListView extends ViewPane { } }); this._register(this.list.onContextMenu(e => this.onContextMenu(e), this)); - this._register(this.list.onFocusChange(e => extensionsViewState.onFocusChange(coalesce(e.elements)), this)); + this._register(this.list.onDidChangeFocus(e => extensionsViewState.onFocusChange(coalesce(e.elements)), this)); this._register(this.list); this._register(extensionsViewState); - this._register(Event.chain(this.list.onOpen) - .map(e => e.elements[0]) - .filter(e => !!e) - .on(this.openExtension, this)); + const resourceNavigator = this._register(ResourceNavigator.createListResourceNavigator(this.list, { openOnFocus: false, openOnSelection: true, openOnSingleClick: true })); + this._register(Event.debounce(Event.filter(resourceNavigator.onDidOpenResource, e => e.element !== null), (last, event) => event, 75, true)(options => { + this.openExtension(this.list!.model.get(options.element!), { sideByside: options.sideBySide, ...options.editorOptions }); + })); this._register(Event.chain(this.list.onPin) .map(e => e.elements[0]) @@ -232,12 +231,10 @@ export class ExtensionsListView extends ViewPane { private async onContextMenu(e: IListContextMenuEvent): Promise { if (e.element) { const runningExtensions = await this.extensionService.getExtensions(); - const colorThemes = await this.workbenchThemeService.getColorThemes(); - const fileIconThemes = await this.workbenchThemeService.getFileIconThemes(); const manageExtensionAction = this.instantiationService.createInstance(ManageExtensionAction); manageExtensionAction.extension = e.element; if (manageExtensionAction.enabled) { - const groups = manageExtensionAction.getActionGroups(runningExtensions, colorThemes, fileIconThemes); + const groups = await manageExtensionAction.getActionGroups(runningExtensions); let actions: IAction[] = []; for (const menuActions of groups) { actions = [...actions, ...menuActions, new Separator()]; @@ -750,16 +747,16 @@ export class ExtensionsListView extends ViewPane { } } - private openExtension(extension: IExtension): void { + private openExtension(extension: IExtension, options: { sideByside?: boolean, preserveFocus?: boolean, pinned?: boolean }): void { extension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, extension.identifier))[0] || extension; - this.extensionsWorkbenchService.open(extension).then(undefined, err => this.onError(err)); + this.extensionsWorkbenchService.open(extension, options).then(undefined, err => this.onError(err)); } private pin(): void { - const activeControl = this.editorService.activeControl; - if (activeControl) { - activeControl.group.pinEditor(activeControl.input); - activeControl.focus(); + const activeEditorPane = this.editorService.activeEditorPane; + if (activeEditorPane) { + activeEditorPane.group.pinEditor(activeEditorPane.input); + activeEditorPane.focus(); } } @@ -773,7 +770,7 @@ export class ExtensionsListView extends ViewPane { if (/ECONNREFUSED/.test(message)) { const error = createErrorWithActions(localize('suggestProxyError', "Marketplace returned 'ECONNREFUSED'. Please check the 'http.proxy' setting."), { actions: [ - this.instantiationService.createInstance(OpenGlobalSettingsAction, OpenGlobalSettingsAction.ID, OpenGlobalSettingsAction.LABEL) + new Action('open user settings', localize('open user settings', "Open User Settings"), undefined, true, () => this.preferencesService.openGlobalSettings()) ] }); @@ -881,7 +878,6 @@ export class ServerExtensionsView extends ExtensionsListView { @IConfigurationService configurationService: IConfigurationService, @IWorkspaceContextService contextService: IWorkspaceContextService, @IExperimentService experimentService: IExperimentService, - @IWorkbenchThemeService workbenchThemeService: IWorkbenchThemeService, @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService, @IProductService productService: IProductService, @@ -889,9 +885,10 @@ export class ServerExtensionsView extends ExtensionsListView { @IMenuService menuService: IMenuService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, + @IPreferencesService preferencesService: IPreferencesService, ) { options.server = server; - super(options, notificationService, keybindingService, contextMenuService, instantiationService, themeService, extensionService, extensionsWorkbenchService, editorService, tipsService, telemetryService, configurationService, contextService, experimentService, workbenchThemeService, extensionManagementServerService, productService, contextKeyService, viewDescriptorService, menuService, openerService); + super(options, notificationService, keybindingService, contextMenuService, instantiationService, themeService, extensionService, extensionsWorkbenchService, editorService, tipsService, telemetryService, configurationService, contextService, experimentService, extensionManagementServerService, productService, contextKeyService, viewDescriptorService, menuService, openerService, preferencesService); this._register(onDidChangeTitle(title => this.updateTitle(title))); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index a885d7ec525..c2957c2c2a3 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -6,16 +6,17 @@ import 'vs/css!./media/extensionsWidgets'; import { Disposable, toDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { IExtension, IExtensionsWorkbenchService, IExtensionContainer } from 'vs/workbench/contrib/extensions/common/extensions'; -import { append, $, addClass } from 'vs/base/browser/dom'; +import { append, $, addClass, removeNode } from 'vs/base/browser/dom'; import * as platform from 'vs/base/common/platform'; import { localize } from 'vs/nls'; import { IExtensionTipsService, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ILabelService } from 'vs/platform/label/common/label'; import { extensionButtonProminentBackground, extensionButtonProminentForeground, ExtensionToolTipAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { EXTENSION_BADGE_REMOTE_BACKGROUND, EXTENSION_BADGE_REMOTE_FOREGROUND } from 'vs/workbench/common/theme'; import { Emitter, Event } from 'vs/base/common/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; export abstract class ExtensionWidget extends Disposable implements IExtensionContainer { private _extension: IExtension | null = null; @@ -222,17 +223,17 @@ export class RecommendationWidget extends ExtensionWidget { } const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason(); if (extRecommendations[this.extension.identifier.id.toLowerCase()]) { - this.element = append(this.parent, $('div.bookmark')); + this.element = append(this.parent, $('div.extension-bookmark')); const recommendation = append(this.element, $('.recommendation')); append(recommendation, $('span.codicon.codicon-star')); - const applyBookmarkStyle = (theme: ITheme) => { + const applyBookmarkStyle = (theme: IColorTheme) => { const bgColor = theme.getColor(extensionButtonProminentBackground); const fgColor = theme.getColor(extensionButtonProminentForeground); recommendation.style.borderTopColor = bgColor ? bgColor.toString() : 'transparent'; recommendation.style.color = fgColor ? fgColor.toString() : 'white'; }; - applyBookmarkStyle(this.themeService.getTheme()); - this.themeService.onThemeChange(applyBookmarkStyle, this, this.disposables); + applyBookmarkStyle(this.themeService.getColorTheme()); + this.themeService.onDidColorThemeChange(applyBookmarkStyle, this, this.disposables); this.tooltip = extRecommendations[this.extension.identifier.id.toLowerCase()].reasonText; } } @@ -285,7 +286,7 @@ class RemoteBadge extends Disposable { @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService ) { super(); - this.element = $('div.extension-remote-badge'); + this.element = $('div.extension-badge.extension-remote-badge'); this.render(); } @@ -296,13 +297,13 @@ class RemoteBadge extends Disposable { if (!this.element) { return; } - const bgColor = this.themeService.getTheme().getColor(EXTENSION_BADGE_REMOTE_BACKGROUND); - const fgColor = this.themeService.getTheme().getColor(EXTENSION_BADGE_REMOTE_FOREGROUND); + const bgColor = this.themeService.getColorTheme().getColor(EXTENSION_BADGE_REMOTE_BACKGROUND); + const fgColor = this.themeService.getColorTheme().getColor(EXTENSION_BADGE_REMOTE_FOREGROUND); this.element.style.backgroundColor = bgColor ? bgColor.toString() : ''; this.element.style.color = fgColor ? fgColor.toString() : ''; }; applyBadgeStyle(); - this._register(this.themeService.onThemeChange(() => applyBadgeStyle())); + this._register(this.themeService.onDidColorThemeChange(() => applyBadgeStyle())); if (this.tooltip) { const updateTitle = () => { @@ -315,3 +316,32 @@ class RemoteBadge extends Disposable { } } } + +export class ExtensionPackCountWidget extends ExtensionWidget { + + private element: HTMLElement | undefined; + + constructor( + private readonly parent: HTMLElement, + ) { + super(); + this.render(); + this._register(toDisposable(() => this.clear())); + } + + private clear(): void { + if (this.element) { + removeNode(this.element); + } + } + + render(): void { + this.clear(); + if (!this.extension || !this.extension.extensionPack.length) { + return; + } + this.element = append(this.parent, $('.extension-badge.extension-pack-badge')); + const countBadge = new CountBadge(this.element); + countBadge.setCount(this.extension.extensionPack.length); + } +} diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index f512f88db55..158c1444c27 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -630,8 +630,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return text.substr(0, 350); } - open(extension: IExtension, sideByside: boolean = false): Promise { - return Promise.resolve(this.editorService.openEditor(this.instantiationService.createInstance(ExtensionsInput, extension), undefined, sideByside ? SIDE_GROUP : ACTIVE_GROUP)); + open(extension: IExtension, { sideByside, preserveFocus, pinned }: { sideByside?: boolean, preserveFocus?: boolean, pinned?: boolean } = { sideByside: false, preserveFocus: false, pinned: false }): Promise { + return Promise.resolve(this.editorService.openEditor(this.instantiationService.createInstance(ExtensionsInput, extension), { preserveFocus, pinned }, sideByside ? SIDE_GROUP : ACTIVE_GROUP)); } private getPrimaryExtension(extensions: IExtension[]): IExtension { diff --git a/src/vs/workbench/contrib/extensions/browser/media/extension.css b/src/vs/workbench/contrib/extensions/browser/media/extension.css new file mode 100644 index 00000000000..7e293b7776e --- /dev/null +++ b/src/vs/workbench/contrib/extensions/browser/media/extension.css @@ -0,0 +1,173 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.extension-bookmark-container { + position: relative; +} + +.extension-bookmark-container > .extension-bookmark { + position: absolute; +} + +.extension-list-item { + box-sizing: border-box; + width: 100%; + height: 100%; + padding: 0 0 0 16px; + overflow: hidden; + display: flex; +} + +.extension-list-item > .icon-container { + position: relative; +} + +.extension-list-item > .icon-container > .icon { + width: 42px; + height: 42px; + padding: 10px 14px 10px 0; + flex-shrink: 0; + object-fit: contain; +} + +.extension-list-item > .icon-container .extension-badge { + position: absolute; + bottom: 5px; + width: 22px; + height: 22px; + line-height: 22px; + border-radius: 20px; + display: flex; + align-items: center; + justify-content: center; +} + +.extension-list-item > .icon-container .extension-badge.extension-remote-badge { + right: 5px; +} + +.extension-list-item > .icon-container .extension-remote-badge .codicon { + color: currentColor; +} + +.extension-list-item > .details { + flex: 1; + padding: 4px 0; + overflow: hidden; +} + +.extension-list-item > .details > .header-container { + height: 19px; + display: flex; + overflow: hidden; + padding-right: 11px; +} + +.extension-list-item > .details > .header-container > .header { + display: flex; + align-items: baseline; + flex-wrap: nowrap; + overflow: hidden; + flex: 1; + min-width: 0; +} + +.extension-list-item > .details > .header-container > .header > .name { + font-weight: bold; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +.extension-list-item > .details > .header-container > .header > .version { + opacity: 0.85; + font-size: 80%; + padding-left: 6px; + min-width: fit-content; + min-width: -moz-fit-content; +} + +.extension-list-item > .details > .header-container > .header > .version { + flex: 1; +} + +.extension-list-item > .details > .header-container > .header > .install-count, +.extension-list-item > .details > .header-container > .header > .ratings { + display: flex; + align-items: center; +} + +.extension-list-item > .details > .header-container > .header > .install-count:not(:empty) { + font-size: 80%; + margin: 0 6px; +} + +.extension-list-item > .details > .header-container > .header .codicon { + font-size: 120%; + margin-right: 2px; + -webkit-mask: inherit; +} + +.extension-list-item > .details > .header-container > .header > .ratings { + text-align: right; +} + +.extension-list-item > .details > .header-container > .header > .extension-remote-badge-container { + margin-left: 6px; + display: none; +} + +.extension-list-item > .details > .header-container > .header .extension-remote-badge .codicon { + margin-right: 0; +} + +.extension-list-item > .details > .header-container > .header .extension-remote-badge { + width: 14px; + height: 14px; + line-height: 14px; + border-radius: 20px; + text-align: center; + display: flex; + align-items: center; + justify-content: center; +} + +.extension-list-item > .details > .header-container > .header .extension-remote-badge > .codicon { + font-size: 12px; + color: currentColor; +} + +.extension-list-item > .details > .description { + padding-right: 11px; +} + +.extension-list-item > .details > .footer { + display: flex; + justify-content: flex-end; + padding-right: 7px; + height: 18px; + overflow: hidden; +} + +.extension-list-item > .details > .footer > .author { + flex: 1; + font-size: 90%; + opacity: 0.9; + font-weight: 600; +} + +.extension-list-item .ellipsis { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.extension-list-item > .details > .footer > .monaco-action-bar > .actions-container { + flex-wrap: wrap-reverse; +} + +.extension-list-item > .details > .footer > .monaco-action-bar > .actions-container .extension-action.label { + max-width: 150px; +} diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css b/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css index 7bafd26dfbf..e08ff8e8b8e 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css @@ -12,6 +12,17 @@ text-overflow: ellipsis; } +.monaco-action-bar .action-item .action-label.extension-action.text, +.monaco-action-bar .action-item .action-label.extension-action.label { + line-height: 14px; + margin-top: 2px; +} + +.monaco-action-bar .action-item .action-label.extension-action.icon { + height: 18px; + width: 10px; +} + .monaco-action-bar .action-item .action-label.extension-action.multiserver.install:after, .monaco-action-bar .action-item .action-label.extension-action.multiserver.update:after, .monaco-action-bar .action-item .action-label.extension-action.extension-editor-dropdown-action.dropdown:after { @@ -20,6 +31,7 @@ font-size: 80%; } +.monaco-action-bar .action-item.disabled .action-label.extension-action.hide, .monaco-action-bar .action-item.disabled .action-label.extension-action.ignore, .monaco-action-bar .action-item.disabled .action-label.extension-action.undo-ignore, .monaco-action-bar .action-item.disabled .action-label.extension-action.install:not(.installing), @@ -31,6 +43,7 @@ .monaco-action-bar .action-item.disabled .action-label.disable-status.hide, .monaco-action-bar .action-item.disabled .action-label.system-disable.hide, .monaco-action-bar .action-item.disabled .action-label.extension-status-label.hide, +.monaco-action-bar .action-item .action-label.extension-action.manage.hide, .monaco-action-bar .action-item.disabled .action-label.malicious-status.not-malicious { display: none; } @@ -40,70 +53,12 @@ padding-right: 4px; } -.monaco-action-bar .action-item .action-label.disable-status, -.monaco-action-bar .action-item .action-label.malicious-status, -.monaco-action-bar .action-item.disabled .action-label.extension-status-label { - opacity: 0.9; - line-height: initial; - padding: 0 5px; +.monaco-action-bar .action-item.disabled .action-label.extension-action { + opacity: 1; + pointer-events: none; } -.monaco-action-bar .action-item .action-label.disable-status, -.monaco-action-bar .action-item .action-label.malicious-status { - border-radius: 4px; - color: inherit; - background-color: transparent; +.monaco-action-bar .action-item.disabled .action-label.extension-action.text { + opacity: 0.9; font-style: italic; } - -.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .action-label.disable-status { - margin-left: 0; - margin-top: 6px; - padding-left: 0; -} - -.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .action-label.system-disable { - margin-right: 0.15em; -} - -.extensions-viewlet>.extensions .extension>.details>.footer>.monaco-action-bar .action-item .action-label.system-disable.codicon-info, -.extensions-viewlet>.extensions .extension>.details>.footer>.monaco-action-bar .action-item .action-label.system-disable.codicon-warning { - margin-top: 0.25em; - margin-left: 0.1em; -} - -.monaco-action-bar .action-item .action-label.system-disable.codicon { - opacity: 1; - height: 18px; - width: 10px; -} - -.extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.extension-status-label, -.extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.disable-status, -.extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.malicious-status { - font-weight: normal; -} - -.extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.extension-status-label:hover, -.extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.disable-status:hover, -.extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.malicious-status:hover { - opacity: 0.9; -} - -.extensions-viewlet>.extensions .extension>.details>.footer>.monaco-action-bar .action-item .action-label.extension-action.manage.hide { - display: none; -} - -.extensions-viewlet>.extensions .extension>.details>.footer>.monaco-action-bar .action-item .action-label.extension-action.manage { - height: 18px; - width: 10px; - border: none; - color: inherit; - background: none; - outline-offset: 0px; - margin-top: 0.25em; -} - -.extension-editor > .header.recommended > .details > .recommendation > .monaco-action-bar .actions-container { - justify-content: flex-start; -} diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index bfa10d1e062..36d89e741bc 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -154,16 +154,32 @@ justify-content: flex-start; } -.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .action-label { - font-weight: 600; - margin: 4px 8px 4px 0px; - padding: 1px 6px; +.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .extension-action { + margin-right: 8px; } -.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .extension-action { +.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .extension-action.label { + font-weight: 600; + padding: 1px 6px; max-width: 300px; } +.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .action-label.system-disable { + margin-right: 0; +} + +.extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.extension-status-label, +.extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.disable-status, +.extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.malicious-status { + font-weight: normal; +} + +.extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.extension-status-label:hover, +.extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.disable-status:hover, +.extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.malicious-status:hover { + opacity: 0.9; +} + .extension-editor > .header > .details > .subtext-container { display: block; float: left; @@ -183,6 +199,7 @@ margin-top: 5px; margin-right: 4px; } + .extension-editor > .header > .details > .subtext-container > .monaco-action-bar .action-label { margin-top: 4px; margin-left: 4px; @@ -190,6 +207,10 @@ padding-bottom: 2px; } +.extension-editor > .header.recommended > .details > .recommendation > .monaco-action-bar .actions-container { + justify-content: flex-start; +} + .extension-editor > .body { flex: 1; overflow: hidden; @@ -247,6 +268,72 @@ margin-left: 20px; } +.extension-editor > .body > .content > .extension-pack-readme { + height: 100%; +} + +.extension-editor > .body > .content > .extension-pack-readme > .extension-pack { + height: 224px; + padding-left: 20px; +} + +.extension-editor > .body > .content > .extension-pack-readme.one-row > .extension-pack { + height: 142px; +} + +.extension-editor > .body > .content > .extension-pack-readme.two-rows > .extension-pack { + height: 224px; +} + +.extension-editor > .body > .content > .extension-pack-readme.three-rows > .extension-pack { + height: 306px; +} + +.extension-editor > .body > .content > .extension-pack-readme.more-rows > .extension-pack { + height: 326px; +} + +.extension-editor > .body > .content > .extension-pack-readme.one-row > .readme-content { + height: calc(100% - 142px); +} + +.extension-editor > .body > .content > .extension-pack-readme.two-rows > .readme-content { + height: calc(100% - 224px); +} + +.extension-editor > .body > .content > .extension-pack-readme.three-rows > .readme-content { + height: calc(100% - 306px); +} + +.extension-editor > .body > .content > .extension-pack-readme.more-rows > .readme-content { + height: calc(100% - 326px); +} + +.extension-editor > .body > .content > .extension-pack-readme > .extension-pack > .header, +.extension-editor > .body > .content > .extension-pack-readme > .extension-pack > .footer { + margin-bottom: 10px; + margin-right: 30px; + font-weight: bold; + font-size: 120%; + border-bottom: 1px solid rgba(128, 128, 128, 0.22); + padding: 4px 6px; + line-height: 22px; +} + +.extension-editor > .body > .content > .extension-pack-readme > .extension-pack > .extension-pack-content { + height: calc(100% - 60px); +} + +.extension-editor > .body > .content > .extension-pack-readme > .extension-pack > .extension-pack-content > .monaco-scrollable-element { + height: 100%; +} + +.extension-editor > .body > .content .extension-pack-readme > .extension-pack > .extension-pack-content > .monaco-scrollable-element > .subcontent { + height: 100%; + overflow-y: scroll; + box-sizing: border-box; +} + .extension-editor > .body > .content > .monaco-scrollable-element > .subcontent { height: 100%; padding: 20px; @@ -398,3 +485,31 @@ font-weight: 600; opacity: 0.6; } + +.extension-editor .extensions-grid-view { + display: flex; + flex-wrap: wrap; +} + +.extension-editor .extensions-grid-view > .extension-container { + margin: 0 10px 20px 0; +} + +.extension-editor .extensions-grid-view .extension-list-item { + cursor: default; +} + +.extension-editor .extensions-grid-view .extension-list-item > .details .header > .name { + cursor: pointer; +} + +.extension-editor .extensions-grid-view .extension-list-item > .details > .header-container > .header > .version, +.extension-editor .extensions-grid-view .extension-list-item > .details > .header-container > .header > .ratings, +.extension-editor .extensions-grid-view .extension-list-item > .details > .header-container > .header > .install-count { + display: none; +} + +.extension-editor .extensions-grid-view > .extension-container:focus > .extension-list-item > .details .header > .name, +.extension-editor .extensions-grid-view > .extension-container:focus > .extension-list-item > .details .header > .name:hover { + text-decoration: underline; +} diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css index f61faa096cb..44db7a50ae8 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css @@ -28,6 +28,7 @@ height: calc(100% - 41px); } +.extensions-viewlet > .extensions .extension-view-header .count-badge-wrapper, .extensions-viewlet > .extensions .extension-view-header .monaco-action-bar { margin-right: 4px; } @@ -73,232 +74,47 @@ flex-shrink: 0; } -.extensions-viewlet > .extensions .monaco-list-row > .bookmark { - display: inline-block; - height: 20px; - width: 20px; -} - -.extensions-viewlet > .extensions .monaco-list-row > .bookmark > .recommendation { - border-right: 20px solid transparent; - border-top: 20px solid; - box-sizing: border-box; -} - -.extensions-viewlet > .extensions .monaco-list-row > .bookmark > .recommendation > .codicon { +.extensions-viewlet > .extensions .extension-list-item { position: absolute; - top: 1px; - left: 1px; - color: inherit; - font-size: 80%; } -.extensions-viewlet > .extensions .extension { - box-sizing: border-box; - width: 100%; - height: 100%; - padding: 0 0 0 16px; - overflow: hidden; - display: flex; - position: absolute; - top: 0; -} - -.extensions-viewlet > .extensions .extension.loading { +.extensions-viewlet > .extensions .extension-list-item.loading { background: url('loading.svg') center center no-repeat; } -.extensions-viewlet > .extensions .monaco-list-row > .extension > .icon-container { - position: relative; -} - -.extensions-viewlet > .extensions .extension > .icon-container > .icon { - width: 42px; - height: 42px; - padding: 10px 14px 10px 0; - flex-shrink: 0; - object-fit: contain; -} - -.extensions-viewlet > .extensions .monaco-list-row > .extension > .icon-container .extension-remote-badge { - position: absolute; - right: 5px; - bottom: 5px; - width: 22px; - height: 22px; - line-height: 22px; - border-radius: 20px; - display: flex; - align-items: center; - justify-content: center; -} - -.extensions-viewlet > .extensions .monaco-list-row > .extension > .icon-container .extension-remote-badge .codicon { - color: currentColor; -} - -.extensions-viewlet > .extensions .monaco-list-row > .extension > .details > .header-container > .header > .extension-remote-badge-container { - margin-left: 6px; -} - -.extensions-viewlet > .extensions .monaco-list-row > .extension > .details > .header-container > .header .extension-remote-badge { - width: 14px; - height: 14px; - line-height: 14px; - border-radius: 20px; - text-align: center; - display: flex; - align-items: center; - justify-content: center; -} - -.extensions-viewlet > .extensions .monaco-list-row > .extension > .details > .header-container > .header .extension-remote-badge > .codicon { - font-size: 12px; - color: currentColor; -} - -.extensions-viewlet.narrow > .extensions .extension > .icon-container, -.extensions-viewlet > .extensions .extension.loading > .icon-container { +.extensions-viewlet.narrow > .extensions .extension-list-item > .icon-container, +.extensions-viewlet > .extensions .extension-list-item.loading > .icon-container { display: none; } -.extensions-viewlet > .extensions .extension > .details { - flex: 1; - padding: 4px 0; - overflow: hidden; -} - -.extensions-viewlet > .extensions .extension > .details > .header-container { - height: 19px; - display: flex; - overflow: hidden; - padding-right: 11px; -} - -.extensions-viewlet > .extensions .extension > .details > .header-container > .header { - display: flex; - align-items: baseline; - flex-wrap: nowrap; - overflow: hidden; - flex: 1; - min-width: 0; -} - -.extensions-viewlet > .extensions .extension > .details > .header-container > .header > .name { - font-weight: bold; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; -} - -.extensions-viewlet > .extensions .extension > .details > .header-container > .header > .version { - opacity: 0.85; - font-size: 80%; - padding-left: 6px; - min-width: fit-content; - min-width: -moz-fit-content; -} - -.extensions-viewlet:not(.narrow) > .extensions .extension > .details > .header-container > .header > .version { - flex: 1; -} - -.extensions-viewlet > .extensions .extension > .details > .header-container > .header > .install-count, -.extensions-viewlet > .extensions .extension > .details > .header-container > .header > .ratings { - display: flex; - align-items: center; -} - -.extensions-viewlet > .extensions .extension > .details > .header-container > .header > .install-count:not(:empty) { - font-size: 80%; - margin: 0 6px; -} - -.extensions-viewlet > .extensions .extension > .details > .header-container > .header .codicon { - font-size: 120%; - margin-right: 2px; - -webkit-mask: inherit; -} - -.extensions-viewlet>.extensions .extension>.details>.header-container>.header .extension-remote-badge .codicon { - margin-right: 0; -} - -.extensions-viewlet > .extensions .extension > .details > .header-container > .header > .ratings { - text-align: right; -} - -.extensions-viewlet:not(.narrow) > .extensions .extension > .details > .header-container > .header > .extension-remote-badge-container, -.extensions-viewlet.narrow > .extensions .extension > .details > .header-container > .header > .ratings, -.extensions-viewlet.narrow > .extensions .extension > .details > .header-container > .header > .install-count { +.extensions-viewlet:not(.narrow) > .extensions .extension-list-item > .details > .header-container > .header > .extension-remote-badge-container, +.extensions-viewlet.narrow > .extensions .extension-list-item > .details > .header-container > .header > .ratings, +.extensions-viewlet.narrow > .extensions .extension-list-item > .details > .header-container > .header > .install-count { display: none; } -.extensions-viewlet > .extensions .extension > .details > .description { - padding-right: 11px; -} - -.extensions-viewlet > .extensions .extension > .details > .footer { - display: flex; - justify-content: flex-end; - padding-right: 7px; - height: 24px; - overflow: hidden; -} - -.extensions-viewlet > .extensions .extension > .details > .footer > .author { - flex: 1; - font-size: 90%; - opacity: 0.9; - font-weight: 600; -} - -.extensions-viewlet > .extensions .selected .extension > .details > .footer > .author, -.extensions-viewlet > .extensions .selected.focused .extension > .details > .footer > .author { +.extensions-viewlet > .extensions .selected .extension-list-item > .details > .footer > .author, +.extensions-viewlet > .extensions .selected.focused .extension-list-item > .details > .footer > .author { opacity: 1; } -.extensions-viewlet > .extensions .extension > .details > .footer > .monaco-action-bar > .actions-container { - flex-wrap: wrap-reverse; -} - -.extensions-viewlet > .extensions .extension > .details > .footer > .monaco-action-bar > .actions-container .extension-action { - max-width: 150px; -} - -.extensions-viewlet.narrow > .extensions .extension > .details > .footer > .monaco-action-bar > .actions-container .extension-action { +.extensions-viewlet.narrow > .extensions .extension-list-item > .details > .footer > .monaco-action-bar > .actions-container .extension-action { max-width: 100px; } -.extensions-viewlet > .extensions .extension > .details > .footer > .monaco-action-bar .action-label { - margin-top: 0.3em; - margin-left: 0.3em; - line-height: 14px; -} - -.extensions-viewlet > .extensions .extension > .details > .footer > .monaco-action-bar .action-label:not(:empty) { - opacity: 0.9; -} - .vs .extensions-viewlet > .extensions .monaco-list-row.disabled > .bookmark, .vs-dark .extensions-viewlet > .extensions .monaco-list-row.disabled > .bookmark, -.vs .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension > .icon-container > .icon, -.vs-dark .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension > .icon-container > .icon, -.vs .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension > .details > .header-container, -.vs-dark .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension > .details > .header-container, -.vs .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension > .details > .description, -.vs-dark .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension > .details > .description, -.vs .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension > .details > .footer > .author, -.vs-dark .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension > .details > .footer > .author { +.vs .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension-list-item > .icon-container > .icon, +.vs-dark .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension-list-item > .icon-container > .icon, +.vs .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension-list-item > .details > .header-container, +.vs-dark .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension-list-item > .details > .header-container, +.vs .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension-list-item > .details > .description, +.vs-dark .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension-list-item > .details > .description, +.vs .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension-list-item > .details > .footer > .author, +.vs-dark .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension-list-item > .details > .footer > .author { opacity: 0.5; } -.extensions-viewlet > .extensions .extension .ellipsis { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} - .extensions-badge.progress-badge > .badge-content { background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNCIgaGVpZ2h0PSIxNCIgdmlld0JveD0iMiAyIDE0IDE0IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDIgMiAxNCAxNCI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTkgMTZjLTMuODYgMC03LTMuMTQtNy03czMuMTQtNyA3LTdjMy44NTkgMCA3IDMuMTQxIDcgN3MtMy4xNDEgNy03IDd6bTAtMTIuNmMtMy4wODggMC01LjYgMi41MTMtNS42IDUuNnMyLjUxMiA1LjYgNS42IDUuNiA1LjYtMi41MTIgNS42LTUuNi0yLjUxMi01LjYtNS42LTUuNnptMy44NiA3LjFsLTMuMTYtMS44OTZ2LTMuODA0aC0xLjR2NC41OTZsMy44NCAyLjMwNS43Mi0xLjIwMXoiLz48L3N2Zz4="); background-position: center center; diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css b/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css index 7b0d3cb682e..97beafd49fe 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css @@ -32,3 +32,24 @@ .extension-ratings .codicon-star-empty { opacity: .4; } + +.extension-bookmark { + display: inline-block; + height: 20px; + width: 20px; +} + +.extension-bookmark > .recommendation { + border-right: 20px solid transparent; + border-top: 20px solid; + box-sizing: border-box; + position: relative; +} + +.extension-bookmark > .recommendation > .codicon { + position: absolute; + bottom: 9px; + left: 1px; + color: inherit; + font-size: 80%; +} diff --git a/src/vs/workbench/contrib/extensions/browser/media/profile-start-dark.svg b/src/vs/workbench/contrib/extensions/browser/media/profile-start-dark.svg deleted file mode 100644 index a60d77cd37a..00000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/profile-start-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/profile-start-light.svg b/src/vs/workbench/contrib/extensions/browser/media/profile-start-light.svg deleted file mode 100644 index f541ed4d519..00000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/profile-start-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/profile-stop-dark.svg b/src/vs/workbench/contrib/extensions/browser/media/profile-stop-dark.svg deleted file mode 100644 index a0948780ee4..00000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/profile-stop-dark.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/profile-stop-light.svg b/src/vs/workbench/contrib/extensions/browser/media/profile-stop-light.svg deleted file mode 100644 index d9222c3c312..00000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/profile-stop-light.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/save-dark.svg b/src/vs/workbench/contrib/extensions/browser/media/save-dark.svg deleted file mode 100644 index 8acad37a99c..00000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/save-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/save-light.svg b/src/vs/workbench/contrib/extensions/browser/media/save-light.svg deleted file mode 100644 index 529e489a816..00000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/save-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/start-dark.svg b/src/vs/workbench/contrib/extensions/browser/media/start-dark.svg deleted file mode 100644 index 8b0a58eca9b..00000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/start-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/extensions/browser/media/start-light.svg b/src/vs/workbench/contrib/extensions/browser/media/start-light.svg deleted file mode 100644 index 2563bfa114b..00000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/start-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index ade46093ea2..fae5943aaf6 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -86,7 +86,7 @@ export interface IExtensionsWorkbenchService { installVersion(extension: IExtension, version: string): Promise; reinstall(extension: IExtension): Promise; setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise; - open(extension: IExtension, sideByside?: boolean): Promise; + open(extension: IExtension, options?: { sideByside?: boolean, preserveFocus?: boolean, pinned?: boolean }): Promise; checkForUpdates(): Promise; } diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts index 85b0c9e3147..0626bbb8cd8 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts @@ -18,10 +18,8 @@ import { RuntimeExtensionsEditor, ShowRuntimeExtensionsAction, IExtensionHostPro import { EditorInput, IEditorInputFactory, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, ActiveEditorContext } from 'vs/workbench/common/editor'; import { ExtensionHostProfileService } from 'vs/workbench/contrib/extensions/electron-browser/extensionProfileService'; import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput'; -import { URI } from 'vs/base/common/uri'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionsAutoProfiler } from 'vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler'; -import { registerAndGetAmdImageURL } from 'vs/base/common/amd'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { OpenExtensionsFolderAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions'; import { ExtensionsLabel } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -106,8 +104,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { id: DebugExtensionHostAction.ID, title: DebugExtensionHostAction.LABEL, icon: { - dark: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/extensions/browser/media/start-dark.svg`)), - light: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/extensions/browser/media/start-light.svg`)), + id: 'codicon/debug-start' } }, group: 'navigation', @@ -119,8 +116,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { id: StartExtensionHostProfileAction.ID, title: StartExtensionHostProfileAction.LABEL, icon: { - dark: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/extensions/browser/media/profile-start-dark.svg`)), - light: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/extensions/browser/media/profile-start-light.svg`)), + id: 'codicon/circle-filled' } }, group: 'navigation', @@ -132,8 +128,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { id: StopExtensionHostProfileAction.ID, title: StopExtensionHostProfileAction.LABEL, icon: { - dark: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/extensions/browser/media/profile-stop-dark.svg`)), - light: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/extensions/browser/media/profile-stop-light.svg`)), + id: 'codicon/debug-stop' } }, group: 'navigation', @@ -145,8 +140,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { id: SaveExtensionHostProfileAction.ID, title: SaveExtensionHostProfileAction.LABEL, icon: { - dark: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/extensions/browser/media/save-dark.svg`)), - light: URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/extensions/browser/media/save-light.svg`)), + id: 'codicon/save-all' }, precondition: CONTEXT_EXTENSION_HOST_PROFILE_RECORDED }, diff --git a/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css b/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css index 47a318e4411..12d9ceac77a 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css +++ b/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css @@ -40,3 +40,53 @@ .runtime-extensions-editor .monaco-action-bar .actions-container { justify-content: left; } + +.runtime-extensions-editor .extension > .icon-container { + position: relative; +} + +.runtime-extensions-editor .extension > .icon-container > .icon { + width: 42px; + height: 42px; + padding: 10px 14px 10px 0; + flex-shrink: 0; + object-fit: contain; +} + +.runtime-extensions-editor .extension > .icon-container .extension-remote-badge .codicon { + color: currentColor; +} + +.vs .runtime-extensions-editor .extension > .icon-container > .icon, +.vs-dark .runtime-extensions-editor .extension > .icon-container > .icon { + opacity: 0.5; +} + +.runtime-extensions-editor .extension > .desc > .header-container { + display: flex; + overflow: hidden; +} + +.runtime-extensions-editor .extension > .desc > .header-container > .header { + display: flex; + align-items: baseline; + flex-wrap: nowrap; + overflow: hidden; + flex: 1; + min-width: 0; +} + +.runtime-extensions-editor .extension > .desc > .header-container > .header > .name { + font-weight: bold; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +.runtime-extensions-editor .extension > .desc > .header-container > .header > .version { + opacity: 0.85; + font-size: 80%; + padding-left: 6px; + min-width: fit-content; + min-width: -moz-fit-content; +} diff --git a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts index e3450b958f5..abc4464ca07 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -47,6 +47,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; +import { domEvent } from 'vs/base/browser/event'; export const IExtensionHostProfileService = createDecorator('extensionHostProfileService'); export const CONTEXT_PROFILE_SESSION_STATE = new RawContextKey('profileSessionState', 'none'); @@ -257,7 +258,9 @@ export class RuntimeExtensionsEditor extends BaseEditor { interface IRuntimeExtensionTemplateData { root: HTMLElement; element: HTMLElement; + icon: HTMLImageElement; name: HTMLElement; + version: HTMLElement; msgContainer: HTMLElement; actionbar: ActionBar; activationTime: HTMLElement; @@ -270,9 +273,14 @@ export class RuntimeExtensionsEditor extends BaseEditor { templateId: TEMPLATE_ID, renderTemplate: (root: HTMLElement): IRuntimeExtensionTemplateData => { const element = append(root, $('.extension')); + const iconContainer = append(element, $('.icon-container')); + const icon = append(iconContainer, $('img.icon')); const desc = append(element, $('div.desc')); - const name = append(desc, $('div.name')); + const headerContainer = append(desc, $('.header-container')); + const header = append(headerContainer, $('.header')); + const name = append(header, $('div.name')); + const version = append(header, $('span.version')); const msgContainer = append(desc, $('div.msg')); @@ -289,13 +297,15 @@ export class RuntimeExtensionsEditor extends BaseEditor { return { root, element, + icon, name, + version, actionbar, activationTime, profileTime, msgContainer, disposables, - elementDisposables: [] + elementDisposables: [], }; }, @@ -305,7 +315,18 @@ export class RuntimeExtensionsEditor extends BaseEditor { toggleClass(data.root, 'odd', index % 2 === 1); + const onError = Event.once(domEvent(data.icon, 'error')); + onError(() => data.icon.src = element.marketplaceInfo.iconUrlFallback, null, data.elementDisposables); + data.icon.src = element.marketplaceInfo.iconUrl; + + if (!data.icon.complete) { + data.icon.style.visibility = 'hidden'; + data.icon.onload = () => data.icon.style.visibility = 'inherit'; + } else { + data.icon.style.visibility = 'inherit'; + } data.name.textContent = element.marketplaceInfo ? element.marketplaceInfo.displayName : element.description.displayName || ''; + data.version.textContent = element.description.version; const activationTimes = element.status.activationTimes!; let syncTime = activationTimes.codeLoadingTime + activationTimes.activateCallTime; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index 52f71ee6229..a0e6f2f0b82 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -27,7 +27,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { TestContextService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; import { TestSharedProcessService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; @@ -39,7 +39,7 @@ import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browse import { ExtensionIdentifier, IExtensionContributions, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { ILabelService } from 'vs/platform/label/common/label'; +import { ILabelService, IFormatterChangeEvent } from 'vs/platform/label/common/label'; import { ExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService'; import { IProductService } from 'vs/platform/product/common/productService'; import { Schemas } from 'vs/base/common/network'; @@ -92,7 +92,7 @@ suite('ExtensionsActions Test', () => { }()); instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - instantiationService.stub(ILabelService, { onDidChangeFormatters: new Emitter().event }); + instantiationService.stub(ILabelService, { onDidChangeFormatters: new Emitter().event }); instantiationService.set(IExtensionTipsService, instantiationService.createInstance(ExtensionTipsService)); instantiationService.stub(IURLService, URLService); @@ -130,7 +130,7 @@ suite('ExtensionsActions Test', () => { testObject.extension = paged.firstPage[0]; assert.ok(!testObject.enabled); assert.equal('Install', testObject.label); - assert.equal('extension-action prominent install', testObject.class); + assert.equal('extension-action label prominent install', testObject.class); }); }); }); @@ -148,7 +148,7 @@ suite('ExtensionsActions Test', () => { assert.ok(!testObject.enabled); assert.equal('Installing', testObject.label); - assert.equal('extension-action install installing', testObject.class); + assert.equal('extension-action label install installing', testObject.class); }); }); @@ -215,7 +215,7 @@ suite('ExtensionsActions Test', () => { uninstallEvent.fire(local.identifier); assert.ok(!testObject.enabled); assert.equal('Uninstalling', testObject.label); - assert.equal('extension-action uninstall uninstalling', testObject.class); + assert.equal('extension-action label uninstall uninstalling', testObject.class); }); }); @@ -230,7 +230,7 @@ suite('ExtensionsActions Test', () => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); assert.equal('Uninstall', testObject.label); - assert.equal('extension-action uninstall', testObject.class); + assert.equal('extension-action label uninstall', testObject.class); }); }); @@ -245,7 +245,7 @@ suite('ExtensionsActions Test', () => { testObject.extension = extensions[0]; assert.ok(!testObject.enabled); assert.equal('Uninstall', testObject.label); - assert.equal('extension-action uninstall', testObject.class); + assert.equal('extension-action label uninstall', testObject.class); }); }); @@ -281,7 +281,7 @@ suite('ExtensionsActions Test', () => { assert.ok(testObject.enabled); assert.equal('Uninstall', testObject.label); - assert.equal('extension-action uninstall', testObject.class); + assert.equal('extension-action label uninstall', testObject.class); }); }); @@ -290,7 +290,7 @@ suite('ExtensionsActions Test', () => { instantiationService.createInstance(ExtensionContainers, [testObject]); assert.ok(!testObject.enabled); - assert.equal('extension-action prominent install no-extension', testObject.class); + assert.equal('extension-action label prominent install no-extension', testObject.class); }); test('Test CombinedInstallAction when extension is system extension', () => { @@ -303,7 +303,7 @@ suite('ExtensionsActions Test', () => { .then(extensions => { testObject.extension = extensions[0]; assert.ok(!testObject.enabled); - assert.equal('extension-action prominent install no-extension', testObject.class); + assert.equal('extension-action label prominent install no-extension', testObject.class); }); }); @@ -319,7 +319,7 @@ suite('ExtensionsActions Test', () => { testObject.extension = paged.firstPage[0]; assert.ok(testObject.enabled); assert.equal('Install', testObject.label); - assert.equal('extension-action prominent install', testObject.class); + assert.equal('extension-action label prominent install', testObject.class); }); }); @@ -334,7 +334,7 @@ suite('ExtensionsActions Test', () => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); assert.equal('Uninstall', testObject.label); - assert.equal('extension-action uninstall', testObject.class); + assert.equal('extension-action label uninstall', testObject.class); }); }); @@ -351,7 +351,7 @@ suite('ExtensionsActions Test', () => { assert.ok(!testObject.enabled); assert.equal('Installing', testObject.label); - assert.equal('extension-action install installing', testObject.class); + assert.equal('extension-action label install installing', testObject.class); }); }); @@ -370,7 +370,7 @@ suite('ExtensionsActions Test', () => { installEvent.fire({ identifier: gallery.identifier, gallery }); assert.ok(!testObject.enabled); assert.equal('Installing', testObject.label); - assert.equal('extension-action install installing', testObject.class); + assert.equal('extension-action label install installing', testObject.class); }); }); @@ -386,7 +386,7 @@ suite('ExtensionsActions Test', () => { uninstallEvent.fire(local.identifier); assert.ok(!testObject.enabled); assert.equal('Uninstalling', testObject.label); - assert.equal('extension-action uninstall uninstalling', testObject.class); + assert.equal('extension-action label uninstall uninstalling', testObject.class); }); }); @@ -498,7 +498,7 @@ suite('ExtensionsActions Test', () => { .then(extensions => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('extension-action manage codicon-gear', testObject.class); + assert.equal('extension-action icon manage codicon-gear', testObject.class); assert.equal('', testObject.tooltip); }); }); @@ -513,7 +513,7 @@ suite('ExtensionsActions Test', () => { .then(page => { testObject.extension = page.firstPage[0]; assert.ok(!testObject.enabled); - assert.equal('extension-action manage codicon-gear hide', testObject.class); + assert.equal('extension-action icon manage codicon-gear hide', testObject.class); assert.equal('', testObject.tooltip); }); }); @@ -530,7 +530,7 @@ suite('ExtensionsActions Test', () => { installEvent.fire({ identifier: gallery.identifier, gallery }); assert.ok(!testObject.enabled); - assert.equal('extension-action manage codicon-gear hide', testObject.class); + assert.equal('extension-action icon manage codicon-gear hide', testObject.class); assert.equal('', testObject.tooltip); }); }); @@ -548,7 +548,7 @@ suite('ExtensionsActions Test', () => { didInstallEvent.fire({ identifier: gallery.identifier, gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }); assert.ok(testObject.enabled); - assert.equal('extension-action manage codicon-gear', testObject.class); + assert.equal('extension-action icon manage codicon-gear', testObject.class); assert.equal('', testObject.tooltip); }); }); @@ -563,7 +563,7 @@ suite('ExtensionsActions Test', () => { .then(extensions => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('extension-action manage codicon-gear', testObject.class); + assert.equal('extension-action icon manage codicon-gear', testObject.class); assert.equal('', testObject.tooltip); }); }); @@ -580,7 +580,7 @@ suite('ExtensionsActions Test', () => { uninstallEvent.fire(local.identifier); assert.ok(!testObject.enabled); - assert.equal('extension-action manage codicon-gear', testObject.class); + assert.equal('extension-action icon manage codicon-gear', testObject.class); assert.equal('Uninstalling', testObject.tooltip); }); }); @@ -1549,7 +1549,7 @@ suite('ExtensionsActions Test', () => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); assert.equal('Install in remote', testObject.label); - assert.equal('extension-action prominent install', testObject.class); + assert.equal('extension-action label prominent install', testObject.class); }); test('Test remote install action when installing local workspace extension', async () => { @@ -1575,12 +1575,12 @@ suite('ExtensionsActions Test', () => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); assert.equal('Install in remote', testObject.label); - assert.equal('extension-action prominent install', testObject.class); + assert.equal('extension-action label prominent install', testObject.class); onInstallExtension.fire({ identifier: localWorkspaceExtension.identifier, gallery }); assert.ok(testObject.enabled); assert.equal('Installing', testObject.label); - assert.equal('extension-action install installing', testObject.class); + assert.equal('extension-action label install installing', testObject.class); }); test('Test remote install action when installing local workspace extension is finished', async () => { @@ -1608,12 +1608,12 @@ suite('ExtensionsActions Test', () => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); assert.equal('Install in remote', testObject.label); - assert.equal('extension-action prominent install', testObject.class); + assert.equal('extension-action label prominent install', testObject.class); onInstallExtension.fire({ identifier: localWorkspaceExtension.identifier, gallery }); assert.ok(testObject.enabled); assert.equal('Installing', testObject.label); - assert.equal('extension-action install installing', testObject.class); + assert.equal('extension-action label install installing', testObject.class); const installedExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); onDidInstallEvent.fire({ identifier: installedExtension.identifier, local: installedExtension, operation: InstallOperation.Install }); @@ -1639,7 +1639,7 @@ suite('ExtensionsActions Test', () => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); assert.equal('Install in remote', testObject.label); - assert.equal('extension-action prominent install', testObject.class); + assert.equal('extension-action label prominent install', testObject.class); }); test('Test remote install action is disabled when extension is not set', async () => { @@ -1856,7 +1856,7 @@ suite('ExtensionsActions Test', () => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); assert.equal('Install in remote', testObject.label); - assert.equal('extension-action prominent install', testObject.class); + assert.equal('extension-action label prominent install', testObject.class); }); test('Test remote install action is disabled if local language pack extension is uninstalled', async () => { @@ -1902,7 +1902,7 @@ suite('ExtensionsActions Test', () => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); assert.equal('Install Locally', testObject.label); - assert.equal('extension-action prominent install', testObject.class); + assert.equal('extension-action label prominent install', testObject.class); }); test('Test local install action when installing remote ui extension', async () => { @@ -1928,12 +1928,12 @@ suite('ExtensionsActions Test', () => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); assert.equal('Install Locally', testObject.label); - assert.equal('extension-action prominent install', testObject.class); + assert.equal('extension-action label prominent install', testObject.class); onInstallExtension.fire({ identifier: remoteUIExtension.identifier, gallery }); assert.ok(testObject.enabled); assert.equal('Installing', testObject.label); - assert.equal('extension-action install installing', testObject.class); + assert.equal('extension-action label install installing', testObject.class); }); test('Test local install action when installing remote ui extension is finished', async () => { @@ -1961,12 +1961,12 @@ suite('ExtensionsActions Test', () => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); assert.equal('Install Locally', testObject.label); - assert.equal('extension-action prominent install', testObject.class); + assert.equal('extension-action label prominent install', testObject.class); onInstallExtension.fire({ identifier: remoteUIExtension.identifier, gallery }); assert.ok(testObject.enabled); assert.equal('Installing', testObject.label); - assert.equal('extension-action install installing', testObject.class); + assert.equal('extension-action label install installing', testObject.class); const installedExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`) }); onDidInstallEvent.fire({ identifier: installedExtension.identifier, local: installedExtension, operation: InstallOperation.Install }); @@ -1992,7 +1992,7 @@ suite('ExtensionsActions Test', () => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); assert.equal('Install Locally', testObject.label); - assert.equal('extension-action prominent install', testObject.class); + assert.equal('extension-action label prominent install', testObject.class); }); test('Test local install action is disabled when extension is not set', async () => { @@ -2212,7 +2212,7 @@ suite('ExtensionsActions Test', () => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); assert.equal('Install Locally', testObject.label); - assert.equal('extension-action prominent install', testObject.class); + assert.equal('extension-action label prominent install', testObject.class); }); test('Test local install action is disabled if remote language pack extension is uninstalled', async () => { diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts index 5242e47633c..d8a87bcbf64 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts @@ -22,7 +22,8 @@ import { Emitter } from 'vs/base/common/event'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { TestContextService, TestLifecycleService, productService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestLifecycleService, productService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; import { TestSharedProcessService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index 7334f11625f..9fd9263ac72 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts @@ -27,7 +27,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { TestContextService, TestMenuService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestMenuService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestSharedProcessService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; @@ -46,6 +46,8 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { IMenuService } from 'vs/platform/actions/common/actions'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; +import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; suite('ExtensionsListView Tests', () => { @@ -137,6 +139,12 @@ suite('ExtensionsListView Tests', () => { instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage()); instantiationService.stubPromise(IExperimentService, 'getExperimentsByType', []); + instantiationService.stub(IViewDescriptorService, { + getViewLocation(): ViewContainerLocation { + return ViewContainerLocation.Sidebar; + } + }); + instantiationService.stub(IExtensionService, { getExtensions: (): Promise => { return Promise.resolve([ diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index 6d22875244d..f04352be125 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -27,7 +27,6 @@ import { IPager } from 'vs/base/common/paging'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { TestContextService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestSharedProcessService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; @@ -41,6 +40,7 @@ import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; suite('ExtensionsWorkbenchServiceTest', () => { diff --git a/src/vs/workbench/contrib/externalTerminal/node/TerminalHelper.scpt b/src/vs/workbench/contrib/externalTerminal/node/TerminalHelper.scpt index 1130091f600..145345e0025 100644 Binary files a/src/vs/workbench/contrib/externalTerminal/node/TerminalHelper.scpt and b/src/vs/workbench/contrib/externalTerminal/node/TerminalHelper.scpt differ diff --git a/src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.test.ts b/src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.test.ts index 821460d6eb7..8217f144e92 100644 --- a/src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.test.ts +++ b/src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.test.ts @@ -125,6 +125,28 @@ suite('ExternalTerminalService', () => { ); }); + test(`WinTerminalService - windows terminal should open workspace directory`, done => { + let testShell = 'wt'; + let testCwd = 'c:/foo'; + let mockSpawner = { + spawn: (command: any, args: any, opts: any) => { + // assert + equal(opts.cwd, 'C:/foo'); + done(); + return { on: (evt: any) => evt }; + } + }; + let testService = new WindowsExternalTerminalService(mockConfig); + (testService).spawnTerminal( + mockSpawner, + mockConfig, + testShell, + testCwd, + mockOnExit, + mockOnError + ); + }); + test(`MacTerminalService - uses terminal from configuration`, done => { let testCwd = 'path/to/workspace'; let mockSpawner = { diff --git a/src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.ts b/src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.ts index 1cada1d995e..d8ba1abc301 100644 --- a/src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.ts +++ b/src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.ts @@ -97,6 +97,10 @@ export class WindowsExternalTerminalService implements IExternalTerminalService cmdArgs.push('""'); } cmdArgs.push(exec); + // Add starting directory parameter for Windows Terminal (see #90734) + if (basename === 'wt' || basename === 'wt.exe') { + cmdArgs.push('-d .'); + } return new Promise((c, e) => { const env = cwd ? { cwd: cwd } : undefined; diff --git a/src/vs/workbench/contrib/externalTerminal/node/iTermHelper.scpt b/src/vs/workbench/contrib/externalTerminal/node/iTermHelper.scpt index 3f490f9d1ae..6ef0afa6204 100644 Binary files a/src/vs/workbench/contrib/externalTerminal/node/iTermHelper.scpt and b/src/vs/workbench/contrib/externalTerminal/node/iTermHelper.scpt differ diff --git a/src/vs/workbench/contrib/feedback/browser/feedback.ts b/src/vs/workbench/contrib/feedback/browser/feedback.ts index 60831f00f29..b98b91c634a 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedback.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedback.ts @@ -11,9 +11,9 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView import * as dom from 'vs/base/browser/dom'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity'; -import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { attachButtonStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; -import { editorWidgetBackground, editorWidgetForeground, widgetShadow, inputBorder, inputForeground, inputBackground, inputActiveOptionBorder, editorBackground, buttonBackground, contrastBorder, darken } from 'vs/platform/theme/common/colorRegistry'; +import { editorWidgetBackground, editorWidgetForeground, widgetShadow, inputBorder, inputForeground, inputBackground, inputActiveOptionBorder, editorBackground, textLinkForeground, contrastBorder, darken } from 'vs/platform/theme/common/colorRegistry'; import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; import { Button } from 'vs/base/browser/ui/button/button'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -21,6 +21,8 @@ import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } f import { IStatusbarService } from 'vs/workbench/services/statusbar/common/statusbar'; import { IProductService } from 'vs/platform/product/common/productService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { KeyCode } from 'vs/base/common/keyCodes'; export interface IFeedback { feedback: string; @@ -113,13 +115,19 @@ export class FeedbackDropdown extends Dropdown { dom.append(this.feedbackForm, dom.$('h2.title')).textContent = nls.localize("label.sendASmile", "Tweet us your feedback."); // Close Button (top right) - const closeBtn = dom.append(this.feedbackForm, dom.$('div.cancel')); + const closeBtn = dom.append(this.feedbackForm, dom.$('div.cancel.codicon.codicon-close')); closeBtn.tabIndex = 0; closeBtn.setAttribute('role', 'button'); closeBtn.title = nls.localize('close', "Close"); + disposables.add(dom.addDisposableListener(container, dom.EventType.KEY_DOWN, keyboardEvent => { + const standardKeyboardEvent = new StandardKeyboardEvent(keyboardEvent); + if (standardKeyboardEvent.keyCode === KeyCode.Escape) { + this.hide(); + } + })); disposables.add(dom.addDisposableListener(closeBtn, dom.EventType.MOUSE_OVER, () => { - const theme = this.themeService.getTheme(); + const theme = this.themeService.getColorTheme(); let darkenFactor: number | undefined; switch (theme.type) { case 'light': @@ -268,7 +276,8 @@ export class FeedbackDropdown extends Dropdown { this.sendButton = new Button(buttonsContainer); this.sendButton.enabled = false; this.sendButton.label = nls.localize('tweet', "Tweet"); - dom.addClass(this.sendButton.element, 'send'); + dom.prepend(this.sendButton.element, dom.$('span.codicon.codicon-twitter')); + dom.addClasses(this.sendButton.element, 'send'); this.sendButton.element.title = nls.localize('tweetFeedback', "Tweet Feedback"); disposables.add(attachButtonStyler(this.sendButton, this.themeService)); @@ -423,7 +432,7 @@ export class FeedbackDropdown extends Dropdown { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { // Sentiment Buttons const inputActiveOptionBorderColor = theme.getColor(inputActiveOptionBorder); @@ -432,7 +441,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { } // Links - const linkColor = theme.getColor(buttonBackground) || theme.getColor(contrastBorder); + const linkColor = theme.getColor(textLinkForeground) || theme.getColor(contrastBorder); if (linkColor) { collector.addRule(`.monaco-workbench .feedback-form .content .channels a { color: ${linkColor}; }`); } diff --git a/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts b/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts index b05d7d2d13a..cf4574859d9 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts @@ -14,6 +14,7 @@ import { localize } from 'vs/nls'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; +import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; class TwitterFeedbackService implements IFeedbackDelegate { @@ -63,7 +64,14 @@ export class FeedbackStatusbarConribution extends Disposable implements IWorkben if (productService.sendASmile) { this.entry = this._register(statusbarService.addEntry(this.getStatusEntry(), 'status.feedback', localize('status.feedback', "Tweet Feedback"), StatusbarAlignment.RIGHT, -100 /* towards the end of the right hand side */)); - CommandsRegistry.registerCommand('_feedback.open', () => this.toggleFeedback()); + CommandsRegistry.registerCommand('help.tweetFeedback', () => this.toggleFeedback()); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: 'help.tweetFeedback', + category: localize('help', "Help"), + title: localize('status.feedback', "Tweet Feedback") + } + }); } } @@ -96,9 +104,8 @@ export class FeedbackStatusbarConribution extends Disposable implements IWorkben return { text: '$(feedback)', tooltip: localize('status.feedback', "Tweet Feedback"), - command: '_feedback.open', + command: 'help.tweetFeedback', showBeak }; } - } diff --git a/src/vs/workbench/contrib/feedback/browser/media/close-dark.svg b/src/vs/workbench/contrib/feedback/browser/media/close-dark.svg deleted file mode 100644 index ce0e5896405..00000000000 --- a/src/vs/workbench/contrib/feedback/browser/media/close-dark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/feedback/browser/media/close.svg b/src/vs/workbench/contrib/feedback/browser/media/close.svg deleted file mode 100644 index fde34404d4e..00000000000 --- a/src/vs/workbench/contrib/feedback/browser/media/close.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/feedback/browser/media/feedback.css b/src/vs/workbench/contrib/feedback/browser/media/feedback.css index 3520a24c06a..c7620661268 100644 --- a/src/vs/workbench/contrib/feedback/browser/media/feedback.css +++ b/src/vs/workbench/contrib/feedback/browser/media/feedback.css @@ -91,6 +91,7 @@ padding: .5em; width: 22px; height: 22px; + line-height: 22px; border: none; cursor: pointer; } @@ -119,10 +120,6 @@ border: 1px solid transparent; } -.vs .monaco-workbench .feedback-form .cancel { - background: url('close.svg') center center no-repeat; -} - .monaco-workbench .feedback-form .form-buttons { display: flex; } @@ -138,15 +135,19 @@ .monaco-workbench .feedback-form .form-buttons .send { width: auto; - background-image: url('twitter.svg'); - background-position: 12px center; - background-size: 20px; - background-repeat: no-repeat; - padding: 8px 12px 8px 38px; + padding: 8px 12px; margin-left: auto; + display: flex; + align-items: center; transition: width 200ms ease-out; } +.monaco-workbench .feedback-form .form-buttons .send .codicon { + color: currentColor; + font-size: 20px; + padding-right: 8px; +} + .monaco-workbench .feedback-form .form-buttons .send.in-progress, .monaco-workbench .feedback-form .form-buttons .send:hover { background-color: #006BB3; @@ -179,11 +180,6 @@ font-family: inherit; } -.vs-dark .monaco-workbench .feedback-form .cancel, -.hc-black .monaco-workbench .feedback-form .cancel { - background: url('close-dark.svg') center center no-repeat; -} - .monaco-workbench .feedback-form .sentiment.smile { background-image: url('happy.svg'); background-position: center; @@ -196,19 +192,6 @@ background-repeat: no-repeat; } -.monaco-workbench .feedback-form .infotip { - background-image: url('info.svg'); - background-position: center; - background-repeat: no-repeat; - - height: 16px; - width: 16px; - display: inline-block; - vertical-align: text-bottom; - box-sizing: border-box; - margin-left: 5px; -} - /* High Contrast Theming */ .hc-black .monaco-workbench .feedback-form { outline: 2px solid #6fc3df; @@ -235,15 +218,3 @@ .hc-black .monaco-workbench .feedback-form .form-buttons .send:hover { background-color: #0C141F; } - - -.monaco-workbench .feedback-form .infotip { - background: none; -} - -.monaco-workbench .feedback-form .infotip:before { - content: url('info.svg'); - height: 16px; - width: 16px; - display: inline-block; -} \ No newline at end of file diff --git a/src/vs/workbench/contrib/feedback/browser/media/info.svg b/src/vs/workbench/contrib/feedback/browser/media/info.svg deleted file mode 100644 index 6578b81ea3f..00000000000 --- a/src/vs/workbench/contrib/feedback/browser/media/info.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/feedback/browser/media/twitter.svg b/src/vs/workbench/contrib/feedback/browser/media/twitter.svg deleted file mode 100644 index f84018e01cf..00000000000 --- a/src/vs/workbench/contrib/feedback/browser/media/twitter.svg +++ /dev/null @@ -1 +0,0 @@ -BrandTwitter_white_16x \ No newline at end of file diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts index d15f6a57e81..9d6d291ea17 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts @@ -13,7 +13,6 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { RunOnceWorker } from 'vs/base/common/async'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { Schemas } from 'vs/base/common/network'; export class TextFileEditorTracker extends Disposable implements IWorkbenchContribution { @@ -45,7 +44,7 @@ export class TextFileEditorTracker extends Disposable implements IWorkbenchContr //#region Text File: Ensure every dirty text and untitled file is opened in an editor - private readonly ensureDirtyFilesAreOpenedWorker = this._register(new RunOnceWorker(units => this.ensureDirtyTextFilesAreOpened(units), 250)); + private readonly ensureDirtyFilesAreOpenedWorker = this._register(new RunOnceWorker(units => this.ensureDirtyTextFilesAreOpened(units), 50)); private ensureDirtyTextFilesAreOpened(resources: URI[]): void { this.doEnsureDirtyTextFilesAreOpened(distinct(resources.filter(resource => { @@ -58,7 +57,7 @@ export class TextFileEditorTracker extends Disposable implements IWorkbenchContr return false; // resource must not be pending to save } - if (this.editorService.isOpen(this.editorService.createInput({ resource, forceFile: resource.scheme !== Schemas.untitled, forceUntitled: resource.scheme === Schemas.untitled }))) { + if (this.editorService.isOpen({ resource })) { return false; // model must not be opened already as file } diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts index c622bf0d5dd..6a2baff6544 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts @@ -32,6 +32,7 @@ import { isWindows } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { SaveReason } from 'vs/workbench/common/editor'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; export const CONFLICT_RESOLUTION_CONTEXT = 'saveConflictResolutionContext'; export const CONFLICT_RESOLUTION_SCHEME = 'conflictResolution'; @@ -53,10 +54,14 @@ export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHa @IEditorService private readonly editorService: IEditorService, @ITextModelService textModelService: ITextModelService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IStorageService private readonly storageService: IStorageService + @IStorageService private readonly storageService: IStorageService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService ) { super(); + // opt-in to syncing + storageKeysSyncRegistryService.registerStorageKey({ key: LEARN_MORE_DIRTY_WRITE_IGNORE_KEY, version: 1 }); + this.messages = new ResourceMap(); this.conflictResolutionContext = new RawContextKey(CONFLICT_RESOLUTION_CONTEXT, false).bindTo(contextKeyService); @@ -236,9 +241,13 @@ class ResolveSaveConflictAction extends Action { @IEditorService private readonly editorService: IEditorService, @INotificationService private readonly notificationService: INotificationService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IProductService private readonly productService: IProductService + @IProductService private readonly productService: IProductService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService ) { super('workbench.files.action.resolveConflict', nls.localize('compareChanges', "Compare")); + + // opt-in to syncing + storageKeysSyncRegistryService.registerStorageKey({ key: LEARN_MORE_DIRTY_WRITE_IGNORE_KEY, version: 1 }); } async run(): Promise { @@ -330,13 +339,13 @@ export const acceptLocalChangesCommand = async (accessor: ServicesAccessor, reso const editorService = accessor.get(IEditorService); const resolverService = accessor.get(ITextModelService); - const control = editorService.activeControl; - if (!control) { + const editorPane = editorService.activeEditorPane; + if (!editorPane) { return; } - const editor = control.input; - const group = control.group; + const editor = editorPane.input; + const group = editorPane.group; const reference = await resolverService.createModelReference(resource); const model = reference.object as IResolvedTextFileEditorModel; @@ -359,13 +368,13 @@ export const revertLocalChangesCommand = async (accessor: ServicesAccessor, reso const editorService = accessor.get(IEditorService); const resolverService = accessor.get(ITextModelService); - const control = editorService.activeControl; - if (!control) { + const editorPane = editorService.activeEditorPane; + if (!editorPane) { return; } - const editor = control.input; - const group = control.group; + const editor = editorPane.input; + const group = editorPane.group; const reference = await resolverService.createModelReference(resource); const model = reference.object as ITextFileEditorModel; diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index ba4f2cae30f..c2f3364c260 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -28,13 +28,14 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la import { DelegatingEditorService } from 'vs/workbench/services/editor/browser/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditor } from 'vs/workbench/common/editor'; +import { IEditorPane } from 'vs/workbench/common/editor'; import { ViewPane, ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { KeyChord, KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { Registry } from 'vs/platform/registry/common/platform'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { WorkbenchStateContext, RemoteNameContext, IsWebContext } from 'vs/workbench/browser/contextkeys'; +import { WorkbenchStateContext, RemoteNameContext } from 'vs/workbench/browser/contextkeys'; +import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; import { AddRootFolderAction, OpenFolderAction, OpenFileFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; import { isMacintosh } from 'vs/base/common/platform'; @@ -63,24 +64,6 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor } private registerViews(): void { - const viewsRegistry = Registry.as(Extensions.ViewsRegistry); - - this._register(viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { - content: localize('noWorkspaceHelp', "You have not yet added a folder to the workspace.\n[Add Folder](command:{0})", AddRootFolderAction.ID), - when: WorkbenchStateContext.isEqualTo('workspace') - })); - - const commandId = isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID; - this._register(viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { - content: localize('remoteNoFolderHelp', "Connected to remote.\n[Open Folder](command:{0})", commandId), - when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.notEqualsTo(''), IsWebContext.toNegated()) - })); - - this._register(viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { - content: localize('noFolderHelp', "You have not yet opened a folder.\n[Open Folder](command:{0})", commandId), - when: ContextKeyExpr.or(ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.isEqualTo('')), ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), IsWebContext)) - })); - const viewDescriptors = viewsRegistry.getViews(VIEW_CONTAINER); let viewDescriptorsToRegister: IViewDescriptor[] = []; @@ -207,7 +190,7 @@ export class ExplorerViewPaneContainer extends ViewPaneContainer { // without causing the animation in the opened editors view to kick in and change scroll position. // We try to be smart and only use the delay if we recognize that the user action is likely to cause // a new entry in the opened editors view. - const delegatingEditorService = this.instantiationService.createInstance(DelegatingEditorService, async (delegate, group, editor, options): Promise => { + const delegatingEditorService = this.instantiationService.createInstance(DelegatingEditorService, async (delegate, group, editor, options): Promise => { let openEditorsView = this.getOpenEditorsView(); if (openEditorsView) { let delay = 0; @@ -274,3 +257,23 @@ export const VIEW_CONTAINER: ViewContainer = Registry.as(Extensions.ViewsRegistry); +viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { + content: localize({ key: 'noWorkspaceHelp', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + "You have not yet added a folder to the workspace.\n[Add Folder](command:{0})", AddRootFolderAction.ID), + when: WorkbenchStateContext.isEqualTo('workspace') +}); + +const commandId = isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID; +viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { + content: localize({ key: 'remoteNoFolderHelp', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + "Connected to remote.\n[Open Folder](command:{0})", commandId), + when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.notEqualsTo(''), IsWebContext.toNegated()) +}); + +viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { + content: localize({ key: 'noFolderHelp', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + "You have not yet opened a folder.\n[Open Folder](command:{0})", commandId), + when: ContextKeyExpr.or(ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.isEqualTo('')), ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), IsWebContext)) +}); diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index 5e9f7843888..4c21a261e15 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -22,7 +22,8 @@ import { AutoSaveAfterShortDelayContext } from 'vs/workbench/services/filesConfi import { ResourceContextKey } from 'vs/workbench/common/resources'; import { WorkbenchListDoubleSelection } from 'vs/platform/list/browser/listService'; import { Schemas } from 'vs/base/common/network'; -import { WorkspaceFolderCountContext, IsWebContext } from 'vs/workbench/browser/contextkeys'; +import { WorkspaceFolderCountContext } from 'vs/workbench/browser/contextkeys'; +import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { OpenFileFolderAction, OpenFileAction, OpenFolderAction, OpenWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions'; import { ActiveEditorIsReadonlyContext, DirtyWorkingCopiesContext, ActiveEditorContext } from 'vs/workbench/common/editor'; diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 2bc6d031192..2181986b683 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/fileactions'; import * as nls from 'vs/nls'; import { isWindows, isWeb } from 'vs/base/common/platform'; import * as extpath from 'vs/base/common/extpath'; @@ -19,7 +18,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { IFileService } from 'vs/platform/files/common/files'; import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/explorerViewlet'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; +import { IQuickInputService, ItemActivation } from 'vs/platform/quickinput/common/quickInput'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ITextModel } from 'vs/editor/common/model'; @@ -45,8 +44,9 @@ import { triggerDownload, asDomUri } from 'vs/base/browser/dom'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { sequence } from 'vs/base/common/async'; +import { sequence, timeout } from 'vs/base/common/async'; import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +import { once } from 'vs/base/common/functional'; export const NEW_FILE_COMMAND_ID = 'explorer.newFile'; export const NEW_FILE_LABEL = nls.localize('newFile', "New File"); @@ -467,7 +467,7 @@ export class GlobalCompareResourcesAction extends Action { constructor( id: string, label: string, - @IQuickOpenService private readonly quickOpenService: IQuickOpenService, + @IQuickInputService private readonly quickInputService: IQuickInputService, @IEditorService private readonly editorService: IEditorService, @INotificationService private readonly notificationService: INotificationService, ) { @@ -499,9 +499,13 @@ export class GlobalCompareResourcesAction extends Action { return undefined; }); - // Bring up quick open - await this.quickOpenService.show('', { autoFocus: { autoFocusSecondEntry: true } }); - toDispose.dispose(); // make sure to unbind if quick open is closing + once(this.quickInputService.onHide)((async () => { + await timeout(0); // prevent race condition with editor + toDispose.dispose(); + })); + + // Bring up quick access + this.quickInputService.quickAccess.show('', { itemActivation: ItemActivation.SECOND }); } else { this.notificationService.info(nls.localize('openFileToCompare', "Open a file first to compare it with another file.")); } @@ -733,18 +737,24 @@ export class ShowOpenedFileInNewWindow extends Action { } } -export function validateFileName(item: ExplorerItem, name: string): string | null { +export function validateFileName(item: ExplorerItem, name: string): { content: string, severity: Severity } | null { // Produce a well formed file name name = getWellFormedFileName(name); // Name not provided if (!name || name.length === 0 || /^\s+$/.test(name)) { - return nls.localize('emptyFileNameError', "A file or folder name must be provided."); + return { + content: nls.localize('emptyFileNameError', "A file or folder name must be provided."), + severity: Severity.Error + }; } // Relative paths only if (name[0] === '/' || name[0] === '\\') { - return nls.localize('fileNameStartsWithSlashError', "A file or folder name cannot start with a slash."); + return { + content: nls.localize('fileNameStartsWithSlashError', "A file or folder name cannot start with a slash."), + severity: Severity.Error + }; } const names = coalesce(name.split(/[\\/]/)); @@ -754,14 +764,27 @@ export function validateFileName(item: ExplorerItem, name: string): string | nul // Do not allow to overwrite existing file const child = parent?.getChild(name); if (child && child !== item) { - return nls.localize('fileNameExistsError', "A file or folder **{0}** already exists at this location. Please choose a different name.", name); + return { + content: nls.localize('fileNameExistsError', "A file or folder **{0}** already exists at this location. Please choose a different name.", name), + severity: Severity.Error + }; } } // Invalid File name const windowsBasenameValidity = item.resource.scheme === Schemas.file && isWindows; if (names.some((folderName) => !extpath.isValidBasename(folderName, windowsBasenameValidity))) { - return nls.localize('invalidFileNameError', "The name **{0}** is not valid as a file or folder name. Please choose a different name.", trimLongName(name)); + return { + content: nls.localize('invalidFileNameError', "The name **{0}** is not valid as a file or folder name. Please choose a different name.", trimLongName(name)), + severity: Severity.Error + }; + } + + if (names.some(name => /^\s|\s$/.test(name))) { + return { + content: nls.localize('fileNameWhitespaceWarning', "Leading or trailing whitespace detected in file or folder name."), + severity: Severity.Warning + }; } return null; @@ -783,7 +806,7 @@ export function getWellFormedFileName(filename: string): string { // Trim tabs filename = strings.trim(filename, '\t'); - // Remove trailing dots, slashes, and spaces + // Remove trailing dots and slashes filename = strings.rtrim(filename, '.'); filename = strings.rtrim(filename, '/'); filename = strings.rtrim(filename, '\\'); diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 6112c0cb3d0..4c816f7b2a1 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -13,7 +13,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IEditorInputFactory, EditorInput, IFileEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor'; -import { AutoSaveConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files'; +import { AutoSaveConfiguration, HotExitConfiguration, FILES_EXCLUDE_CONFIG, FILES_ASSOCIATIONS_CONFIG } from 'vs/platform/files/common/files'; import { VIEWLET_ID, SortOrder, FILE_EDITOR_INPUT_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { TextFileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/textFileEditorTracker'; import { TextFileSaveErrorHandler } from 'vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler'; @@ -102,17 +102,17 @@ Registry.as(EditorExtensions.Editors).registerEditor( ); // Register default file input factory -Registry.as(EditorInputExtensions.EditorInputFactories).registerFileInputFactory({ - createFileInput: (resource, encoding, mode, instantiationService): IFileEditorInput => { +Registry.as(EditorInputExtensions.EditorInputFactories).registerFileEditorInputFactory({ + createFileEditorInput: (resource, encoding, mode, instantiationService): IFileEditorInput => { return instantiationService.createInstance(FileEditorInput, resource, encoding, mode); }, - isFileInput: (obj): obj is IFileEditorInput => { + isFileEditorInput: (obj): obj is IFileEditorInput => { return obj instanceof FileEditorInput; } }); -interface ISerializedFileInput { +interface ISerializedFileEditorInput { resourceJSON: UriComponents; encoding?: string; modeId?: string; @@ -128,23 +128,23 @@ class FileEditorInputFactory implements IEditorInputFactory { serialize(editorInput: EditorInput): string { const fileEditorInput = editorInput; const resource = fileEditorInput.resource; - const fileInput: ISerializedFileInput = { + const serializedFileEditorInput: ISerializedFileEditorInput = { resourceJSON: resource.toJSON(), encoding: fileEditorInput.getEncoding(), modeId: fileEditorInput.getPreferredMode() // only using the preferred user associated mode here if available to not store redundant data }; - return JSON.stringify(fileInput); + return JSON.stringify(serializedFileEditorInput); } deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): FileEditorInput { return instantiationService.invokeFunction(accessor => { - const fileInput: ISerializedFileInput = JSON.parse(serializedEditorInput); - const resource = URI.revive(fileInput.resourceJSON); - const encoding = fileInput.encoding; - const mode = fileInput.modeId; + const serializedFileEditorInput: ISerializedFileEditorInput = JSON.parse(serializedEditorInput); + const resource = URI.revive(serializedFileEditorInput.resourceJSON); + const encoding = serializedFileEditorInput.encoding; + const mode = serializedFileEditorInput.modeId; - return accessor.get(IEditorService).createInput({ resource, encoding, mode, forceFile: true }) as FileEditorInput; + return accessor.get(IEditorService).createEditorInput({ resource, encoding, mode, forceFile: true }) as FileEditorInput; }); } } @@ -179,9 +179,9 @@ const hotExitConfiguration: IConfigurationPropertySchema = platform.isNative ? 'enum': [HotExitConfiguration.OFF, HotExitConfiguration.ON_EXIT, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE], 'default': HotExitConfiguration.ON_EXIT, 'markdownEnumDescriptions': [ - nls.localize('hotExit.off', 'Disable hot exit.'), - nls.localize('hotExit.onExit', 'Hot exit will be triggered when the last window is closed on Windows/Linux or when the `workbench.action.quit` command is triggered (command palette, keybinding, menu). All windows with backups will be restored upon next launch.'), - nls.localize('hotExit.onExitAndWindowClose', 'Hot exit will be triggered when the last window is closed on Windows/Linux or when the `workbench.action.quit` command is triggered (command palette, keybinding, menu), and also for any window with a folder opened regardless of whether it\'s the last window. All windows without folders opened will be restored upon next launch. To restore folder windows as they were before shutdown set `#window.restoreWindows#` to `all`.') + nls.localize('hotExit.off', 'Disable hot exit. A prompt will show when attempting to close a window with dirty files.'), + nls.localize('hotExit.onExit', 'Hot exit will be triggered when the last window is closed on Windows/Linux or when the `workbench.action.quit` command is triggered (command palette, keybinding, menu). All windows without folders opened will be restored upon next launch. A list of workspaces with unsaved files can be accessed via `File > Open Recent > More...`'), + nls.localize('hotExit.onExitAndWindowClose', 'Hot exit will be triggered when the last window is closed on Windows/Linux or when the `workbench.action.quit` command is triggered (command palette, keybinding, menu), and also for any window with a folder opened regardless of whether it\'s the last window. All windows without folders opened will be restored upon next launch. A list of workspaces with unsaved files can be accessed via `File > Open Recent > More...`') ], 'description': nls.localize('hotExit', "Controls whether unsaved files are remembered between sessions, allowing the save prompt when exiting the editor to be skipped.", HotExitConfiguration.ON_EXIT, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) } : { @@ -190,7 +190,7 @@ const hotExitConfiguration: IConfigurationPropertySchema = platform.isNative ? 'enum': [HotExitConfiguration.OFF, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE], 'default': HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, 'markdownEnumDescriptions': [ - nls.localize('hotExit.off', 'Disable hot exit.'), + nls.localize('hotExit.off', 'Disable hot exit. A prompt will show when attempting to close a window with dirty files.'), nls.localize('hotExit.onExitAndWindowCloseBrowser', 'Hot exit will be triggered when the browser quits or the window or tab is closed.') ], 'description': nls.localize('hotExit', "Controls whether unsaved files are remembered between sessions, allowing the save prompt when exiting the editor to be skipped.", HotExitConfiguration.ON_EXIT, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) @@ -202,9 +202,9 @@ configurationRegistry.registerConfiguration({ 'title': nls.localize('filesConfigurationTitle', "Files"), 'type': 'object', 'properties': { - 'files.exclude': { + [FILES_EXCLUDE_CONFIG]: { 'type': 'object', - 'markdownDescription': nls.localize('exclude', "Configure glob patterns for excluding files and folders. For example, the files explorer decides which files and folders to show or hide based on this setting. Read more about glob patterns [here](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)."), + 'markdownDescription': nls.localize('exclude', "Configure glob patterns for excluding files and folders. For example, the files explorer decides which files and folders to show or hide based on this setting. Refer to the `#search.exclude#` setting to define search specific excludes. Read more about glob patterns [here](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)."), 'default': { '**/.git': true, '**/.svn': true, '**/.hg': true, '**/CVS': true, '**/.DS_Store': true }, 'scope': ConfigurationScope.RESOURCE, 'additionalProperties': { @@ -227,7 +227,7 @@ configurationRegistry.registerConfiguration({ ] } }, - 'files.associations': { + [FILES_ASSOCIATIONS_CONFIG]: { 'type': 'object', 'markdownDescription': nls.localize('associations', "Configure file associations to languages (e.g. `\"*.extension\": \"html\"`). These have precedence over the default associations of the languages installed."), }, @@ -305,8 +305,8 @@ configurationRegistry.registerConfiguration({ }, 'files.watcherExclude': { 'type': 'object', - 'default': platform.isWindows /* https://github.com/Microsoft/vscode/issues/23954 */ ? { '**/.git/objects/**': true, '**/.git/subtree-cache/**': true, '**/node_modules/*/**': true } : { '**/.git/objects/**': true, '**/.git/subtree-cache/**': true, '**/node_modules/**': true }, - 'description': nls.localize('watcherExclude', "Configure glob patterns of file paths to exclude from file watching. Patterns must match on absolute paths (i.e. prefix with ** or the full path to match properly). Changing this setting requires a restart. When you experience Code consuming lots of cpu time on startup, you can exclude large folders to reduce the initial load."), + 'default': platform.isWindows /* https://github.com/Microsoft/vscode/issues/23954 */ ? { '**/.git/objects/**': true, '**/.git/subtree-cache/**': true, '**/node_modules/*/**': true, '**/.hg/store/**': true } : { '**/.git/objects/**': true, '**/.git/subtree-cache/**': true, '**/node_modules/**': true, '**/.hg/store/**': true }, + 'description': nls.localize('watcherExclude', "Configure glob patterns of file paths to exclude from file watching. Patterns must match on absolute paths (i.e. prefix with ** or the full path to match properly). Changing this setting requires a restart. When you experience Code consuming lots of CPU time on startup, you can exclude large folders to reduce the initial load."), 'scope': ConfigurationScope.RESOURCE }, 'files.hotExit': hotExitConfiguration, diff --git a/src/vs/workbench/contrib/files/browser/views/emptyView.ts b/src/vs/workbench/contrib/files/browser/views/emptyView.ts index 212fe31a042..9cfc00be3ae 100644 --- a/src/vs/workbench/contrib/files/browser/views/emptyView.ts +++ b/src/vs/workbench/contrib/files/browser/views/emptyView.ts @@ -11,7 +11,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { ResourcesDropHandler, DragAndDropObserver } from 'vs/workbench/browser/dnd'; import { listDropBackground } from 'vs/platform/theme/common/colorRegistry'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; @@ -40,7 +40,7 @@ export class EmptyView extends ViewPane { @IOpenerService openerService: IOpenerService, @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('explorerSection', "Explorer Section: No Folder Opened") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this._register(this.contextService.onDidChangeWorkbenchState(() => this.refreshTitle())); this._register(this.labelService.onDidChangeFormatters(() => this.refreshTitle())); @@ -55,21 +55,21 @@ export class EmptyView extends ViewPane { this._register(new DragAndDropObserver(container, { onDrop: e => { - const color = this.themeService.getTheme().getColor(SIDE_BAR_BACKGROUND); + const color = this.themeService.getColorTheme().getColor(SIDE_BAR_BACKGROUND); container.style.backgroundColor = color ? color.toString() : ''; const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: true }); dropHandler.handleDrop(e, () => undefined, () => undefined); }, onDragEnter: () => { - const color = this.themeService.getTheme().getColor(listDropBackground); + const color = this.themeService.getColorTheme().getColor(listDropBackground); container.style.backgroundColor = color ? color.toString() : ''; }, onDragEnd: () => { - const color = this.themeService.getTheme().getColor(SIDE_BAR_BACKGROUND); + const color = this.themeService.getColorTheme().getColor(SIDE_BAR_BACKGROUND); container.style.backgroundColor = color ? color.toString() : ''; }, onDragLeave: () => { - const color = this.themeService.getTheme().getColor(SIDE_BAR_BACKGROUND); + const color = this.themeService.getColorTheme().getColor(SIDE_BAR_BACKGROUND); container.style.backgroundColor = color ? color.toString() : ''; }, onDragOver: e => { @@ -89,8 +89,4 @@ export class EmptyView extends ViewPane { this.updateTitle(this.title); } } - - layoutBody(_size: number): void { - // no-op - } } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts b/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts index 0f09459edf8..090962b14d7 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts @@ -8,7 +8,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { localize } from 'vs/nls'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IDecorationsProvider, IDecorationData } from 'vs/workbench/services/decorations/browser/decorations'; -import { listInvalidItemForeground } from 'vs/platform/theme/common/colorRegistry'; +import { listInvalidItemForeground, listDeemphasizedForeground } from 'vs/platform/theme/common/colorRegistry'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { explorerRootErrorEmitter } from 'vs/workbench/contrib/files/browser/views/explorerViewer'; @@ -34,6 +34,11 @@ export function provideDecorations(fileStat: ExplorerItem): IDecorationData | un letter: '?' }; } + if (fileStat.isExcluded) { + return { + color: listDeemphasizedForeground, + }; + } return undefined; } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 3e5fc89ec88..322da94e9d7 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -24,7 +24,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ResourceContextKey } from 'vs/workbench/common/resources'; import { IDecorationsService } from 'vs/workbench/services/decorations/browser/decorations'; -import { TreeResourceNavigator, WorkbenchCompressibleAsyncDataTree } from 'vs/platform/list/browser/listService'; +import { ResourceNavigator, WorkbenchCompressibleAsyncDataTree } from 'vs/platform/list/browser/listService'; import { DelayedDragHandler } from 'vs/base/browser/dnd'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; @@ -172,7 +172,7 @@ export class ExplorerView extends ViewPane { @IFileService private readonly fileService: IFileService, @IOpenerService openerService: IOpenerService, ) { - super({ ...(options as IViewPaneOptions), id: ExplorerView.ID, ariaHeaderLabel: nls.localize('explorerSection', "Explorer Section: {0}", labelService.getWorkspaceLabel(contextService.getWorkspace())) }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.resourceContext = instantiationService.createInstance(ResourceContextKey); this._register(this.resourceContext); @@ -354,6 +354,7 @@ export class ExplorerView extends ViewPane { private createTree(container: HTMLElement): void { this.filter = this.instantiationService.createInstance(FilesFilter); this._register(this.filter); + this._register(this.filter.onDidChange(() => this.refresh(true))); const explorerLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); this._register(explorerLabels); @@ -418,7 +419,7 @@ export class ExplorerView extends ViewPane { // Update resource context based on focused element this._register(this.tree.onDidChangeFocus(e => this.onFocusChanged(e.elements))); this.onFocusChanged([]); - const explorerNavigator = new TreeResourceNavigator(this.tree); + const explorerNavigator = ResourceNavigator.createTreeResourceNavigator(this.tree); this._register(explorerNavigator); // Open when selecting via keyboard this._register(explorerNavigator.onDidOpenResource(async e => { @@ -458,18 +459,7 @@ export class ExplorerView extends ViewPane { this.autoReveal = configuration?.explorer?.autoReveal; // Push down config updates to components of viewer - let needsRefresh = false; - if (this.filter) { - needsRefresh = this.filter.updateConfiguration(); - } - - if (event && !needsRefresh) { - needsRefresh = event.affectsConfiguration('explorer.decorations.colors') - || event.affectsConfiguration('explorer.decorations.badges'); - } - - // Refresh viewer as needed if this originates from a config event - if (event && needsRefresh) { + if (event && (event.affectsConfiguration('explorer.decorations.colors') || event.affectsConfiguration('explorer.decorations.badges'))) { this.refresh(true); } } @@ -509,7 +499,13 @@ export class ExplorerView extends ViewPane { const actions: IAction[] = []; const roots = this.explorerService.roots; // If the click is outside of the elements pass the root resource if there is only one root. If there are multiple roots pass empty object. - const arg = stat instanceof ExplorerItem ? stat.resource : roots.length === 1 ? roots[0].resource : {}; + let arg: URI | {}; + if (stat instanceof ExplorerItem) { + const compressedController = this.renderer.getCompressedNavigationController(stat); + arg = compressedController ? compressedController.current.resource : stat.resource; + } else { + arg = roots.length === 1 ? roots[0].resource : {}; + } disposables.add(createAndFillInContextMenuActions(this.contributedContextMenu, { arg, shouldForwardArgs: true }, actions, this.contextMenuService)); this.contextMenuService.showContextMenu({ @@ -672,19 +668,23 @@ export class ExplorerView extends ViewPane { } if (item && item.parent) { - if (reveal) { - if (item.isDisposed) { - return this.onSelectResource(resource, reveal, retry + 1); + try { + if (reveal) { + if (item.isDisposed) { + return this.onSelectResource(resource, reveal, retry + 1); + } + + // Don't scroll to the item if it's already visible + if (this.tree.getRelativeTop(item) === null) { + this.tree.reveal(item, 0.5); + } } - // Don't scroll to the item if it's already visible - if (this.tree.getRelativeTop(item) === null) { - this.tree.reveal(item, 0.5); - } + this.tree.setFocus([item]); + this.tree.setSelection([item]); + } catch (e) { + // Element might not be in the tree, silently fail } - - this.tree.setFocus([item]); - this.tree.setSelection([item]); } } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 9ae1935e2cd..0b5733d8388 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -55,6 +55,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { isNumber } from 'vs/base/common/types'; import { domEvent } from 'vs/base/browser/event'; import { IEditableData } from 'vs/workbench/common/views'; +import { IEditorInput } from 'vs/workbench/common/editor'; export class ExplorerDelegate implements IListVirtualDelegate { @@ -375,13 +376,13 @@ export class FilesRenderer implements ICompressibleTreeRenderer { - const content = editableData.validationMessage(value); - if (!content) { + const message = editableData.validationMessage(value); + if (!message || message.severity !== Severity.Error) { return null; } return { - content, + content: message.content, formatContent: true, type: MessageType.ERROR }; @@ -391,10 +392,6 @@ export class FilesRenderer implements ICompressibleTreeRenderer { - label.setFile(joinPath(parent, value || ' '), labelOptions); // update label icon while typing! - }); - const lastDot = value.lastIndexOf('.'); inputBox.value = value; @@ -411,8 +408,27 @@ export class FilesRenderer implements ICompressibleTreeRenderer { + if (inputBox.isInputValid()) { + const message = editableData.validationMessage(inputBox.value); + if (message) { + inputBox.showMessage({ + content: message.content, + formatContent: true, + type: message.severity === Severity.Info ? MessageType.INFO : message.severity === Severity.Warning ? MessageType.WARNING : MessageType.ERROR + }); + } else { + inputBox.hideMessage(); + } + } + }; + showInputBoxNotification(); + const toDispose = [ inputBox, + inputBox.onDidChange(value => { + label.setFile(joinPath(parent, value || ' '), labelOptions); // update label icon while typing! + }), DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.KEY_DOWN, (e: IKeyboardEvent) => { if (e.equals(KeyCode.Enter)) { if (inputBox.validate()) { @@ -422,6 +438,9 @@ export class FilesRenderer implements ICompressibleTreeRenderer { + showInputBoxNotification(); + }), DOM.addDisposableListener(inputBox.inputElement, DOM.EventType.BLUR, () => { done(inputBox.isInputValid(), true); }), @@ -472,28 +491,70 @@ interface CachedParsedExpression { parsed: glob.ParsedExpression; } +/** + * Respectes files.exclude setting in filtering out content from the explorer. + * Makes sure that visible editors are always shown in the explorer even if they are filtered out by settings. + */ export class FilesFilter implements ITreeFilter { private hiddenExpressionPerRoot: Map; - private workspaceFolderChangeListener: IDisposable; + private hiddenUris = new Set(); + private editorsAffectingFilter = new Set(); + private _onDidChange = new Emitter(); + private toDispose: IDisposable[] = []; constructor( @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IExplorerService private readonly explorerService: IExplorerService + @IExplorerService private readonly explorerService: IExplorerService, + @IEditorService private readonly editorService: IEditorService, ) { this.hiddenExpressionPerRoot = new Map(); - this.workspaceFolderChangeListener = this.contextService.onDidChangeWorkspaceFolders(() => this.updateConfiguration()); + this.toDispose.push(this.contextService.onDidChangeWorkspaceFolders(() => this.updateConfiguration())); + this.toDispose.push(this.configurationService.onDidChangeConfiguration((e) => { + if (e.affectsConfiguration('files.exclude')) { + this.updateConfiguration(); + } + })); + this.toDispose.push(this.editorService.onDidVisibleEditorsChange(() => { + const editors = this.editorService.visibleEditors; + let shouldFire = false; + this.hiddenUris.forEach(u => { + editors.forEach(e => { + if (e.resource && isEqualOrParent(e.resource, u)) { + // A filtered resource suddenly became visible since user opened an editor + shouldFire = true; + } + }); + }); + + this.editorsAffectingFilter.forEach(e => { + if (editors.indexOf(e) === -1) { + // Editor that was affecting filtering is no longer visible + shouldFire = true; + } + }); + if (shouldFire) { + this.editorsAffectingFilter.clear(); + this.hiddenUris.clear(); + this._onDidChange.fire(); + } + })); + this.updateConfiguration(); } - updateConfiguration(): boolean { - let needsRefresh = false; + get onDidChange(): Event { + return this._onDidChange.event; + } + + private updateConfiguration(): void { + let shouldFire = false; this.contextService.getWorkspace().folders.forEach(folder => { const configuration = this.configurationService.getValue({ resource: folder.uri }); const excludesConfig: glob.IExpression = configuration?.files?.exclude || Object.create(null); - if (!needsRefresh) { + if (!shouldFire) { const cached = this.hiddenExpressionPerRoot.get(folder.uri.toString()); - needsRefresh = !cached || !equals(cached.original, excludesConfig); + shouldFire = !cached || !equals(cached.original, excludesConfig); } const excludesConfigCopy = deepClone(excludesConfig); // do not keep the config, as it gets mutated under our hoods @@ -501,11 +562,28 @@ export class FilesFilter implements ITreeFilter { this.hiddenExpressionPerRoot.set(folder.uri.toString(), { original: excludesConfigCopy, parsed: glob.parse(excludesConfigCopy) }); }); - return needsRefresh; + if (shouldFire) { + this.editorsAffectingFilter.clear(); + this.hiddenUris.clear(); + this._onDidChange.fire(); + } } filter(stat: ExplorerItem, parentVisibility: TreeVisibility): TreeFilterResult { + const isVisible = this.isVisible(stat, parentVisibility); + if (isVisible) { + this.hiddenUris.delete(stat.resource); + } else { + this.hiddenUris.add(stat.resource); + } + + return isVisible; + } + + private isVisible(stat: ExplorerItem, parentVisibility: TreeVisibility): boolean { + stat.isExcluded = false; if (parentVisibility === TreeVisibility.Hidden) { + stat.isExcluded = true; return false; } if (this.explorerService.getEditableData(stat) || stat.isRoot) { @@ -514,15 +592,23 @@ export class FilesFilter implements ITreeFilter { // Hide those that match Hidden Patterns const cached = this.hiddenExpressionPerRoot.get(stat.root.resource.toString()); - if (cached && cached.parsed(path.relative(stat.root.resource.path, stat.resource.path), stat.name, name => !!(stat.parent && stat.parent.getChild(name)))) { + if ((cached && cached.parsed(path.relative(stat.root.resource.path, stat.resource.path), stat.name, name => !!(stat.parent && stat.parent.getChild(name)))) || stat.parent?.isExcluded) { + stat.isExcluded = true; + const editors = this.editorService.visibleEditors; + const editor = editors.filter(e => e.resource && isEqualOrParent(e.resource, stat.resource)).pop(); + if (editor) { + this.editorsAffectingFilter.add(editor); + return true; // Show all opened files and their parents + } + return false; // hidden through pattern } return true; } - public dispose(): void { - dispose(this.workspaceFolderChangeListener); + dispose(): void { + dispose(this.toDispose); } } diff --git a/src/vs/workbench/contrib/files/browser/views/media/openeditors.css b/src/vs/workbench/contrib/files/browser/views/media/openeditors.css index a64e503df77..1f5fd5b019e 100644 --- a/src/vs/workbench/contrib/files/browser/views/media/openeditors.css +++ b/src/vs/workbench/contrib/files/browser/views/media/openeditors.css @@ -25,6 +25,10 @@ justify-content: center; } +.open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .codicon-close::before { + content: "\ea71"; /* Close icon flips between black dot and "X" for dirty open editors */ +} + .open-editors .monaco-list .monaco-list-row > .monaco-action-bar .action-close-all-files, .open-editors .monaco-list .monaco-list-row > .monaco-action-bar .save-all { width: 23px; diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 339367815bb..bb367439774 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -31,10 +31,10 @@ import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; -import { DirtyEditorContext, OpenEditorsGroupContext, ReadonlyEditorContext as ReadonlyEditorContext } from 'vs/workbench/contrib/files/browser/fileCommands'; +import { DirtyEditorContext, OpenEditorsGroupContext, ReadonlyEditorContext } from 'vs/workbench/contrib/files/browser/fileCommands'; import { ResourceContextKey } from 'vs/workbench/common/resources'; import { ResourcesDropHandler, fillResourceDataTransfers, CodeDataTransfers, containsDragType } from 'vs/workbench/browser/dnd'; -import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IDragAndDropData, DataTransfers } from 'vs/base/browser/dnd'; import { memoize } from 'vs/base/common/decorators'; @@ -46,6 +46,7 @@ import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities } from 'vs/w import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { Orientation } from 'vs/base/browser/ui/splitview/splitview'; const $ = dom.$; @@ -84,10 +85,7 @@ export class OpenEditorsView extends ViewPane { @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, @IOpenerService openerService: IOpenerService, ) { - super({ - ...(options as IViewPaneOptions), - ariaHeaderLabel: nls.localize({ key: 'openEditosrSection', comment: ['Open is an adjective'] }, "Open Editors Section"), - }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.structuralRefreshDelay = 0; this.listRefreshScheduler = new RunOnceScheduler(() => { @@ -247,7 +245,7 @@ export class OpenEditorsView extends ViewPane { this.readonlyEditorFocusedContext = ReadonlyEditorContext.bindTo(this.contextKeyService); this._register(this.list.onContextMenu(e => this.onListContextMenu(e))); - this.list.onFocusChange(e => { + this.list.onDidChangeFocus(e => { this.resourceContext.reset(); this.groupFocusedContext.reset(); this.dirtyEditorFocusedContext.reset(); @@ -418,8 +416,8 @@ export class OpenEditorsView extends ViewPane { private updateSize(): void { // Adjust expanded body size - this.minimumBodySize = this.getMinExpandedBodySize(); - this.maximumBodySize = this.getMaxExpandedBodySize(); + this.minimumBodySize = this.orientation === Orientation.VERTICAL ? this.getMinExpandedBodySize() : 170; + this.maximumBodySize = this.orientation === Orientation.VERTICAL ? this.getMaxExpandedBodySize() : Number.POSITIVE_INFINITY; } private updateDirtyIndicator(workingCopy?: IWorkingCopy): void { @@ -595,7 +593,7 @@ class OpenEditorRenderer implements IListRenderer { instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService( instantiationService.createInstance(MockContextKeyService), - configurationService, - TestEnvironmentService + configurationService )); const part = instantiationService.createInstance(EditorPart); diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts index 69fa0be16bf..5b78f85f045 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts @@ -149,7 +149,7 @@ suite('Files - FileEditorInput', () => { resolved.textEditorModel!.setValue('changed'); assert.ok(input.isDirty()); - assert.ok(await input.revert(0)); + await input.revert(0); assert.ok(!input.isDirty()); input.dispose(); diff --git a/src/vs/workbench/contrib/files/test/browser/textFileEditor.test.ts b/src/vs/workbench/contrib/files/test/browser/textFileEditor.test.ts index a58dd887f65..9325e95d69b 100644 --- a/src/vs/workbench/contrib/files/test/browser/textFileEditor.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/textFileEditor.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { toResource } from 'vs/base/test/common/utils'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { workbenchInstantiationService, TestServiceAccessor, TestFilesConfigurationService, TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor, TestFilesConfigurationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; @@ -56,8 +56,7 @@ suite('Files - TextFileEditor', () => { instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService( instantiationService.createInstance(MockContextKeyService), - configurationService, - TestEnvironmentService + configurationService )); const part = instantiationService.createInstance(EditorPart); @@ -87,14 +86,14 @@ suite('Files - TextFileEditor', () => { async function viewStateTest(context: Mocha.ITestCallbackContext, restoreViewState: boolean): Promise { const [part, accessor] = await createPart(restoreViewState); - let editor = await accessor.editorService.openEditor(accessor.editorService.createInput({ resource: toResource.call(context, '/path/index.txt'), forceFile: true })); + let editor = await accessor.editorService.openEditor(accessor.editorService.createEditorInput({ resource: toResource.call(context, '/path/index.txt'), forceFile: true })); let codeEditor = editor?.getControl() as CodeEditorWidget; const selection = new Selection(1, 3, 1, 4); codeEditor.setSelection(selection); - editor = await accessor.editorService.openEditor(accessor.editorService.createInput({ resource: toResource.call(context, '/path/index-other.txt'), forceFile: true })); - editor = await accessor.editorService.openEditor(accessor.editorService.createInput({ resource: toResource.call(context, '/path/index.txt'), forceFile: true })); + editor = await accessor.editorService.openEditor(accessor.editorService.createEditorInput({ resource: toResource.call(context, '/path/index-other.txt'), forceFile: true })); + editor = await accessor.editorService.openEditor(accessor.editorService.createEditorInput({ resource: toResource.call(context, '/path/index.txt'), forceFile: true })); codeEditor = editor?.getControl() as CodeEditorWidget; diff --git a/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts b/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts index 081e0d02d1a..f50a26af1a9 100644 --- a/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts @@ -97,14 +97,14 @@ suite('Files - TextFileEditorTracker', () => { const resource = toResource.call(this, '/path/index.txt'); - assert.ok(!accessor.editorService.isOpen(accessor.editorService.createInput({ resource, forceFile: true }))); + assert.ok(!accessor.editorService.isOpen(accessor.editorService.createEditorInput({ resource, forceFile: true }))); const model = await accessor.textFileService.files.resolve(resource) as IResolvedTextFileEditorModel; model.textEditorModel.setValue('Super Good'); await awaitEditorOpening(accessor.editorService); - assert.ok(accessor.editorService.isOpen(accessor.editorService.createInput({ resource, forceFile: true }))); + assert.ok(accessor.editorService.isOpen(accessor.editorService.createEditorInput({ resource, forceFile: true }))); part.dispose(); tracker.dispose(); @@ -114,7 +114,7 @@ suite('Files - TextFileEditorTracker', () => { test('dirty untitled text file model opens as editor', async function () { const [part, accessor, tracker, , editorService] = await createTracker(); - const untitledEditor = editorService.createInput({ forceUntitled: true }) as UntitledTextEditorInput; + const untitledEditor = editorService.createEditorInput({ forceUntitled: true }) as UntitledTextEditorInput; const model = await untitledEditor.resolve(); assert.ok(!accessor.editorService.isOpen(untitledEditor)); @@ -140,7 +140,7 @@ suite('Files - TextFileEditorTracker', () => { const resource = toResource.call(this, '/path/index.txt'); - await accessor.editorService.openEditor(accessor.editorService.createInput({ resource, forceFile: true })); + await accessor.editorService.openEditor(accessor.editorService.createEditorInput({ resource, forceFile: true })); accessor.hostService.setFocus(false); accessor.hostService.setFocus(true); diff --git a/src/vs/workbench/contrib/issue/electron-browser/issueService.ts b/src/vs/workbench/contrib/issue/electron-browser/issueService.ts index 6b5b4068648..3efe946f1ee 100644 --- a/src/vs/workbench/contrib/issue/electron-browser/issueService.ts +++ b/src/vs/workbench/contrib/issue/electron-browser/issueService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IssueReporterStyles, IIssueService, IssueReporterData, ProcessExplorerData, IssueReporterExtensionData } from 'vs/platform/issue/node/issue'; -import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { textLinkForeground, inputBackground, inputBorder, inputForeground, buttonBackground, buttonHoverBackground, buttonForeground, inputValidationErrorBorder, foreground, inputActiveOptionBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, editorBackground, editorForeground, listHoverBackground, listHoverForeground, listHighlightForeground, textLinkActiveForeground, inputValidationErrorBackground, inputValidationErrorForeground } from 'vs/platform/theme/common/colorRegistry'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -12,8 +12,9 @@ import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/exte import { webFrame } from 'electron'; import { assign } from 'vs/base/common/objects'; import { IWorkbenchIssueService } from 'vs/workbench/contrib/issue/electron-browser/issue'; -import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; +import { ExtensionType } from 'vs/platform/extensions/common/extensions'; export class WorkbenchIssueService implements IWorkbenchIssueService { _serviceBrand: undefined; @@ -23,43 +24,40 @@ export class WorkbenchIssueService implements IWorkbenchIssueService { @IThemeService private readonly themeService: IThemeService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService ) { } - openReporter(dataOverrides: Partial = {}): Promise { - return this.extensionManagementService.getInstalled(ExtensionType.User).then(extensions => { - const enabledExtensions = extensions.filter(extension => this.extensionEnablementService.isEnabled(extension)); - const extensionData: IssueReporterExtensionData[] = enabledExtensions.map(extension => { - const { manifest } = extension; - const manifestKeys = manifest.contributes ? Object.keys(manifest.contributes) : []; - const isTheme = !manifest.activationEvents && manifestKeys.length === 1 && manifestKeys[0] === 'themes'; - - return { - name: manifest.name, - publisher: manifest.publisher, - version: manifest.version, - repositoryUrl: manifest.repository && manifest.repository.url, - bugsUrl: manifest.bugs && manifest.bugs.url, - displayName: manifest.displayName, - id: extension.identifier.id, - isTheme: isTheme - }; - }); - const theme = this.themeService.getTheme(); - const issueReporterData: IssueReporterData = assign( - { - styles: getIssueReporterStyles(theme), - zoomLevel: webFrame.getZoomLevel(), - enabledExtensions: extensionData - }, - dataOverrides); - - return this.issueService.openReporter(issueReporterData); + async openReporter(dataOverrides: Partial = {}): Promise { + const extensions = await this.extensionManagementService.getInstalled(); + const enabledExtensions = extensions.filter(extension => this.extensionEnablementService.isEnabled(extension)); + const extensionData = enabledExtensions.map((extension): IssueReporterExtensionData => { + const { manifest } = extension; + const manifestKeys = manifest.contributes ? Object.keys(manifest.contributes) : []; + const isTheme = !manifest.activationEvents && manifestKeys.length === 1 && manifestKeys[0] === 'themes'; + const isBuiltin = extension.type === ExtensionType.System; + return { + name: manifest.name, + publisher: manifest.publisher, + version: manifest.version, + repositoryUrl: manifest.repository && manifest.repository.url, + bugsUrl: manifest.bugs && manifest.bugs.url, + displayName: manifest.displayName, + id: extension.identifier.id, + isTheme, + isBuiltin, + }; }); + const theme = this.themeService.getColorTheme(); + const issueReporterData: IssueReporterData = assign({ + styles: getIssueReporterStyles(theme), + zoomLevel: webFrame.getZoomLevel(), + enabledExtensions: extensionData, + }, dataOverrides); + return this.issueService.openReporter(issueReporterData); } openProcessExplorer(): Promise { - const theme = this.themeService.getTheme(); + const theme = this.themeService.getColorTheme(); const data: ProcessExplorerData = { pid: this.environmentService.configuration.mainPid, zoomLevel: webFrame.getZoomLevel(), @@ -75,7 +73,7 @@ export class WorkbenchIssueService implements IWorkbenchIssueService { } } -export function getIssueReporterStyles(theme: ITheme): IssueReporterStyles { +export function getIssueReporterStyles(theme: IColorTheme): IssueReporterStyles { return { backgroundColor: getColor(theme, SIDE_BAR_BACKGROUND), color: getColor(theme, foreground), @@ -97,7 +95,7 @@ export function getIssueReporterStyles(theme: ITheme): IssueReporterStyles { }; } -function getColor(theme: ITheme, key: string): string | undefined { +function getColor(theme: IColorTheme, key: string): string | undefined { const color = theme.getColor(key); return color ? color.toString() : undefined; } diff --git a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts index a03705bc2d0..5fa43acd817 100644 --- a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts +++ b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts @@ -26,11 +26,14 @@ import { minimumTranslatedStrings } from 'vs/workbench/contrib/localizations/bro import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; // Register action to configure locale and related settings const registry = Registry.as(Extensions.WorkbenchActions); registry.registerWorkbenchAction(SyncActionDescriptor.create(ConfigureLocaleAction, ConfigureLocaleAction.ID, ConfigureLocaleAction.LABEL), 'Configure Display Language'); +const LANGUAGEPACK_SUGGESTION_IGNORE_STORAGE_KEY = 'extensionsAssistant/languagePackSuggestionIgnore'; + export class LocalizationWorkbenchContribution extends Disposable implements IWorkbenchContribution { constructor( @INotificationService private readonly notificationService: INotificationService, @@ -41,9 +44,13 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, @IViewletService private readonly viewletService: IViewletService, - @ITelemetryService private readonly telemetryService: ITelemetryService + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService ) { super(); + + storageKeysSyncRegistryService.registerStorageKey({ key: LANGUAGEPACK_SUGGESTION_IGNORE_STORAGE_KEY, version: 1 }); + storageKeysSyncRegistryService.registerStorageKey({ key: 'langugage.update.donotask', version: 1 }); this.checkAndInstall(); this._register(this.extensionManagementService.onDidInstallExtension(e => this.onDidInstallExtension(e))); } @@ -76,7 +83,7 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo private checkAndInstall(): void { const language = platform.language; const locale = platform.locale; - const languagePackSuggestionIgnoreList = JSON.parse(this.storageService.get('extensionsAssistant/languagePackSuggestionIgnore', StorageScope.GLOBAL, '[]')); + const languagePackSuggestionIgnoreList = JSON.parse(this.storageService.get(LANGUAGEPACK_SUGGESTION_IGNORE_STORAGE_KEY, StorageScope.GLOBAL, '[]')); if (!this.galleryService.isEnabled()) { return; @@ -167,7 +174,7 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo run: () => { languagePackSuggestionIgnoreList.push(language); this.storageService.store( - 'extensionsAssistant/languagePackSuggestionIgnore', + LANGUAGEPACK_SUGGESTION_IGNORE_STORAGE_KEY, JSON.stringify(languagePackSuggestionIgnoreList), StorageScope.GLOBAL ); diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts index f7f39056c5c..9e44d568d9c 100644 --- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -45,7 +45,7 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { } private registerCommonContributions(): void { - this.registerLogChannel(Constants.userDataSyncLogChannelId, nls.localize('userDataSyncLog', "Sync"), this.environmentService.userDataSyncLogResource); + this.registerLogChannel(Constants.userDataSyncLogChannelId, nls.localize('userDataSyncLog', "Preferences Sync"), this.environmentService.userDataSyncLogResource); this.registerLogChannel(Constants.rendererLogChannelId, nls.localize('rendererLog', "Window"), this.environmentService.logFile); } diff --git a/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts b/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts index 9834a34dc62..97c16d4d4af 100644 --- a/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts +++ b/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts @@ -9,8 +9,9 @@ import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IFileService } from 'vs/platform/files/common/files'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; export class OpenLogsFolderAction extends Action { @@ -35,7 +36,7 @@ export class OpenExtensionLogsFolderAction extends Action { static readonly LABEL = nls.localize('openExtensionLogsFolder', "Open Extension Logs Folder"); constructor(id: string, label: string, - @IElectronEnvironmentService private readonly electronEnvironmentSerice: IElectronEnvironmentService, + @IWorkbenchEnvironmentService private readonly environmentSerice: INativeWorkbenchEnvironmentService, @IFileService private readonly fileService: IFileService, @IElectronService private readonly electronService: IElectronService ) { @@ -43,7 +44,7 @@ export class OpenExtensionLogsFolderAction extends Action { } async run(): Promise { - const folderStat = await this.fileService.resolve(this.electronEnvironmentSerice.extHostLogsPath); + const folderStat = await this.fileService.resolve(this.environmentSerice.extHostLogsPath); if (folderStat.children && folderStat.children[0]) { return this.electronService.showItemInFolder(folderStat.children[0].resource.fsPath); } diff --git a/src/vs/workbench/contrib/markers/browser/constants.ts b/src/vs/workbench/contrib/markers/browser/constants.ts index 6a06908d88a..6714a443377 100644 --- a/src/vs/workbench/contrib/markers/browser/constants.ts +++ b/src/vs/workbench/contrib/markers/browser/constants.ts @@ -14,6 +14,7 @@ export default { RELATED_INFORMATION_COPY_MESSAGE_ACTION_ID: 'problems.action.copyRelatedInformationMessage', FOCUS_PROBLEMS_FROM_FILTER: 'problems.action.focusProblemsFromFilter', MARKERS_VIEW_FOCUS_FILTER: 'problems.action.focusFilter', + MARKERS_VIEW_CLEAR_FILTER_TEXT: 'problems.action.clearFilterText', MARKERS_VIEW_SHOW_MULTILINE_MESSAGE: 'problems.action.showMultilineMessage', MARKERS_VIEW_SHOW_SINGLELINE_MESSAGE: 'problems.action.showSinglelineMessage', MARKER_OPEN_SIDE_ACTION_ID: 'problems.action.openToSide', @@ -21,7 +22,7 @@ export default { MARKER_SHOW_QUICK_FIX: 'problems.action.showQuickFixes', TOGGLE_MARKERS_VIEW_ACTION_ID: 'workbench.actions.view.toggleProblems', - MarkerViewFocusContextKey: new RawContextKey('problemsViewFocus', false), + MarkersViewSmallLayoutContextKey: new RawContextKey(`problemsView.smallLayout`, false), MarkerFocusContextKey: new RawContextKey('problemFocus', false), MarkerViewFilterFocusContextKey: new RawContextKey('problemsFilterFocus', false), RelatedInformationFocusContextKey: new RawContextKey('relatedInformationFocus', false) diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index 30e05092dfe..ed8b865da5c 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -4,9 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/workbench/contrib/markers/browser/markersFileDecorations'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; -import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; @@ -14,7 +13,6 @@ import { localize } from 'vs/nls'; import { Marker, RelatedInformation } from 'vs/workbench/contrib/markers/browser/markersModel'; import { MarkersView } from 'vs/workbench/contrib/markers/browser/markersView'; import { MenuId, MenuRegistry, SyncActionDescriptor, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; -import { TogglePanelAction } from 'vs/workbench/browser/panel'; import { Registry } from 'vs/platform/registry/common/platform'; import { ShowProblemsPanelAction } from 'vs/workbench/contrib/markers/browser/markersViewActions'; import Constants from 'vs/workbench/contrib/markers/browser/constants'; @@ -28,11 +26,12 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; import { IMarkerService, MarkerStatistics } from 'vs/platform/markers/common/markers'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewsRegistry, IViewsService, getVisbileViewContextKey } from 'vs/workbench/common/views'; +import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewsRegistry, IViewsService, getVisbileViewContextKey, FocusedViewContext, IViewDescriptorService } from 'vs/workbench/common/views'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import type { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ToggleViewAction } from 'vs/workbench/browser/actions/layoutActions'; registerSingleton(IMarkersWorkbenchService, MarkersWorkbenchService, false); @@ -94,16 +93,18 @@ Registry.as(Extensions.Configuration).registerConfigurat } }); -class ToggleMarkersPanelAction extends TogglePanelAction { +class ToggleMarkersPanelAction extends ToggleViewAction { public static readonly ID = 'workbench.actions.view.problems'; public static readonly LABEL = Messages.MARKERS_PANEL_TOGGLE_LABEL; constructor(id: string, label: string, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @IPanelService panelService: IPanelService + @IViewsService viewsService: IViewsService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IContextKeyService contextKeyService: IContextKeyService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService ) { - super(id, label, Constants.MARKERS_CONTAINER_ID, panelService, layoutService); + super(id, label, Constants.MARKERS_VIEW_ID, viewsService, viewDescriptorService, contextKeyService, layoutService); } } @@ -111,6 +112,8 @@ class ToggleMarkersPanelAction extends TogglePanelAction { const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: Constants.MARKERS_CONTAINER_ID, name: Messages.MARKERS_PANEL_TITLE_PROBLEMS, + hideIfEmpty: true, + order: 0, ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [Constants.MARKERS_CONTAINER_ID, Constants.MARKERS_VIEW_STORAGE_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), focusCommand: { id: ToggleMarkersPanelAction.ID, keybindings: { @@ -121,8 +124,10 @@ const VIEW_CONTAINER: ViewContainer = Registry.as(ViewC Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ id: Constants.MARKERS_VIEW_ID, + containerIcon: 'codicon-warning', name: Messages.MARKERS_PANEL_TITLE_PROBLEMS, canToggleVisibility: false, + canMoveView: true, ctorDescriptor: new SyncDescriptor(MarkersView), }], VIEW_CONTAINER); @@ -211,7 +216,7 @@ registerAction2(class extends Action2 { id: Constants.MARKERS_VIEW_FOCUS_FILTER, title: localize('focusProblemsFilter', "Focus problems filter"), keybinding: { - when: Constants.MarkerViewFocusContextKey, + when: FocusedViewContext.isEqualTo(Constants.MARKERS_VIEW_ID), weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.KEY_F } @@ -259,6 +264,25 @@ registerAction2(class extends Action2 { } } }); +registerAction2(class extends Action2 { + constructor() { + super({ + id: Constants.MARKERS_VIEW_CLEAR_FILTER_TEXT, + title: localize('clearFiltersText', "Clear filters text"), + category: localize('problems', "Problems"), + keybinding: { + when: Constants.MarkerViewFilterFocusContextKey, + weight: KeybindingWeight.WorkbenchContrib, + } + }); + } + run(accessor: ServicesAccessor) { + const markersView = accessor.get(IViewsService).getActiveViewWithId(Constants.MARKERS_VIEW_ID); + if (markersView) { + markersView.clearFilterText(); + } + } +}); async function copyMarker(viewsService: IViewsService, clipboardService: IClipboardService) { const markersView = viewsService.getActiveViewWithId(Constants.MARKERS_VIEW_ID); diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index bb4a5763759..5bc57f81a4a 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -13,20 +13,20 @@ import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/ import Constants from 'vs/workbench/contrib/markers/browser/constants'; import { Marker, ResourceMarkers, RelatedInformation, MarkerChangesEvent } from 'vs/workbench/contrib/markers/browser/markersModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { MarkersFilterActionViewItem, MarkersFilterAction, IMarkersFilterActionChangeEvent, IMarkerFilterController } from 'vs/workbench/contrib/markers/browser/markersViewActions'; +import { MarkersFilterActionViewItem, MarkersFilters, IMarkersFiltersChangeEvent, IMarkerFilterController } from 'vs/workbench/contrib/markers/browser/markersViewActions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import Messages from 'vs/workbench/contrib/markers/browser/messages'; import { RangeHighlightDecorations } from 'vs/workbench/browser/parts/editor/rangeDecorations'; -import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IMarkersWorkbenchService } from 'vs/workbench/contrib/markers/browser/markers'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { localize } from 'vs/nls'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyService, ContextKeyEqualsExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Iterator } from 'vs/base/common/iterator'; import { ITreeElement, ITreeNode, ITreeContextMenuEvent, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { Relay, Event, Emitter } from 'vs/base/common/event'; -import { WorkbenchObjectTree, TreeResourceNavigator, IListService, IWorkbenchObjectTreeOptions } from 'vs/platform/list/browser/listService'; +import { WorkbenchObjectTree, ResourceNavigator, IListService, IWorkbenchObjectTreeOptions } from 'vs/platform/list/browser/listService'; import { FilterOptions } from 'vs/workbench/contrib/markers/browser/markersFilterOptions'; import { IExpression } from 'vs/base/common/glob'; import { deepClone } from 'vs/base/common/objects'; @@ -34,7 +34,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { FilterData, Filter, VirtualDelegate, ResourceMarkersRenderer, MarkerRenderer, RelatedInformationRenderer, TreeElement, MarkersTreeAccessibilityProvider, MarkersViewModel, ResourceDragAndDrop } from 'vs/workbench/contrib/markers/browser/markersTreeViewer'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Separator, ActionViewItem, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { domEvent } from 'vs/base/browser/event'; @@ -44,7 +44,6 @@ import { withUndefinedAsNull } from 'vs/base/common/types'; import { MementoObject, Memento } from 'vs/workbench/common/memento'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; import { KeyCode } from 'vs/base/common/keyCodes'; import { editorLightBulbForeground, editorLightBulbAutoFixForeground } from 'vs/platform/theme/common/colorRegistry'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; @@ -75,22 +74,28 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { private filterActionBar: ActionBar | undefined; private messageBoxContainer: HTMLElement | undefined; private ariaLabelElement: HTMLElement | undefined; - private readonly collapseAllAction: IAction; - private readonly filterAction: MarkersFilterAction; + readonly filters: MarkersFilters; private readonly panelState: MementoObject; - private panelFoucusContextKey: IContextKey; - private _onDidFilter = this._register(new Emitter()); - readonly onDidFilter: Event = this._onDidFilter.event; + private _onDidChangeFilterStats = this._register(new Emitter<{ total: number, filtered: number }>()); + readonly onDidChangeFilterStats: Event<{ total: number, filtered: number }> = this._onDidChangeFilterStats.event; private cachedFilterStats: { total: number; filtered: number; } | undefined = undefined; private currentResourceGotAddedToMarkersData: boolean = false; readonly markersViewModel: MarkersViewModel; - private isSmallLayout: boolean = false; + private readonly smallLayoutContextKey: IContextKey; + private get smallLayout(): boolean { return !!this.smallLayoutContextKey.get(); } + private set smallLayout(smallLayout: boolean) { this.smallLayoutContextKey.set(smallLayout); } readonly onDidChangeVisibility = this.onDidChangeBodyVisibility; + private readonly _onDidFocusFilter: Emitter = this._register(new Emitter()); + readonly onDidFocusFilter: Event = this._onDidFocusFilter.event; + + private readonly _onDidClearFilterText: Emitter = this._register(new Emitter()); + readonly onDidClearFilterText: Event = this._onDidClearFilterText.event; + constructor( options: IViewPaneOptions, @IInstantiationService instantiationService: IInstantiationService, @@ -108,8 +113,8 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, ) { - super({ ...(options as IViewPaneOptions), id: Constants.MARKERS_VIEW_ID, ariaHeaderLabel: Messages.MARKERS_PANEL_TITLE_PROBLEMS }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); - this.panelFoucusContextKey = Constants.MarkerViewFocusContextKey.bindTo(contextKeyService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + this.smallLayoutContextKey = Constants.MarkersViewSmallLayoutContextKey.bindTo(this.contextKeyService); this.panelState = new Memento(Constants.MARKERS_VIEW_STORAGE_ID, storageService).getMemento(StorageScope.WORKSPACE); this.markersViewModel = this._register(instantiationService.createInstance(MarkersViewModel, this.panelState['multiline'])); this._register(this.markersViewModel.onDidChange(marker => this.onDidChangeViewState(marker))); @@ -119,15 +124,16 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { this.rangeHighlightDecorations = this._register(this.instantiationService.createInstance(RangeHighlightDecorations)); // actions - this.collapseAllAction = this._register(new Action('vs.tree.collapse', localize('collapseAll', "Collapse All"), 'monaco-tree-action codicon-collapse-all', true, async () => this.collapseAll())); - this.filterAction = this._register(this.instantiationService.createInstance(MarkersFilterAction, { + this.regiserActions(); + this.filters = this._register(new MarkersFilters({ filterText: this.panelState['filter'] || '', filterHistory: this.panelState['filterHistory'] || [], showErrors: this.panelState['showErrors'] !== false, showWarnings: this.panelState['showWarnings'] !== false, showInfos: this.panelState['showInfos'] !== false, excludedFiles: !!this.panelState['useFilesExclude'], - activeFile: !!this.panelState['activeFile'] + activeFile: !!this.panelState['activeFile'], + layout: new dom.Dimension(0, 0) })); } @@ -146,9 +152,6 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { this.updateFilter(); - this._register(this.onDidFocus(() => this.panelFoucusContextKey.set(true))); - this._register(this.onDidBlur(() => this.panelFoucusContextKey.set(false))); - this._register(this.onDidChangeVisibility(visible => { if (visible) { this.refreshPanel(); @@ -157,7 +160,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { } })); - this.filterActionBar!.push(this.filterAction); + this.filterActionBar!.push(new Action(`workbench.actions.treeView.${this.id}.filter`)); this.renderContent(); } @@ -166,22 +169,21 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { } public layoutBody(height: number, width: number): void { - const wasSmallLayout = this.isSmallLayout; - this.isSmallLayout = width < 600 && height > 100; - if (this.isSmallLayout !== wasSmallLayout) { - this.updateActions(); + const wasSmallLayout = this.smallLayout; + this.smallLayout = width < 600 && height > 100; + if (this.smallLayout !== wasSmallLayout) { if (this.filterActionBar) { - dom.toggleClass(this.filterActionBar.getContainer(), 'hide', !this.isSmallLayout); + dom.toggleClass(this.filterActionBar.getContainer(), 'hide', !this.smallLayout); } } - const contentHeight = this.isSmallLayout ? height - 44 : height; + const contentHeight = this.smallLayout ? height - 44 : height; if (this.tree) { this.tree.layout(contentHeight, width); } if (this.messageBoxContainer) { this.messageBoxContainer.style.height = `${contentHeight}px`; } - this.filterAction.layout(this.isSmallLayout ? width : width - 200); + this.filters.layout = new dom.Dimension(this.smallLayout ? width : width - 200, height); } public focus(): void { @@ -197,14 +199,48 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { } public focusFilter(): void { - this.filterAction.focus(); + this._onDidFocusFilter.fire(); } - public getActions(): IAction[] { - if (this.isSmallLayout) { - return [this.collapseAllAction]; - } - return [this.filterAction, this.collapseAllAction]; + public clearFilterText(): void { + this._onDidClearFilterText.fire(); + } + + private regiserActions(): void { + const that = this; + this._register(registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.actions.treeView.${that.id}.collapseAll`, + title: localize('collapseAll', "Collapse All"), + menu: { + id: MenuId.ViewTitle, + when: ContextKeyEqualsExpr.create('view', that.id), + group: 'navigation', + order: Number.MAX_SAFE_INTEGER, + }, + icon: { id: 'codicon/collapse-all' } + }); + } + async run(): Promise { + return that.collapseAll(); + } + })); + this._register(registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.actions.treeView.${that.id}.filter`, + title: localize('filter', "Filter"), + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', that.id), Constants.MarkersViewSmallLayoutContextKey.negate()), + group: 'navigation', + order: 1, + }, + }); + } + async run(): Promise { } + })); } public showQuickFixes(marker: Marker): void { @@ -279,7 +315,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { const { total, filtered } = this.getFilterStats(); this.tree.toggleVisibility(total === 0 || filtered === 0); this.renderMessage(); - this._onDidFilter.fire(); + this._onDidChangeFilterStats.fire(this.getFilterStats()); } } @@ -292,7 +328,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { return; } let resourceMarkers: ResourceMarkers[] = []; - if (this.filterAction.activeFile) { + if (this.filters.activeFile) { if (this.currentActiveResource) { const activeResourceMarkers = this.markersWorkbenchService.markersModel.getResourceMarkers(this.currentActiveResource); if (activeResourceMarkers) { @@ -307,11 +343,11 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { private updateFilter() { this.cachedFilterStats = undefined; - this.filter.options = new FilterOptions(this.filterAction.filterText, this.getFilesExcludeExpressions(), this.filterAction.showWarnings, this.filterAction.showErrors, this.filterAction.showInfos); + this.filter.options = new FilterOptions(this.filters.filterText, this.getFilesExcludeExpressions(), this.filters.showWarnings, this.filters.showErrors, this.filters.showInfos); if (this.tree) { this.tree.refilter(); } - this._onDidFilter.fire(); + this._onDidChangeFilterStats.fire(this.getFilterStats()); const { total, filtered } = this.getFilterStats(); if (this.tree) { @@ -321,7 +357,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { } private getFilesExcludeExpressions(): { root: URI, expression: IExpression }[] | IExpression { - if (!this.filterAction.excludedFiles) { + if (!this.filters.excludedFiles) { return []; } @@ -338,7 +374,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { private createFilterActionBar(parent: HTMLElement): void { this.filterActionBar = this._register(new ActionBar(parent, { actionViewItemProvider: action => this.getActionViewItem(action) })); dom.addClass(this.filterActionBar.getContainer(), 'markers-panel-filter-container'); - dom.toggleClass(this.filterActionBar.getContainer(), 'hide', !this.isSmallLayout); + dom.toggleClass(this.filterActionBar.getContainer(), 'hide', !this.smallLayout); } private createMessageBox(parent: HTMLElement): void { @@ -382,7 +418,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { dnd: new ResourceDragAndDrop(this.instantiationService), expandOnlyOnTwistieClick: (e: TreeElement) => e instanceof Marker && e.relatedInformation.length > 0, overrideStyles: { - listBackground: PANEL_BACKGROUND + listBackground: this.getBackgroundColor() } }, )); @@ -396,7 +432,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { relatedInformationFocusContextKey.set(focus.elements.some(e => e instanceof RelatedInformation)); })); - const markersNavigator = this._register(new TreeResourceNavigator(this.tree, { openOnFocus: true })); + const markersNavigator = this._register(ResourceNavigator.createTreeResourceNavigator(this.tree, { openOnFocus: true })); this._register(Event.debounce(markersNavigator.onDidOpenResource, (last, event) => event, 75, true)(options => { this.openFileAtElement(options.element, !!options.editorOptions.preserveFocus, options.sideBySide, !!options.editorOptions.pinned); })); @@ -416,7 +452,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { this._register(this.tree.onContextMenu(this.onContextMenu, this)); this._register(this.configurationService.onDidChangeConfiguration(e => { - if (this.filterAction.excludedFiles && e.affectsConfiguration('files.exclude')) { + if (this.filters.excludedFiles && e.affectsConfiguration('files.exclude')) { this.updateFilter(); } })); @@ -424,7 +460,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { // move focus to input, whenever a key is pressed in the panel container this._register(domEvent(parent, 'keydown')(e => { if (this.keybindingService.mightProducePrintableCharacter(new StandardKeyboardEvent(e))) { - this.filterAction.focus(); + this.focusFilter(); } })); @@ -462,7 +498,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { if (this.tree) { this._register(this.tree.onDidChangeSelection(() => this.onSelected())); } - this._register(this.filterAction.onDidChange((event: IMarkersFilterActionChangeEvent) => { + this._register(this.filters.onDidChange((event: IMarkersFiltersChangeEvent) => { this.reportFilteringUsed(); if (event.activeFile) { this.refreshPanel(); @@ -508,7 +544,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { private onActiveEditorChanged(): void { this.setCurrentActiveEditor(); - if (this.filterAction.activeFile) { + if (this.filters.activeFile) { this.refreshPanel(); } this.autoReveal(); @@ -552,7 +588,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { if (filtered === 0) { this.messageBoxContainer.style.display = 'block'; this.messageBoxContainer.setAttribute('tabIndex', '0'); - if (this.filterAction.activeFile) { + if (this.filters.activeFile) { this.renderFilterMessageForActiveFile(this.messageBoxContainer); } else { if (total > 0) { @@ -611,16 +647,16 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { } private clearFilters(): void { - this.filterAction.filterText = ''; - this.filterAction.excludedFiles = false; - this.filterAction.showErrors = true; - this.filterAction.showWarnings = true; - this.filterAction.showInfos = true; + this.filters.filterText = ''; + this.filters.excludedFiles = false; + this.filters.showErrors = true; + this.filters.showWarnings = true; + this.filters.showInfos = true; } private autoReveal(focus: boolean = false): void { // No need to auto reveal if active file filter is on - if (this.filterAction.activeFile || !this.tree) { + if (this.filters.activeFile || !this.tree) { return; } let autoReveal = this.configurationService.getValue('problems.autoReveal'); @@ -749,16 +785,12 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { } public getActionViewItem(action: IAction): IActionViewItem | undefined { - if (action.id === MarkersFilterAction.ID) { - return this.instantiationService.createInstance(MarkersFilterActionViewItem, this.filterAction, this); + if (action.id === `workbench.actions.treeView.${this.id}.filter`) { + return this.instantiationService.createInstance(MarkersFilterActionViewItem, action, this); } return super.getActionViewItem(action); } - getFilterOptions(): FilterOptions { - return this.filter.options; - } - getFilterStats(): { total: number; filtered: number; } { if (!this.cachedFilterStats) { this.cachedFilterStats = this.computeFilterStats(); @@ -790,11 +822,11 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { private reportFilteringUsed(): void { const data = { - errors: this.filterAction.showErrors, - warnings: this.filterAction.showWarnings, - infos: this.filterAction.showInfos, - activeFile: this.filterAction.activeFile, - excludedFiles: this.filterAction.excludedFiles, + errors: this.filters.showErrors, + warnings: this.filters.showWarnings, + infos: this.filters.showInfos, + activeFile: this.filters.activeFile, + excludedFiles: this.filters.excludedFiles, }; /* __GDPR__ "problems.filter" : { @@ -809,13 +841,13 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { } saveState(): void { - this.panelState['filter'] = this.filterAction.filterText; - this.panelState['filterHistory'] = this.filterAction.filterHistory; - this.panelState['showErrors'] = this.filterAction.showErrors; - this.panelState['showWarnings'] = this.filterAction.showWarnings; - this.panelState['showInfos'] = this.filterAction.showInfos; - this.panelState['useFilesExclude'] = this.filterAction.excludedFiles; - this.panelState['activeFile'] = this.filterAction.activeFile; + this.panelState['filter'] = this.filters.filterText; + this.panelState['filterHistory'] = this.filters.filterHistory; + this.panelState['showErrors'] = this.filters.showErrors; + this.panelState['showWarnings'] = this.filters.showWarnings; + this.panelState['showInfos'] = this.filters.showInfos; + this.panelState['useFilesExclude'] = this.filters.excludedFiles; + this.panelState['activeFile'] = this.filters.activeFile; this.panelState['multiline'] = this.markersViewModel.multiline; super.saveState(); @@ -852,7 +884,7 @@ class MarkersTree extends WorkbenchObjectTree { } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { // Lightbulb Icon const editorLightBulbForegroundColor = theme.getColor(editorLightBulbForeground); diff --git a/src/vs/workbench/contrib/markers/browser/markersViewActions.ts b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts index 3130ecaee2e..0f424cd9e16 100644 --- a/src/vs/workbench/contrib/markers/browser/markersViewActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts @@ -5,16 +5,16 @@ import { Delayer } from 'vs/base/common/async'; import * as DOM from 'vs/base/browser/dom'; -import { Action, IActionChangeEvent, IAction, IActionRunner } from 'vs/base/common/actions'; +import { Action, IAction, IActionRunner } from 'vs/base/common/actions'; import { HistoryInputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import Messages from 'vs/workbench/contrib/markers/browser/messages'; import Constants from 'vs/workbench/contrib/markers/browser/constants'; -import { IThemeService, registerThemingParticipant, ICssStyleCollector, ITheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, ICssStyleCollector, IColorTheme } from 'vs/platform/theme/common/themeService'; import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; -import { toDisposable } from 'vs/base/common/lifecycle'; +import { toDisposable, Disposable } from 'vs/base/common/lifecycle'; import { BaseActionViewItem, ActionViewItem, ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { badgeBackground, badgeForeground, contrastBorder, inputActiveOptionBorder, inputActiveOptionBackground } from 'vs/platform/theme/common/colorRegistry'; import { localize } from 'vs/nls'; @@ -23,7 +23,6 @@ import { ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedH import { Marker } from 'vs/workbench/contrib/markers/browser/markersModel'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Event, Emitter } from 'vs/base/common/event'; -import { FilterOptions } from 'vs/workbench/contrib/markers/browser/markersFilterOptions'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IViewsService } from 'vs/workbench/common/views'; @@ -44,16 +43,17 @@ export class ShowProblemsPanelAction extends Action { } } -export interface IMarkersFilterActionChangeEvent extends IActionChangeEvent { +export interface IMarkersFiltersChangeEvent { filterText?: boolean; excludedFiles?: boolean; showWarnings?: boolean; showErrors?: boolean; showInfos?: boolean; activeFile?: boolean; + layout?: boolean; } -export interface IMarkersFilterActionOptions { +export interface IMarkersFiltersOptions { filterText: string; filterHistory: string[]; showErrors: boolean; @@ -61,17 +61,16 @@ export interface IMarkersFilterActionOptions { showInfos: boolean; excludedFiles: boolean; activeFile: boolean; + layout: DOM.Dimension; } -export class MarkersFilterAction extends Action { +export class MarkersFilters extends Disposable { - public static readonly ID: string = 'workbench.actions.problems.filter'; + private readonly _onDidChange: Emitter = this._register(new Emitter()); + readonly onDidChange: Event = this._onDidChange.event; - private readonly _onFocus: Emitter = this._register(new Emitter()); - readonly onFocus: Event = this._onFocus.event; - - constructor(options: IMarkersFilterActionOptions) { - super(MarkersFilterAction.ID, Messages.MARKERS_PANEL_ACTION_TOOLTIP_FILTER, 'markers-panel-action-filter', true); + constructor(options: IMarkersFiltersOptions) { + super(); this._filterText = options.filterText; this._showErrors = options.showErrors; this._showWarnings = options.showWarnings; @@ -79,6 +78,7 @@ export class MarkersFilterAction extends Action { this._excludedFiles = options.excludedFiles; this._activeFile = options.activeFile; this.filterHistory = options.filterHistory; + this._layout = options.layout; } private _filterText: string; @@ -88,7 +88,7 @@ export class MarkersFilterAction extends Action { set filterText(filterText: string) { if (this._filterText !== filterText) { this._filterText = filterText; - this._onDidChange.fire({ filterText: true }); + this._onDidChange.fire({ filterText: true }); } } @@ -101,7 +101,7 @@ export class MarkersFilterAction extends Action { set excludedFiles(filesExclude: boolean) { if (this._excludedFiles !== filesExclude) { this._excludedFiles = filesExclude; - this._onDidChange.fire({ excludedFiles: true }); + this._onDidChange.fire({ excludedFiles: true }); } } @@ -112,7 +112,7 @@ export class MarkersFilterAction extends Action { set activeFile(activeFile: boolean) { if (this._activeFile !== activeFile) { this._activeFile = activeFile; - this._onDidChange.fire({ activeFile: true }); + this._onDidChange.fire({ activeFile: true }); } } @@ -123,7 +123,7 @@ export class MarkersFilterAction extends Action { set showWarnings(showWarnings: boolean) { if (this._showWarnings !== showWarnings) { this._showWarnings = showWarnings; - this._onDidChange.fire({ showWarnings: true }); + this._onDidChange.fire({ showWarnings: true }); } } @@ -134,7 +134,7 @@ export class MarkersFilterAction extends Action { set showErrors(showErrors: boolean) { if (this._showErrors !== showErrors) { this._showErrors = showErrors; - this._onDidChange.fire({ showErrors: true }); + this._onDidChange.fire({ showErrors: true }); } } @@ -145,35 +145,34 @@ export class MarkersFilterAction extends Action { set showInfos(showInfos: boolean) { if (this._showInfos !== showInfos) { this._showInfos = showInfos; - this._onDidChange.fire({ showInfos: true }); + this._onDidChange.fire({ showInfos: true }); } } - focus(): void { - this._onFocus.fire(); + private _layout: DOM.Dimension = new DOM.Dimension(0, 0); + get layout(): DOM.Dimension { + return this._layout; } - - layout(width: number): void { - if (width > 600) { - this.class = 'markers-panel-action-filter grow'; - } else if (width < 400) { - this.class = 'markers-panel-action-filter small'; - } else { - this.class = 'markers-panel-action-filter'; + set layout(layout: DOM.Dimension) { + if (this._layout.width !== layout.width || this._layout.height !== layout.height) { + this._layout = layout; + this._onDidChange.fire({ layout: true }); } } } export interface IMarkerFilterController { - onDidFilter: Event; - getFilterOptions(): FilterOptions; + readonly onDidFocusFilter: Event; + readonly onDidClearFilterText: Event; + readonly filters: MarkersFilters; + readonly onDidChangeFilterStats: Event<{ total: number, filtered: number }>; getFilterStats(): { total: number, filtered: number }; } class FiltersDropdownMenuActionViewItem extends DropdownMenuActionViewItem { constructor( - action: IAction, private filterAction: MarkersFilterAction, actionRunner: IActionRunner, + action: IAction, private filters: MarkersFilters, actionRunner: IActionRunner, @IContextMenuService contextMenuService: IContextMenuService ) { super(action, @@ -194,53 +193,53 @@ class FiltersDropdownMenuActionViewItem extends DropdownMenuActionViewItem { private getActions(): IAction[] { return [ { - checked: this.filterAction.showErrors, + checked: this.filters.showErrors, class: undefined, enabled: true, id: 'showErrors', label: Messages.MARKERS_PANEL_FILTER_LABEL_SHOW_ERRORS, - run: async () => this.filterAction.showErrors = !this.filterAction.showErrors, + run: async () => this.filters.showErrors = !this.filters.showErrors, tooltip: '', dispose: () => null }, { - checked: this.filterAction.showWarnings, + checked: this.filters.showWarnings, class: undefined, enabled: true, id: 'showWarnings', label: Messages.MARKERS_PANEL_FILTER_LABEL_SHOW_WARNINGS, - run: async () => this.filterAction.showWarnings = !this.filterAction.showWarnings, + run: async () => this.filters.showWarnings = !this.filters.showWarnings, tooltip: '', dispose: () => null }, { - checked: this.filterAction.showInfos, + checked: this.filters.showInfos, class: undefined, enabled: true, id: 'showInfos', label: Messages.MARKERS_PANEL_FILTER_LABEL_SHOW_INFOS, - run: async () => this.filterAction.showInfos = !this.filterAction.showInfos, + run: async () => this.filters.showInfos = !this.filters.showInfos, tooltip: '', dispose: () => null }, new Separator(), { - checked: this.filterAction.activeFile, + checked: this.filters.activeFile, class: undefined, enabled: true, id: 'activeFile', label: Messages.MARKERS_PANEL_FILTER_LABEL_ACTIVE_FILE, - run: async () => this.filterAction.activeFile = !this.filterAction.activeFile, + run: async () => this.filters.activeFile = !this.filters.activeFile, tooltip: '', dispose: () => null }, { - checked: this.filterAction.excludedFiles, + checked: this.filters.excludedFiles, class: undefined, enabled: true, id: 'useFilesExclude', label: Messages.MARKERS_PANEL_FILTER_LABEL_EXCLUDED_FILES, - run: async () => this.filterAction.excludedFiles = !this.filterAction.excludedFiles, + run: async () => this.filters.excludedFiles = !this.filters.excludedFiles, tooltip: '', dispose: () => null }, @@ -263,7 +262,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { private readonly filtersAction: IAction; constructor( - readonly action: MarkersFilterAction, + action: IAction, private filterController: IMarkerFilterController, @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextViewService private readonly contextViewService: IContextViewService, @@ -274,10 +273,11 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { this.focusContextKey = Constants.MarkerViewFilterFocusContextKey.bindTo(contextKeyService); this.delayedFilterUpdate = new Delayer(200); this._register(toDisposable(() => this.delayedFilterUpdate.cancel())); - this._register(action.onFocus(() => this.focus())); + this._register(filterController.onDidFocusFilter(() => this.focus())); + this._register(filterController.onDidClearFilterText(() => this.clearFilterText())); this.filtersAction = new Action('markersFiltersAction', Messages.MARKERS_PANEL_ACTION_TOOLTIP_MORE_FILTERS, 'markers-filters codicon-filter'); this.filtersAction.checked = this.hasFiltersChanged(); - this._register(action.onDidChange(() => this.filtersAction.checked = this.hasFiltersChanged())); + this._register(filterController.filters.onDidChange(e => this.onDidFiltersChange(e))); } render(container: HTMLElement): void { @@ -285,7 +285,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { DOM.addClass(this.container, 'markers-panel-action-filter-container'); this.element = DOM.append(this.container, DOM.$('')); - this.element.className = this.action.class || ''; + this.element.className = this.class; this.createInput(this.element); this.createControls(this.element); this.updateClass(); @@ -299,23 +299,36 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { } } + private clearFilterText(): void { + if (this.filterInputBox) { + this.filterInputBox.value = ''; + } + } + + private onDidFiltersChange(e: IMarkersFiltersChangeEvent): void { + this.filtersAction.checked = this.hasFiltersChanged(); + if (e.layout) { + this.updateClass(); + } + } + private hasFiltersChanged(): boolean { - return !this.action.showErrors || !this.action.showWarnings || !this.action.showInfos || this.action.excludedFiles || this.action.activeFile; + return !this.filterController.filters.showErrors || !this.filterController.filters.showWarnings || !this.filterController.filters.showInfos || this.filterController.filters.excludedFiles || this.filterController.filters.activeFile; } private createInput(container: HTMLElement): void { this.filterInputBox = this._register(this.instantiationService.createInstance(ContextScopedHistoryInputBox, container, this.contextViewService, { placeholder: Messages.MARKERS_PANEL_FILTER_PLACEHOLDER, ariaLabel: Messages.MARKERS_PANEL_FILTER_ARIA_LABEL, - history: this.action.filterHistory + history: this.filterController.filters.filterHistory })); this.filterInputBox.inputElement.setAttribute('aria-labelledby', 'markers-panel-arialabel'); this._register(attachInputBoxStyler(this.filterInputBox, this.themeService)); - this.filterInputBox.value = this.action.filterText; + this.filterInputBox.value = this.filterController.filters.filterText; this._register(this.filterInputBox.onDidChange(filter => this.delayedFilterUpdate.trigger(() => this.onDidInputChange(this.filterInputBox!)))); - this._register(this.action.onDidChange((event: IMarkersFilterActionChangeEvent) => { + this._register(this.filterController.filters.onDidChange((event: IMarkersFiltersChangeEvent) => { if (event.filterText) { - this.filterInputBox!.value = this.action.filterText; + this.filterInputBox!.value = this.filterController.filters.filterText; } })); this._register(DOM.addStandardDisposableListener(this.filterInputBox.inputElement, DOM.EventType.KEY_DOWN, (e: any) => this.onInputKeyDown(e, this.filterInputBox!))); @@ -349,14 +362,14 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { filterBadge.style.color = foreground; })); this.updateBadge(); - this._register(this.filterController.onDidFilter(() => this.updateBadge())); + this._register(this.filterController.onDidChangeFilterStats(() => this.updateBadge())); } private createFilters(container: HTMLElement): void { const actionbar = this._register(new ActionBar(container, { actionViewItemProvider: action => { if (action.id === this.filtersAction.id) { - return this.instantiationService.createInstance(FiltersDropdownMenuActionViewItem, action, this.action, this.actionRunner); + return this.instantiationService.createInstance(FiltersDropdownMenuActionViewItem, action, this.filterController.filters, this.actionRunner); } return undefined; } @@ -366,8 +379,8 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { private onDidInputChange(inputbox: HistoryInputBox) { inputbox.addToHistory(); - this.action.filterText = inputbox.value; - this.action.filterHistory = inputbox.getHistory(); + this.filterController.filters.filterText = inputbox.value; + this.filterController.filters.filterHistory = inputbox.getHistory(); } private updateBadge(): void { @@ -399,7 +412,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { private onInputKeyDown(event: StandardKeyboardEvent, filterInputBox: HistoryInputBox) { let handled = false; if (event.equals(KeyCode.Escape)) { - filterInputBox.value = ''; + this.clearFilterText(); handled = true; } if (handled) { @@ -410,11 +423,21 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { protected updateClass(): void { if (this.element && this.container) { - this.element.className = this.action.class || ''; + this.element.className = this.class; DOM.toggleClass(this.container, 'grow', DOM.hasClass(this.element, 'grow')); this.adjustInputBox(); } } + + protected get class(): string { + if (this.filterController.filters.layout.width > 600) { + return 'markers-panel-action-filter grow'; + } else if (this.filterController.filters.layout.width < 400) { + return 'markers-panel-action-filter small'; + } else { + return 'markers-panel-action-filter'; + } + } } export class QuickFixAction extends Action { @@ -482,7 +505,7 @@ export class QuickFixActionViewItem extends ActionViewItem { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const inputActiveOptionBorderColor = theme.getColor(inputActiveOptionBorder); if (inputActiveOptionBorderColor) { collector.addRule(`.markers-panel-action-filter > .markers-panel-filter-controls > .monaco-action-bar .action-label.markers-filters.checked { border-color: ${inputActiveOptionBorderColor}; }`); diff --git a/src/vs/workbench/contrib/notebook/browser/constants.ts b/src/vs/workbench/contrib/notebook/browser/constants.ts new file mode 100644 index 00000000000..227cf8c490e --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/constants.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. + *--------------------------------------------------------------------------------------------*/ + +// Cell sizing related +export const CELL_MARGIN = 20; +export const CELL_RUN_GUTTER = 32; // TODO should be dynamic based on execution order width, and runnable enablement + +export const EDITOR_TOOLBAR_HEIGHT = 20; +export const BOTTOM_CELL_TOOLBAR_HEIGHT = 32; + +// Top margin of editor +export const EDITOR_TOP_MARGIN = 0; + +// Top and bottom padding inside the monaco editor in a cell, which are included in `cell.editorHeight` +export const EDITOR_TOP_PADDING = 8; +export const EDITOR_BOTTOM_PADDING = 8; + +// Cell context keys + +export const NOTEBOOK_VIEW_TYPE = 'notebookViewType'; +export const NOTEBOOK_CELL_TYPE_CONTEXT_KEY = 'notebookCellType'; // code, markdown +export const NOTEBOOK_CELL_EDITABLE_CONTEXT_KEY = 'notebookCellEditable'; // bool +export const NOTEBOOK_CELL_MARKDOWN_EDIT_MODE_CONTEXT_KEY = 'notebookCellMarkdownEditMode'; // bool +export const NOTEBOOK_CELL_RUN_STATE_CONTEXT_KEY = 'notebookCellRunState'; // idle, running + +// Notebook context keys +export const NOTEBOOK_EDITABLE_CONTEXT_KEY = 'notebookEditable'; +export const NOTEBOOK_EXECUTING_KEY = 'notebookExecuting'; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookActions.ts new file mode 100644 index 00000000000..4cfefe25cdb --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookActions.ts @@ -0,0 +1,1014 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { localize } from 'vs/nls'; +import { Action2, IAction2Options, MenuId, MenuItemAction, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { InputFocusedContext, InputFocusedContextKey, IsDevelopmentContext } from 'vs/platform/contextkey/common/contextkeys'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { NOTEBOOK_CELL_EDITABLE_CONTEXT_KEY, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE_CONTEXT_KEY, NOTEBOOK_CELL_TYPE_CONTEXT_KEY, NOTEBOOK_EDITABLE_CONTEXT_KEY, NOTEBOOK_EXECUTING_KEY } from 'vs/workbench/contrib/notebook/browser/constants'; +import { BaseCellRenderTemplate, CellEditState, CellRunState, ICellViewModel, INotebookEditor, KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; + +const INSERT_CODE_CELL_ABOVE_COMMAND_ID = 'workbench.notebook.code.insertCellAbove'; +const INSERT_CODE_CELL_BELOW_COMMAND_ID = 'workbench.notebook.code.insertCellBelow'; +const INSERT_MARKDOWN_CELL_ABOVE_COMMAND_ID = 'workbench.notebook.markdown.insertCellAbove'; +const INSERT_MARKDOWN_CELL_BELOW_COMMAND_ID = 'workbench.notebook.markdown.insertCellBelow'; + +const EDIT_CELL_COMMAND_ID = 'workbench.notebook.cell.edit'; +const SAVE_CELL_COMMAND_ID = 'workbench.notebook.cell.save'; +const DELETE_CELL_COMMAND_ID = 'workbench.notebook.cell.delete'; + +const MOVE_CELL_UP_COMMAND_ID = 'workbench.notebook.cell.moveUp'; +const MOVE_CELL_DOWN_COMMAND_ID = 'workbench.notebook.cell.moveDown'; +const COPY_CELL_UP_COMMAND_ID = 'workbench.notebook.cell.copyUp'; +const COPY_CELL_DOWN_COMMAND_ID = 'workbench.notebook.cell.copyDown'; + +const EXECUTE_CELL_COMMAND_ID = 'workbench.notebook.cell.execute'; +const EXECUTE_ACTIVE_CELL_COMMAND_ID = 'workbench.notebook.cell.executeActive'; +const CANCEL_CELL_COMMAND_ID = 'workbench.notebook.cell.cancelExecution'; +const EXECUTE_NOTEBOOK_COMMAND_ID = 'workbench.notebook.executeNotebook'; +const CANCEL_NOTEBOOK_COMMAND_ID = 'workbench.notebook.cancelExecution'; + +const NOTEBOOK_ACTIONS_CATEGORY = localize('notebookActions.category', "Notebook"); + +const enum CellToolbarOrder { + MoveCellUp, + MoveCellDown, + EditCell, + SaveCell, + InsertCell, + DeleteCell +} + +registerAction2(class extends Action2 { + constructor() { + super({ + id: EXECUTE_CELL_COMMAND_ID, + category: NOTEBOOK_ACTIONS_CATEGORY, + title: localize('notebookActions.execute', "Execute Cell"), + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext), + primary: KeyMod.WinCtrl | KeyCode.Enter, + win: { + primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.Enter + }, + weight: KeybindingWeight.WorkbenchContrib + }, + icon: { id: 'codicon/play' }, + f1: true + }); + } + + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext): Promise { + if (!context) { + context = getActiveCellContext(accessor); + if (!context) { + return; + } + } + + runCell(context); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: CANCEL_CELL_COMMAND_ID, + title: localize('notebookActions.cancel', "Stop Execution"), + icon: { id: 'codicon/primitive-square' } + }); + } + + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext): Promise { + if (!context) { + context = getActiveCellContext(accessor); + if (!context) { + return; + } + } + + return context.notebookEditor.cancelNotebookCellExecution(context.cell); + } +}); + +export class ExecuteCellAction extends MenuItemAction { + constructor( + @IContextKeyService contextKeyService: IContextKeyService, + @ICommandService commandService: ICommandService + ) { + super( + { + id: EXECUTE_CELL_COMMAND_ID, + title: localize('notebookActions.executeCell', "Execute Cell"), + icon: { id: 'codicon/play' } + }, + undefined, + { shouldForwardArgs: true }, + contextKeyService, + commandService); + } +} + +export class CancelCellAction extends MenuItemAction { + constructor( + @IContextKeyService contextKeyService: IContextKeyService, + @ICommandService commandService: ICommandService + ) { + super( + { + id: CANCEL_CELL_COMMAND_ID, + title: localize('notebookActions.CancelCell', "Cancel Execution"), + icon: { id: 'codicon/primitive-square' } + }, + undefined, + { shouldForwardArgs: true }, + contextKeyService, + commandService); + } +} + + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.executeNotebookCellSelectBelow', + title: localize('notebookActions.executeAndSelectBelow', "Execute Notebook Cell and Select Below"), + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext), + primary: KeyMod.Shift | KeyCode.Enter, + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + + async run(accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + const activeCell = await runActiveCell(accessor); + if (!activeCell) { + return; + } + + const editor = getActiveNotebookEditor(editorService); + if (!editor) { + return; + } + + const idx = editor.viewModel?.getViewCellIndex(activeCell); + if (typeof idx !== 'number') { + return; + } + + // Try to select below, fall back on inserting + const nextCell = editor.viewModel?.viewCells[idx + 1]; + if (nextCell) { + editor.focusNotebookCell(nextCell, false); + } else { + await editor.insertNotebookCell(activeCell, CellKind.Code, 'below'); + } + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.executeNotebookCellInsertBelow', + title: localize('notebookActions.executeAndInsertBelow', "Execute Notebook Cell and Insert Below"), + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext), + primary: KeyMod.Alt | KeyCode.Enter, + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + + async run(accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + const activeCell = await runActiveCell(accessor); + if (!activeCell) { + return; + } + + const editor = getActiveNotebookEditor(editorService); + if (!editor) { + return; + } + + await editor.insertNotebookCell(activeCell, CellKind.Code, 'below'); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: EXECUTE_NOTEBOOK_COMMAND_ID, + title: localize('notebookActions.executeNotebook', "Execute Notebook") + }); + } + + async run(accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + const editor = getActiveNotebookEditor(editorService); + if (!editor) { + return; + } + + return editor.executeNotebook(); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: CANCEL_NOTEBOOK_COMMAND_ID, + title: localize('notebookActions.cancelNotebook', "Cancel Notebook Execution") + }); + } + + async run(accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + const editor = getActiveNotebookEditor(editorService); + if (!editor) { + return; + } + + return editor.cancelNotebookExecution(); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: EXECUTE_ACTIVE_CELL_COMMAND_ID, + title: localize('notebookActions.executeNotebookCell', "Execute Notebook Active Cell") + }); + } + + async run(accessor: ServicesAccessor): Promise { + let editorService = accessor.get(IEditorService); + let editor = getActiveNotebookEditor(editorService); + + if (!editor) { + return; + } + + let activeCell = editor.getActiveCell(); + if (activeCell) { + return editor.executeNotebookCell(activeCell); + } + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.quitNotebookEdit', + title: localize('notebookActions.quitEditing', "Quit Notebook Cell Editing"), + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext), + primary: KeyCode.Escape, + weight: KeybindingWeight.EditorContrib - 5 + } + }); + } + + async run(accessor: ServicesAccessor): Promise { + let editorService = accessor.get(IEditorService); + let editor = getActiveNotebookEditor(editorService); + + if (!editor) { + return; + } + + let activeCell = editor.getActiveCell(); + if (activeCell) { + if (activeCell.cellKind === CellKind.Markdown) { + activeCell.editState = CellEditState.Preview; + } + + editor.focusNotebookCell(activeCell, false); + } + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.notebook.hideFind', + title: localize('notebookActions.hideFind', "Hide Find in Notebook"), + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED), + primary: KeyCode.Escape, + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + + async run(accessor: ServicesAccessor): Promise { + let editorService = accessor.get(IEditorService); + let editor = getActiveNotebookEditor(editorService); + + editor?.hideFind(); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.notebook.find', + title: localize('notebookActions.findInNotebook', "Find in Notebook"), + keybinding: { + when: NOTEBOOK_EDITOR_FOCUSED, + primary: KeyCode.KEY_F | KeyMod.CtrlCmd, + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + + async run(accessor: ServicesAccessor): Promise { + let editorService = accessor.get(IEditorService); + let editor = getActiveNotebookEditor(editorService); + + editor?.showFind(); + } +}); + +MenuRegistry.appendMenuItem(MenuId.EditorTitle, { + command: { + id: EXECUTE_NOTEBOOK_COMMAND_ID, + title: localize('notebookActions.menu.executeNotebook', "Execute Notebook (Run all cells)"), + icon: { id: 'codicon/run-all' } + }, + order: -1, + group: 'navigation', + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(NOTEBOOK_EXECUTING_KEY)) +}); + +MenuRegistry.appendMenuItem(MenuId.EditorTitle, { + command: { + id: CANCEL_NOTEBOOK_COMMAND_ID, + title: localize('notebookActions.menu.cancelNotebook', "Stop Notebook Execution"), + icon: { id: 'codicon/primitive-square' } + }, + order: -1, + group: 'navigation', + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK) +}); + + +MenuRegistry.appendMenuItem(MenuId.EditorTitle, { + command: { + id: EXECUTE_ACTIVE_CELL_COMMAND_ID, + title: localize('notebookActions.menu.execute', "Execute Notebook Cell"), + icon: { id: 'codicon/run' } + }, + order: 0, + group: 'navigation', + when: NOTEBOOK_EDITOR_FOCUSED +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.changeCellToCode', + title: localize('notebookActions.changeCellToCode', "Change Cell to Code"), + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), + primary: KeyCode.KEY_Y, + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + + async run(accessor: ServicesAccessor): Promise { + return changeActiveCellToKind(CellKind.Code, accessor); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.changeCellToMarkdown', + title: localize('notebookActions.changeCellToMarkdown', "Change Cell to Markdown"), + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), + primary: KeyCode.KEY_M, + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext): Promise { + return changeActiveCellToKind(CellKind.Markdown, accessor); + } +}); + +export function getActiveNotebookEditor(editorService: IEditorService): INotebookEditor | undefined { + // TODO can `isNotebookEditor` be on INotebookEditor to avoid a circular dependency? + const activeEditorPane = editorService.activeEditorPane as any | undefined; + return activeEditorPane?.isNotebookEditor ? activeEditorPane : undefined; +} + +async function runActiveCell(accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + const editor = getActiveNotebookEditor(editorService); + if (!editor) { + return; + } + + const activeCell = editor.getActiveCell(); + if (!activeCell) { + return; + } + + editor.executeNotebookCell(activeCell); + return activeCell; +} + +async function runCell(context: INotebookCellActionContext): Promise { + if (context.cell.runState === CellRunState.Running) { + return; + } + + return context.notebookEditor.executeNotebookCell(context.cell); +} + +async function changeActiveCellToKind(kind: CellKind, accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + const editor = getActiveNotebookEditor(editorService); + if (!editor) { + return; + } + + const activeCell = editor.getActiveCell(); + if (!activeCell) { + return; + } + + if (activeCell.cellKind === kind) { + return; + } + + const text = activeCell.getText(); + await editor.insertNotebookCell(activeCell, kind, 'below', text); + const idx = editor.viewModel?.getViewCellIndex(activeCell); + if (typeof idx !== 'number') { + return; + } + + const newCell = editor.viewModel?.viewCells[idx + 1]; + if (!newCell) { + return; + } + + editor.focusNotebookCell(newCell, true); + editor.deleteNotebookCell(activeCell); +} + +export interface INotebookCellActionContext { + cellTemplate?: BaseCellRenderTemplate; + cell: ICellViewModel; + notebookEditor: INotebookEditor; +} + +function getActiveCellContext(accessor: ServicesAccessor): INotebookCellActionContext | undefined { + const editorService = accessor.get(IEditorService); + + const editor = getActiveNotebookEditor(editorService); + if (!editor) { + return; + } + + const activeCell = editor.getActiveCell(); + if (!activeCell) { + return; + } + + return { + cell: activeCell, + notebookEditor: editor + }; +} + +abstract class InsertCellCommand extends Action2 { + constructor( + desc: Readonly, + private kind: CellKind, + private direction: 'above' | 'below' + ) { + super(desc); + } + + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext): Promise { + if (!context) { + context = getActiveCellContext(accessor); + if (!context) { + return; + } + } + + await context.notebookEditor.insertNotebookCell(context.cell, this.kind, this.direction); + } +} + +registerAction2(class extends InsertCellCommand { + constructor() { + super( + { + id: INSERT_CODE_CELL_ABOVE_COMMAND_ID, + title: localize('notebookActions.insertCodeCellAbove', "Insert Code Cell Above") + }, + CellKind.Code, + 'above'); + } +}); + +export class InsertCodeCellAction extends MenuItemAction { + constructor( + @IContextKeyService contextKeyService: IContextKeyService, + @ICommandService commandService: ICommandService + ) { + super( + { + id: INSERT_CODE_CELL_BELOW_COMMAND_ID, + title: localize('notebookActions.insertCodeCellBelow', "Insert Code Cell Below"), + // icon: { id: 'codicon/add' }, + }, + undefined, + { shouldForwardArgs: true }, + contextKeyService, + commandService); + } +} + +registerAction2(class extends InsertCellCommand { + constructor() { + super( + { + id: INSERT_CODE_CELL_BELOW_COMMAND_ID, + title: localize('notebookActions.insertCodeCellBelow', "Insert Code Cell Below"), + category: NOTEBOOK_ACTIONS_CATEGORY, + icon: { id: 'codicon/add' }, + menu: { + id: MenuId.NotebookCellTitle, + order: CellToolbarOrder.InsertCell, + alt: { + id: INSERT_MARKDOWN_CELL_BELOW_COMMAND_ID, + title: localize('notebookActions.insertMarkdownCellBelow', "Insert Markdown Cell Below"), + icon: { id: 'codicon/add' }, + }, + when: ContextKeyExpr.equals(NOTEBOOK_EDITABLE_CONTEXT_KEY, true) + }, + f1: true + }, + CellKind.Code, + 'below'); + } +}); + +registerAction2(class extends InsertCellCommand { + constructor() { + super( + { + id: INSERT_MARKDOWN_CELL_ABOVE_COMMAND_ID, + title: localize('notebookActions.insertMarkdownCellAbove', "Insert Markdown Cell Above"), + }, + CellKind.Markdown, + 'above'); + } +}); + +export class InsertMarkdownCellAction extends MenuItemAction { + constructor( + @IContextKeyService contextKeyService: IContextKeyService, + @ICommandService commandService: ICommandService + ) { + super( + { + id: INSERT_MARKDOWN_CELL_BELOW_COMMAND_ID, + title: localize('notebookActions.insertMarkdownCellBelow', "Insert Markdown Cell Below") + }, + undefined, + { shouldForwardArgs: true }, + contextKeyService, + commandService); + } +} + +registerAction2(class extends InsertCellCommand { + constructor() { + super( + { + id: INSERT_MARKDOWN_CELL_BELOW_COMMAND_ID, + title: localize('notebookActions.insertMarkdownCellBelow', "Insert Markdown Cell Below") + }, + CellKind.Markdown, + 'below'); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super( + { + id: EDIT_CELL_COMMAND_ID, + title: localize('notebookActions.editCell', "Edit Cell"), + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), + primary: KeyCode.Enter, + weight: KeybindingWeight.WorkbenchContrib + }, + menu: { + id: MenuId.NotebookCellTitle, + when: ContextKeyExpr.and( + ContextKeyExpr.equals(NOTEBOOK_CELL_TYPE_CONTEXT_KEY, 'markdown'), + ContextKeyExpr.equals(NOTEBOOK_CELL_MARKDOWN_EDIT_MODE_CONTEXT_KEY, false), + ContextKeyExpr.equals(NOTEBOOK_CELL_EDITABLE_CONTEXT_KEY, true)), + order: CellToolbarOrder.EditCell + }, + icon: { id: 'codicon/pencil' } + }); + } + + run(accessor: ServicesAccessor, context?: INotebookCellActionContext) { + if (!context) { + context = getActiveCellContext(accessor); + if (!context) { + return; + } + } + + return context.notebookEditor.editNotebookCell(context.cell); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super( + { + id: SAVE_CELL_COMMAND_ID, + title: localize('notebookActions.saveCell', "Save Cell"), + menu: { + id: MenuId.NotebookCellTitle, + when: ContextKeyExpr.and( + ContextKeyExpr.equals(NOTEBOOK_CELL_TYPE_CONTEXT_KEY, 'markdown'), + ContextKeyExpr.equals(NOTEBOOK_CELL_MARKDOWN_EDIT_MODE_CONTEXT_KEY, true), + ContextKeyExpr.equals(NOTEBOOK_CELL_EDITABLE_CONTEXT_KEY, true)), + order: CellToolbarOrder.SaveCell + }, + icon: { id: 'codicon/save' } + }); + } + + run(accessor: ServicesAccessor, context?: INotebookCellActionContext) { + if (!context) { + context = getActiveCellContext(accessor); + if (!context) { + return; + } + } + + return context.notebookEditor.saveNotebookCell(context.cell); + } +}); + + +registerAction2(class extends Action2 { + constructor() { + super( + { + id: DELETE_CELL_COMMAND_ID, + title: localize('notebookActions.deleteCell', "Delete Cell"), + category: NOTEBOOK_ACTIONS_CATEGORY, + menu: { + id: MenuId.NotebookCellTitle, + order: CellToolbarOrder.DeleteCell, + when: ContextKeyExpr.equals(NOTEBOOK_EDITABLE_CONTEXT_KEY, true) + }, + icon: { id: 'codicon/trash' }, + f1: true + }); + } + + run(accessor: ServicesAccessor, context?: INotebookCellActionContext) { + if (!context) { + context = getActiveCellContext(accessor); + if (!context) { + return; + } + } + + return context.notebookEditor.deleteNotebookCell(context.cell); + } +}); + +async function moveCell(context: INotebookCellActionContext, direction: 'up' | 'down'): Promise { + direction === 'up' ? + context.notebookEditor.moveCellUp(context.cell) : + context.notebookEditor.moveCellDown(context.cell); +} + +async function copyCell(context: INotebookCellActionContext, direction: 'up' | 'down'): Promise { + const text = context.cell.getText(); + const newCellDirection = direction === 'up' ? 'above' : 'below'; + await context.notebookEditor.insertNotebookCell(context.cell, context.cell.cellKind, newCellDirection, text); +} + +registerAction2(class extends Action2 { + constructor() { + super( + { + id: MOVE_CELL_UP_COMMAND_ID, + title: localize('notebookActions.moveCellUp', "Move Cell Up"), + category: NOTEBOOK_ACTIONS_CATEGORY, + icon: { id: 'codicon/arrow-up' }, + menu: { + id: MenuId.NotebookCellTitle, + order: CellToolbarOrder.MoveCellUp, + alt: { + id: COPY_CELL_UP_COMMAND_ID, + title: localize('notebookActions.copyCellUp', "Copy Cell Up"), + icon: { id: 'codicon/arrow-up' } + }, + when: ContextKeyExpr.equals(NOTEBOOK_EDITABLE_CONTEXT_KEY, true) + }, + f1: true + }); + } + + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext) { + if (!context) { + context = getActiveCellContext(accessor); + if (!context) { + return; + } + } + + return moveCell(context, 'up'); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super( + { + id: MOVE_CELL_DOWN_COMMAND_ID, + title: localize('notebookActions.moveCellDown', "Move Cell Down"), + category: NOTEBOOK_ACTIONS_CATEGORY, + icon: { id: 'codicon/arrow-down' }, + menu: { + id: MenuId.NotebookCellTitle, + order: CellToolbarOrder.MoveCellDown, + alt: { + id: COPY_CELL_DOWN_COMMAND_ID, + title: localize('notebookActions.copyCellDown', "Copy Cell Down"), + icon: { id: 'codicon/arrow-down' } + }, + when: ContextKeyExpr.equals(NOTEBOOK_EDITABLE_CONTEXT_KEY, true) + }, + f1: true + }); + } + + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext) { + if (!context) { + context = getActiveCellContext(accessor); + if (!context) { + return; + } + } + + return moveCell(context, 'down'); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super( + { + id: COPY_CELL_UP_COMMAND_ID, + title: localize('notebookActions.copyCellUp', "Copy Cell Up") + }); + } + + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext) { + if (!context) { + context = getActiveCellContext(accessor); + if (!context) { + return; + } + } + + return copyCell(context, 'up'); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super( + { + id: COPY_CELL_DOWN_COMMAND_ID, + title: localize('notebookActions.copyCellDown', "Copy Cell Down") + }); + } + + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext) { + if (!context) { + context = getActiveCellContext(accessor); + if (!context) { + return; + } + } + + return copyCell(context, 'down'); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.notebook.cursorDown', + title: 'Notebook Cursor Move Down', + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.has(InputFocusedContextKey), EditorContextKeys.editorTextFocus, NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('top'), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none')), + primary: KeyCode.DownArrow, + weight: KeybindingWeight.EditorContrib // smaller than Suggest Widget, etc + } + }); + } + + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext): Promise { + if (!context) { + context = getActiveCellContext(accessor); + if (!context) { + return; + } + } + + const editor = context.notebookEditor; + const activeCell = context.cell; + + const idx = editor.viewModel?.getViewCellIndex(activeCell); + if (typeof idx !== 'number') { + return; + } + + const newCell = editor.viewModel?.viewCells[idx + 1]; + + if (!newCell) { + return; + } + + editor.focusNotebookCell(newCell, true); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.notebook.cursorUp', + title: 'Notebook Cursor Move Up', + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.has(InputFocusedContextKey), EditorContextKeys.editorTextFocus, NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('bottom'), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none')), + primary: KeyCode.UpArrow, + weight: KeybindingWeight.EditorContrib // smaller than Suggest Widget, etc + }, + }); + } + + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext): Promise { + if (!context) { + context = getActiveCellContext(accessor); + if (!context) { + return; + } + } + + const editor = context.notebookEditor; + const activeCell = context.cell; + + const idx = editor.viewModel?.getViewCellIndex(activeCell); + if (typeof idx !== 'number') { + return; + } + + if (idx < 1) { + // we don't do loop + return; + } + + const newCell = editor.viewModel?.viewCells[idx - 1]; + + if (!newCell) { + return; + } + + editor.focusNotebookCell(newCell, true); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.notebook.undo', + title: 'Notebook Undo', + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), + primary: KeyMod.CtrlCmd | KeyCode.KEY_Z, + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + + async run(accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + + const editor = getActiveNotebookEditor(editorService); + if (!editor) { + return; + } + + const viewModel = editor.viewModel; + + if (!viewModel) { + return; + } + + viewModel.undo(); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.notebook.redo', + title: 'Notebook Redo', + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z, + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + + async run(accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + + const editor = getActiveNotebookEditor(editorService); + if (!editor) { + return; + } + + const viewModel = editor.viewModel; + + if (!viewModel) { + return; + } + + viewModel.redo(); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.notebook.testResize', + title: 'Notebook Test Cell Resize', + category: NOTEBOOK_ACTIONS_CATEGORY, + keybinding: { + when: IsDevelopmentContext, + primary: undefined, + weight: KeybindingWeight.WorkbenchContrib + }, + f1: true + }); + } + + async run(accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + const resource = editorService.activeEditor?.resource; + if (!resource) { + return; + } + + const editor = getActiveNotebookEditor(editorService); + if (!editor) { + return; + } + + const cells = editor.viewModel?.viewCells; + + if (cells && cells.length) { + const firstCell = cells[0]; + editor.layoutNotebookCell(firstCell, 400); + } + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookFindWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookFindWidget.ts new file mode 100644 index 00000000000..8a78e23134b --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookFindWidget.ts @@ -0,0 +1,238 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED, INotebookEditor, CellFindMatch, CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { FindDecorations } from 'vs/editor/contrib/find/findDecorations'; +import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; +import { IModelDeltaDecoration } from 'vs/editor/common/model'; +import { ICellModelDeltaDecorations, ICellModelDecorations } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; +import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; +import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { SimpleFindReplaceWidget } from 'vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; + +export class NotebookFindWidget extends SimpleFindReplaceWidget { + protected _findWidgetFocused: IContextKey; + private _findMatches: CellFindMatch[] = []; + protected _findMatchesStarts: PrefixSumComputer | null = null; + private _currentMatch: number = -1; + private _allMatchesDecorations: ICellModelDecorations[] = []; + private _currentMatchDecorations: ICellModelDecorations[] = []; + + constructor( + private readonly _notebookEditor: INotebookEditor, + @IContextViewService contextViewService: IContextViewService, + @IContextKeyService contextKeyService: IContextKeyService, + @IThemeService themeService: IThemeService, + + ) { + super(contextViewService, contextKeyService, themeService); + this._findWidgetFocused = KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED.bindTo(contextKeyService); + this._register(this._findInput.onKeyDown((e) => this._onFindInputKeyDown(e))); + } + + private _onFindInputKeyDown(e: IKeyboardEvent): void { + if (e.equals(KeyCode.Enter)) { + if (this._findMatches.length) { + this.find(false); + } else { + this.set(null); + } + e.preventDefault(); + return; + } else if (e.equals(KeyMod.Shift | KeyCode.Enter)) { + if (this._findMatches.length) { + this.find(true); + } else { + this.set(null); + } + e.preventDefault(); + return; + } + } + + protected onInputChanged(): boolean { + const val = this.inputValue; + if (val) { + this._findMatches = this._notebookEditor.viewModel!.find(val).filter(match => match.matches.length > 0); + if (this._findMatches.length) { + return true; + } else { + return false; + } + } + + return false; + } + + protected find(previous: boolean): void { + if (!this._findMatches.length) { + return; + } + + if (!this._findMatchesStarts) { + this.set(this._findMatches); + } else { + const totalVal = this._findMatchesStarts!.getTotalValue(); + const nextVal = (this._currentMatch + (previous ? -1 : 1) + totalVal) % totalVal; + this._currentMatch = nextVal; + } + + + const nextIndex = this._findMatchesStarts!.getIndexOf(this._currentMatch); + this.setCurrentFindMatchDecoration(nextIndex.index, nextIndex.remainder); + this.revealCellRange(nextIndex.index, nextIndex.remainder); + } + + protected replaceOne() { + if (!this._findMatches.length) { + return; + } + + if (!this._findMatchesStarts) { + this.set(this._findMatches); + } + + const nextIndex = this._findMatchesStarts!.getIndexOf(this._currentMatch); + const cell = this._findMatches[nextIndex.index].cell; + const match = this._findMatches[nextIndex.index].matches[nextIndex.remainder]; + + this._progressBar.infinite().show(); + + this._notebookEditor.viewModel!.replaceOne(cell, match.range, this.replaceValue).then(() => { + this._progressBar.stop(); + }); + } + + protected replaceAll() { + this._progressBar.infinite().show(); + + this._notebookEditor.viewModel!.replaceAll(this._findMatches, this.replaceValue).then(() => { + this._progressBar.stop(); + }); + } + + private revealCellRange(cellIndex: number, matchIndex: number) { + this._findMatches[cellIndex].cell.editState = CellEditState.Editing; + this._notebookEditor.selectElement(this._findMatches[cellIndex].cell); + this._notebookEditor.setCellSelection(this._findMatches[cellIndex].cell, this._findMatches[cellIndex].matches[matchIndex].range); + this._notebookEditor.revealRangeInCenterIfOutsideViewport(this._findMatches[cellIndex].cell, this._findMatches[cellIndex].matches[matchIndex].range); + } + + hide() { + super.hide(); + this.set([]); + } + + protected findFirst(): void { } + + protected onFocusTrackerFocus() { + this._findWidgetFocused.set(true); + } + + protected onFocusTrackerBlur() { + this._findWidgetFocused.reset(); + } + + protected onReplaceInputFocusTrackerFocus(): void { + // throw new Error('Method not implemented.'); + } + protected onReplaceInputFocusTrackerBlur(): void { + // throw new Error('Method not implemented.'); + } + + protected onFindInputFocusTrackerFocus(): void { } + protected onFindInputFocusTrackerBlur(): void { } + + private constructFindMatchesStarts() { + if (this._findMatches && this._findMatches.length) { + const values = new Uint32Array(this._findMatches.length); + for (let i = 0; i < this._findMatches.length; i++) { + values[i] = this._findMatches[i].matches.length; + } + + this._findMatchesStarts = new PrefixSumComputer(values); + } else { + this._findMatchesStarts = null; + } + } + + private set(cellFindMatches: CellFindMatch[] | null): void { + if (!cellFindMatches || !cellFindMatches.length) { + this._findMatches = []; + this.setAllFindMatchesDecorations([]); + + this.constructFindMatchesStarts(); + this._currentMatch = -1; + this.clearCurrentFindMatchDecoration(); + return; + } + + // all matches + this._findMatches = cellFindMatches; + this.setAllFindMatchesDecorations(cellFindMatches || []); + + // current match + this.constructFindMatchesStarts(); + this._currentMatch = 0; + this.setCurrentFindMatchDecoration(0, 0); + } + + private setCurrentFindMatchDecoration(cellIndex: number, matchIndex: number) { + this._notebookEditor.changeDecorations(accessor => { + const findMatchesOptions: ModelDecorationOptions = FindDecorations._CURRENT_FIND_MATCH_DECORATION; + + const cell = this._findMatches[cellIndex].cell; + const match = this._findMatches[cellIndex].matches[matchIndex]; + const decorations: IModelDeltaDecoration[] = [ + { range: match.range, options: findMatchesOptions } + ]; + const deltaDecoration: ICellModelDeltaDecorations = { + ownerId: cell.handle, + decorations: decorations + }; + + this._currentMatchDecorations = accessor.deltaDecorations(this._currentMatchDecorations, [deltaDecoration]); + }); + } + + private clearCurrentFindMatchDecoration() { + this._notebookEditor.changeDecorations(accessor => { + this._currentMatchDecorations = accessor.deltaDecorations(this._currentMatchDecorations, []); + }); + } + + private setAllFindMatchesDecorations(cellFindMatches: CellFindMatch[]) { + this._notebookEditor.changeDecorations((accessor) => { + + let findMatchesOptions: ModelDecorationOptions = FindDecorations._FIND_MATCH_DECORATION; + + let deltaDecorations: ICellModelDeltaDecorations[] = cellFindMatches.map(cellFindMatch => { + const findMatches = cellFindMatch.matches; + + // Find matches + let newFindMatchesDecorations: IModelDeltaDecoration[] = new Array(findMatches.length); + for (let i = 0, len = findMatches.length; i < len; i++) { + newFindMatchesDecorations[i] = { + range: findMatches[i].range, + options: findMatchesOptions + }; + } + + return { ownerId: cellFindMatch.cell.handle, decorations: newFindMatchesDecorations }; + }); + + this._allMatchesDecorations = accessor.deltaDecorations(this._allMatchesDecorations, deltaDecorations); + }); + } + + clear() { + this._currentMatch = -1; + this._findMatches = []; + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts b/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts new file mode 100644 index 00000000000..7dbcd235058 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts @@ -0,0 +1,119 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import * as nls from 'vs/nls'; +import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { NotebookSelector } from 'vs/workbench/contrib/notebook/common/notebookProvider'; + +namespace NotebookEditorContribution { + export const viewType = 'viewType'; + export const displayName = 'displayName'; + export const selector = 'selector'; +} + +interface INotebookEditorContribution { + readonly [NotebookEditorContribution.viewType]: string; + readonly [NotebookEditorContribution.displayName]: string; + readonly [NotebookEditorContribution.selector]?: readonly NotebookSelector[]; +} + +namespace NotebookRendererContribution { + export const viewType = 'viewType'; + export const displayName = 'displayName'; + export const mimeTypes = 'mimeTypes'; +} + +interface INotebookRendererContribution { + readonly [NotebookRendererContribution.viewType]: string; + readonly [NotebookRendererContribution.displayName]: string; + readonly [NotebookRendererContribution.mimeTypes]?: readonly string[]; +} + + + +const notebookProviderContribution: IJSONSchema = { + description: nls.localize('contributes.notebook.provider', 'Contributes notebook document provider.'), + type: 'array', + defaultSnippets: [{ body: [{ viewType: '', displayName: '' }] }], + items: { + type: 'object', + required: [ + NotebookEditorContribution.viewType, + NotebookEditorContribution.displayName, + NotebookEditorContribution.selector, + ], + properties: { + [NotebookEditorContribution.viewType]: { + type: 'string', + description: nls.localize('contributes.notebook.provider.viewType', 'Unique identifier of the notebook.'), + }, + [NotebookEditorContribution.displayName]: { + type: 'string', + description: nls.localize('contributes.notebook.provider.displayName', 'Human readable name of the notebook.'), + }, + [NotebookEditorContribution.selector]: { + type: 'array', + description: nls.localize('contributes.notebook.provider.selector', 'Set of globs that the notebook is for.'), + items: { + type: 'object', + properties: { + filenamePattern: { + type: 'string', + description: nls.localize('contributes.notebook.provider.selector.filenamePattern', 'Glob that the notebook is enabled for.'), + }, + excludeFileNamePattern: { + type: 'string', + description: nls.localize('contributes.notebook.selector.provider.excludeFileNamePattern', 'Glob that the notebook is disabled for.') + } + } + } + } + } + } +}; + +const notebookRendererContribution: IJSONSchema = { + description: nls.localize('contributes.notebook.renderer', 'Contributes notebook output renderer provider.'), + type: 'array', + defaultSnippets: [{ body: [{ viewType: '', displayName: '', mimeTypes: [''] }] }], + items: { + type: 'object', + required: [ + NotebookRendererContribution.viewType, + NotebookRendererContribution.displayName, + NotebookRendererContribution.mimeTypes, + ], + properties: { + [NotebookRendererContribution.viewType]: { + type: 'string', + description: nls.localize('contributes.notebook.renderer.viewType', 'Unique identifier of the notebook output renderer.'), + }, + [NotebookRendererContribution.displayName]: { + type: 'string', + description: nls.localize('contributes.notebook.renderer.displayName', 'Human readable name of the notebook output renderer.'), + }, + [NotebookRendererContribution.mimeTypes]: { + type: 'array', + description: nls.localize('contributes.notebook.selector', 'Set of globs that the notebook is for.'), + items: { + type: 'string' + } + } + } + } +}; + +export const notebookProviderExtensionPoint = ExtensionsRegistry.registerExtensionPoint( + { + extensionPoint: 'notebookProvider', + jsonSchema: notebookProviderContribution + }); + +export const notebookRendererExtensionPoint = ExtensionsRegistry.registerExtensionPoint( + { + extensionPoint: 'notebookOutputRenderer', + jsonSchema: notebookRendererContribution + }); diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts new file mode 100644 index 00000000000..3b21068d101 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -0,0 +1,216 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { IEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, IEditorInputFactory, EditorInput } from 'vs/workbench/common/editor'; +import { NotebookEditor, NotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookEditor'; +import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; +import { INotebookService, NotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService'; +import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; +import { ITextModel } from 'vs/editor/common/model'; +import { URI } from 'vs/base/common/uri'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { assertType } from 'vs/base/common/types'; +import { parse } from 'vs/base/common/marshalling'; +import { CellUri, CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ResourceMap } from 'vs/base/common/map'; + +// Output renderers registration + +import 'vs/workbench/contrib/notebook/browser/view/output/transforms/streamTransform'; +import 'vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform'; +import 'vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform'; + +// Actions +import 'vs/workbench/contrib/notebook/browser/contrib/notebookActions'; +import { basename } from 'vs/base/common/resources'; +import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; + +Registry.as(EditorExtensions.Editors).registerEditor( + EditorDescriptor.create( + NotebookEditor, + NotebookEditor.ID, + 'Notebook Editor' + ), + [ + new SyncDescriptor(NotebookEditorInput) + ] +); + +Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory( + NotebookEditorInput.ID, + class implements IEditorInputFactory { + canSerialize(): boolean { + return true; + } + serialize(input: EditorInput): string { + assertType(input instanceof NotebookEditorInput); + return JSON.stringify({ + resource: input.resource, + name: input.name, + viewType: input.viewType, + }); + } + deserialize(instantiationService: IInstantiationService, raw: string) { + type Data = { resource: URI, name: string, viewType: string }; + const data = parse(raw); + if (!data) { + return undefined; + } + const { resource, name, viewType } = data; + if (!data || !URI.isUri(resource) || typeof name !== 'string' || typeof viewType !== 'string') { + return undefined; + } + return instantiationService.createInstance(NotebookEditorInput, resource, name, viewType); + } + } +); + +function getFirstNotebookInfo(notebookService: INotebookService, uri: URI): NotebookProviderInfo | undefined { + return notebookService.getContributedNotebookProviders(uri)[0]; +} + +export class NotebookContribution implements IWorkbenchContribution { + private _resourceMapping = new ResourceMap(); + + constructor( + @IEditorService private readonly editorService: IEditorService, + @INotebookService private readonly notebookService: INotebookService, + @IInstantiationService private readonly instantiationService: IInstantiationService + + ) { + this.editorService.overrideOpenEditor((editor, options, group) => this.onEditorOpening(editor, options, group)); + + this.editorService.onDidActiveEditorChange(() => { + if (this.editorService.activeEditor && this.editorService.activeEditor! instanceof NotebookEditorInput) { + let editorInput = this.editorService.activeEditor! as NotebookEditorInput; + this.notebookService.updateActiveNotebookDocument(editorInput.viewType!, editorInput.resource!); + } + }); + } + + private onEditorOpening(originalInput: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup): IOpenEditorOverride | undefined { + let resource = originalInput.resource; + if (!resource) { + return undefined; + } + + let info: NotebookProviderInfo | undefined; + const data = CellUri.parse(resource); + if (data && (info = getFirstNotebookInfo(this.notebookService, data.notebook))) { + // cell-uri -> open (container) notebook + const name = basename(data.notebook); + const input = this.instantiationService.createInstance(NotebookEditorInput, data.notebook, name, info.id); + this._resourceMapping.set(resource, input); + return { override: this.editorService.openEditor(input, new NotebookEditorOptions({ ...options, forceReload: true, cellOptions: { resource, options } }), group) }; + } + + info = getFirstNotebookInfo(this.notebookService, resource); + if (!info) { + return undefined; + } + + if (this._resourceMapping.has(resource)) { + const input = this._resourceMapping.get(resource); + + if (!input!.isDisposed()) { + return { override: this.editorService.openEditor(input!, new NotebookEditorOptions(options || {}).with({ ignoreOverrides: true }), group) }; + } + } + + const input = this.instantiationService.createInstance(NotebookEditorInput, resource, originalInput.getName(), info.id); + this._resourceMapping.set(resource, input); + + return { override: this.editorService.openEditor(input, options, group) }; + } +} + +class CellContentProvider implements ITextModelContentProvider { + + private readonly _registration: IDisposable; + + constructor( + @ITextModelService textModelService: ITextModelService, + @IModelService private readonly _modelService: IModelService, + @IModeService private readonly _modeService: IModeService, + @INotebookService private readonly _notebookService: INotebookService, + ) { + this._registration = textModelService.registerTextModelContentProvider('vscode-notebook', this); + } + + dispose(): void { + this._registration.dispose(); + } + + async provideTextContent(resource: URI): Promise { + const existing = this._modelService.getModel(resource); + if (existing) { + return existing; + } + const data = CellUri.parse(resource); + // const data = parseCellUri(resource); + if (!data) { + return null; + } + const info = getFirstNotebookInfo(this._notebookService, data.notebook); + if (!info) { + return null; + } + const notebook = await this._notebookService.resolveNotebook(info.id, data.notebook); + if (!notebook) { + return null; + } + for (let cell of notebook.cells) { + if (cell.uri.toString() === resource.toString()) { + const bufferFactory = cell.resolveTextBufferFactory(); + const language = cell.cellKind === CellKind.Markdown ? this._modeService.create('markdown') : (cell.language ? this._modeService.create(cell.language) : this._modeService.createByFilepathOrFirstLine(resource, cell.source[0])); + return this._modelService.createModel( + bufferFactory, + language, + resource + ); + } + } + + return null; + } +} + +const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchContributionsRegistry.registerWorkbenchContribution(NotebookContribution, LifecyclePhase.Starting); +workbenchContributionsRegistry.registerWorkbenchContribution(CellContentProvider, LifecyclePhase.Starting); + +registerSingleton(INotebookService, NotebookService); + +const configurationRegistry = Registry.as(Extensions.Configuration); +configurationRegistry.registerConfiguration({ + id: 'notebook', + order: 100, + title: nls.localize('notebookConfigurationTitle', "Notebook"), + type: 'object', + properties: { + 'notebook.displayOrder': { + markdownDescription: nls.localize('notebook.displayOrder.description', "Priority list for output mime types"), + type: ['array'], + items: { + type: 'string' + }, + default: [] + } + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.css b/src/vs/workbench/contrib/notebook/browser/notebook.css new file mode 100644 index 00000000000..2a6fdeadf1f --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/notebook.css @@ -0,0 +1,409 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-workbench .part.editor > .content .notebook-editor { + box-sizing: border-box; + line-height: 22px; + user-select: initial; + -webkit-user-select: initial; + position: relative; +} + +.cell.markdown { + user-select: text; + -webkit-user-select: text; + white-space: initial; +} + +/* .monaco-workbench .part.editor > .content .notebook-editor .cell-list-container .monaco-scrollable-element { + overflow: visible !important; +} */ + +.monaco-workbench .part.editor > .content .notebook-editor .cell-list-container .monaco-list-rows { + min-height: 100%; + overflow: visible !important; +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell-list-container { + position: relative; +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell-list-container .webview-cover { + position: absolute; + top: 0; +} + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell { + display: flex; +} + +.monaco-workbench .part.editor > .content .notebook-editor .notebook-content-widgets { + position: absolute; + top: 0; + left: 0; + width: 100%; +} + +.monaco-workbench .part.editor > .content .notebook-editor .output { + padding-left: 8px; + padding-right: 8px; + user-select: text; + transform: translate3d(0px, 0px, 0px); + cursor: auto; + box-sizing: border-box; +} + +.monaco-workbench .part.editor > .content .notebook-editor .output p { + white-space: initial; + overflow-x: auto; + margin: 0px; +} + +.monaco-workbench .part.editor > .content .notebook-editor .output > div.foreground { + padding: 8px; + box-sizing: border-box; +} + +.monaco-workbench .part.editor > .content .notebook-editor .output .multi-mimetype-output { + position: absolute; + top: 4px; + left: -32px; + width: 16px; + height: 16px; + cursor: pointer; +} + +.monaco-workbench .part.editor > .content .notebook-editor .output .error_message { + color: red; +} + +.monaco-workbench .part.editor > .content .notebook-editor .output pre.traceback { + margin: 8px 0; +} + +.monaco-workbench .part.editor > .content .notebook-editor .output .traceback > span { + display: block; +} + +.monaco-workbench .part.editor > .content .notebook-editor .output .display img { + max-width: 100%; +} + + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row { + overflow: visible !important; +} + + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:focus-within { + z-index: 10; +} + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .menu { + position: absolute; + left: 0; + top: 28px; + visibility: hidden; + width: 16px; + margin: auto; + padding-left: 4px; +} + + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .menu.mouseover, +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover .menu { + visibility: visible; +} + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover { + outline: none !important; +} + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected, +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused { + outline: none !important; +} + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .menu.mouseover, +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .menu:hover { + cursor: pointer; +} + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > .monaco-toolbar { + visibility: hidden; + margin-right: 24px; +} + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell .run-button-container { + position: relative; +} + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell .run-button-container .monaco-toolbar { + margin-top: 5px; + visibility: hidden; +} + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell .run-button-container .monaco-toolbar .codicon { + margin-right: 8px; +} + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover .cell.runnable .run-button-container .monaco-toolbar, +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused .cell.runnable .run-button-container .monaco-toolbar { + visibility: visible; +} + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell .run-button-container .execution-count-label { + position: absolute; + top: 2px; + font-size: 12px; + visibility: visible; + white-space: pre; + width: 100%; + text-align: center; + padding-right: 2px; + box-sizing: border-box; +} + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover .cell .run-button-container .execution-count-label, +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused .cell .run-button-container .execution-count-label { + visibility: hidden; +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell .cell-editor-container { + position: relative; +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell .monaco-progress-container { + top: 0px; +} + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused .monaco-toolbar, +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover .monaco-toolbar { + visibility: visible; +} + + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list:not(.element-focused):focus:before { + outline: none !important; +} + + +.monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row .notebook-cell-focus-indicator { + display: block; + content: ' '; + position: absolute; + width: 6px; + border-left-width: 2px; + border-left-style: solid; + left: 20px; + top: 22px; + bottom: 8px; + visibility: hidden; +} + +.monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row.focused .notebook-cell-focus-indicator { + visibility: visible; +} + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container { + position: absolute; + display: flex; + opacity: 0; + transition: opacity 0.2s ease-in-out; + cursor: auto; +} + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container:hover { + opacity: 1; +} + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container .seperator { + height: 1px; + flex-grow: 1; + align-self: center; +} + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container .seperator-short { + height: 1px; + width: 16px; + align-self: center; +} + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container .button { + display: flex; + margin: 0 8px; + align-self: center; + align-items: center; + white-space: pre; + cursor: pointer; +} + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container span.codicon { + text-align: center; + font-size: 14px; + color: inherit; +} + +.notebook-webview { + position: absolute; + z-index: 1000000; + left: 373px; + top: 0px; +} + +/* markdown */ + + +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown img { + max-width: 100%; + max-height: 100%; +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown a { + text-decoration: none; +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown a:hover { + text-decoration: underline; +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown a:focus, +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown input:focus, +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown select:focus, +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown textarea:focus { + outline: 1px solid -webkit-focus-ring-color; + outline-offset: -1px; +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown hr { + border: 0; + height: 2px; + border-bottom: 2px solid; +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown h1 { + padding-bottom: 0.3em; + line-height: 1.2; + border-bottom-width: 1px; + border-bottom-style: solid; +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown h1, +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown h2, +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown h3 { + font-weight: normal; +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table { + border-collapse: collapse; + border-spacing: 0; +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table th, +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table td { + border: 1px solid ; +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table > thead > tr > th { + text-align: left; + border-bottom: 1px solid; +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table > thead > tr > th, +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table > thead > tr > td, +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table > tbody > tr > th, +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table > tbody > tr > td { + padding: 5px 10px; +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table > tbody > tr + tr > td { + border-top: 1px solid; +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown blockquote { + margin: 0 7px 0 5px; + padding: 0 16px 0 10px; + border-left-width: 5px; + border-left-style: solid; +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown code { + font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; + font-size: 1em; + line-height: 1.357em; +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown body.wordWrap pre { + white-space: pre-wrap; +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown pre:not(.hljs), +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown pre.hljs code > div { + padding: 16px; + border-radius: 3px; + overflow: auto; +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown pre code { + color: var(--vscode-editor-foreground); + tab-size: 4; +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown .latex-block { + display: block; +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown .latex { + vertical-align: middle; + display: inline-block; +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown .latex img, +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown .latex-block img { + filter: brightness(0) invert(0) +} + +.vs-dark .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown .latex img, +.vs-dark .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown .latex-block img { + filter: brightness(0) invert(1) +} + +/** Theming */ + +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown pre { + background-color: rgba(220, 220, 220, 0.4); +} + +.vs-dark .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown pre { + background-color: rgba(10, 10, 10, 0.4); +} + +.hc-black .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown pre { + background-color: rgb(0, 0, 0); +} + +.hc-black .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown h1 { + border-color: rgb(0, 0, 0); +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table > thead > tr > th { + border-color: rgba(0, 0, 0, 0.18); +} + +.vs-dark .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table > thead > tr > th { + border-color: rgba(255, 255, 255, 0.18); +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown h1, +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown hr, +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table > tbody > tr > td { + border-color: rgba(0, 0, 0, 0.18); +} + +.vs-dark .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown h1, +.vs-dark .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown hr, +.vs-dark .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table > tbody > tr > td { + border-color: rgba(255, 255, 255, 0.18); +} diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts new file mode 100644 index 00000000000..ec58d5b2f12 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -0,0 +1,349 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; +import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; +import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; +import { Range } from 'vs/editor/common/core/range'; +import { FindMatch } from 'vs/editor/common/model'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { NOTEBOOK_EDITABLE_CONTEXT_KEY, NOTEBOOK_EXECUTING_KEY } from 'vs/workbench/contrib/notebook/browser/constants'; +import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; +import { CellViewModel, IModelDecorationsChangeAccessor, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; +import { CellKind, IOutput, IRenderOutput, NotebookCellMetadata, NotebookDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; + +export const KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED = new RawContextKey('notebookFindWidgetFocused', false); + +export const NOTEBOOK_EDITOR_FOCUSED = new RawContextKey('notebookEditorFocused', false); +export const NOTEBOOK_EDITOR_EDITABLE = new RawContextKey(NOTEBOOK_EDITABLE_CONTEXT_KEY, true); +export const NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK = new RawContextKey(NOTEBOOK_EXECUTING_KEY, false); + +export interface NotebookLayoutInfo { + width: number; + height: number; + fontInfo: BareFontInfo; +} + +export interface NotebookLayoutChangeEvent { + width?: boolean; + height?: boolean; + fontInfo?: boolean; +} + +export interface CodeCellLayoutInfo { + readonly fontInfo: BareFontInfo | null; + readonly editorHeight: number; + readonly editorWidth: number; + readonly totalHeight: number; + readonly outputContainerOffset: number; + readonly outputTotalHeight: number; + readonly indicatorHeight: number; + readonly bottomToolbarOffset: number; +} + +export interface CodeCellLayoutChangeEvent { + editorHeight?: boolean; + outputHeight?: boolean; + totalHeight?: boolean; + outerWidth?: number; + font?: BareFontInfo; +} + +export interface MarkdownCellLayoutInfo { + readonly fontInfo: BareFontInfo | null; + readonly editorWidth: number; + readonly bottomToolbarOffset: number; + readonly totalHeight: number; +} + +export interface MarkdownCellLayoutChangeEvent { + font?: BareFontInfo; + outerWidth?: number; + totalHeight?: number; +} + +export interface ICellViewModel { + readonly id: string; + handle: number; + uri: URI; + cellKind: CellKind; + editState: CellEditState; + readonly runState: CellRunState; + currentTokenSource: CancellationTokenSource | undefined; + focusMode: CellFocusMode; + getText(): string; + metadata: NotebookCellMetadata | undefined; + getEvaluatedMetadata(documentMetadata: NotebookDocumentMetadata | undefined): NotebookCellMetadata; +} + +export interface INotebookEditor { + + /** + * Notebook view model attached to the current editor + */ + viewModel: NotebookViewModel | undefined; + + isNotebookEditor: boolean; + + getInnerWebview(): Webview | undefined; + + /** + * Focus the notebook editor cell list + */ + focus(): void; + + /** + * Select & focus cell + */ + selectElement(cell: ICellViewModel): void; + + /** + * Layout info for the notebook editor + */ + getLayoutInfo(): NotebookLayoutInfo; + /** + * Fetch the output renderers for notebook outputs. + */ + getOutputRenderer(): OutputRenderer; + + /** + * Insert a new cell around `cell` + */ + insertNotebookCell(cell: ICellViewModel, type: CellKind, direction: 'above' | 'below', initialText?: string): Promise; + + /** + * Delete a cell from the notebook + */ + deleteNotebookCell(cell: ICellViewModel): void; + + /** + * Move a cell up one spot + */ + moveCellUp(cell: ICellViewModel): void; + + /** + * Move a cell down one spot + */ + moveCellDown(cell: ICellViewModel): void; + + /** + * Switch the cell into editing mode. + * + * For code cell, the monaco editor will be focused. + * For markdown cell, it will switch from preview mode to editing mode, which focuses the monaco editor. + */ + editNotebookCell(cell: ICellViewModel): void; + + /** + * Quit cell editing mode. + */ + saveNotebookCell(cell: ICellViewModel): void; + + /** + * Focus the container of a cell (the monaco editor inside is not focused). + */ + focusNotebookCell(cell: ICellViewModel, focusEditor: boolean): void; + + /** + * Execute the given notebook cell + */ + executeNotebookCell(cell: ICellViewModel): Promise; + + /** + * Cancel the cell execution + */ + cancelNotebookCellExecution(cell: ICellViewModel): void; + + /** + * Executes all notebook cells in order + */ + executeNotebook(): Promise; + + /** + * Cancel the notebook execution + */ + cancelNotebookExecution(): void; + + /** + * Get current active cell + */ + getActiveCell(): ICellViewModel | undefined; + + /** + * Layout the cell with a new height + */ + layoutNotebookCell(cell: ICellViewModel, height: number): Promise; + + /** + * Render the output in webview layer + */ + createInset(cell: ICellViewModel, output: IOutput, shadowContent: string, offset: number): void; + + /** + * Remove the output from the webview layer + */ + removeInset(output: IOutput): void; + + /** + * Send message to the webview for outputs. + */ + postMessage(message: any): void; + + /** + * Trigger the editor to scroll from scroll event programmatically + */ + triggerScroll(event: IMouseWheelEvent): void; + + /** + * Reveal cell into viewport. + */ + revealInView(cell: ICellViewModel): void; + + /** + * Reveal cell into viewport center. + */ + revealInCenter(cell: ICellViewModel): void; + + /** + * Reveal cell into viewport center if cell is currently out of the viewport. + */ + revealInCenterIfOutsideViewport(cell: ICellViewModel): void; + + /** + * Reveal a line in notebook cell into viewport with minimal scrolling. + */ + revealLineInView(cell: ICellViewModel, line: number): void; + + /** + * Reveal a line in notebook cell into viewport center. + */ + revealLineInCenter(cell: ICellViewModel, line: number): void; + + /** + * Reveal a line in notebook cell into viewport center. + */ + revealLineInCenterIfOutsideViewport(cell: ICellViewModel, line: number): void; + + /** + * Reveal a range in notebook cell into viewport with minimal scrolling. + */ + revealRangeInView(cell: ICellViewModel, range: Range): void; + + /** + * Reveal a range in notebook cell into viewport center. + */ + revealRangeInCenter(cell: ICellViewModel, range: Range): void; + + /** + * Reveal a range in notebook cell into viewport center. + */ + revealRangeInCenterIfOutsideViewport(cell: ICellViewModel, range: Range): void; + + setCellSelection(cell: ICellViewModel, selection: Range): void; + + /** + * Change the decorations on cells. + * The notebook is virtualized and this method should be called to create/delete editor decorations safely. + */ + changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => any): any; + + /** + * Show Find Widget. + * + * Currently Find is still part of the NotebookEditor core + */ + showFind(): void; + + /** + * Hide Find Widget + */ + hideFind(): void; +} + +export interface BaseCellRenderTemplate { + container: HTMLElement; + cellContainer: HTMLElement; + toolbar: ToolBar; + focusIndicator: HTMLElement; + disposables: DisposableStore; + bottomCellContainer: HTMLElement; + toJSON: () => any; +} + +export interface MarkdownCellRenderTemplate extends BaseCellRenderTemplate { + editingContainer: HTMLElement; +} + +export interface CodeCellRenderTemplate extends BaseCellRenderTemplate { + editorContainer: HTMLElement; + runToolbar: ToolBar; + runButtonContainer: HTMLElement; + executionOrderLabel: HTMLElement; + outputContainer: HTMLElement; + editor: CodeEditorWidget; + progressBar: ProgressBar; +} + +export interface IOutputTransformContribution { + /** + * Dispose this contribution. + */ + dispose(): void; + + render(output: IOutput, container: HTMLElement, preferredMimeType: string | undefined): IRenderOutput; +} + +export interface CellFindMatch { + cell: CellViewModel; + matches: FindMatch[]; +} + +export enum CellRevealType { + Line, + Range +} + +export enum CellRevealPosition { + Top, + Center +} + +export enum CellRunState { + Idle, + Running +} + +export enum CellEditState { + /** + * Default state. + * For markdown cell, it's Markdown preview. + * For code cell, the browser focus should be on the container instead of the editor + */ + Preview, + + + /** + * Eding mode. Source for markdown or code is rendered in editors and the state will be persistent. + */ + Editing +} + +export enum CellFocusMode { + Container, + Editor +} + +export enum CursorAtBoundary { + None, + Top, + Bottom, + Both +} diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts new file mode 100644 index 00000000000..09427744226 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -0,0 +1,959 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { getZoomLevel } from 'vs/base/browser/browser'; +import * as DOM from 'vs/base/browser/dom'; +import { IMouseWheelEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Color, RGBA } from 'vs/base/common/color'; +import { Emitter, Event } from 'vs/base/common/event'; +import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import 'vs/css!./notebook'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; +import { Range } from 'vs/editor/common/core/range'; +import { ICompositeCodeEditor, IEditor } from 'vs/editor/common/editorCommon'; +import * as nls from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { contrastBorder, editorBackground, focusBorder, foreground, registerColor, textBlockQuoteBackground, textBlockQuoteBorder, textLinkActiveForeground, textLinkForeground, textPreformatForeground } from 'vs/platform/theme/common/colorRegistry'; +import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; +import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; +import { EditorOptions, IEditorCloseEvent, IEditorMemento } from 'vs/workbench/common/editor'; +import { CELL_MARGIN, CELL_RUN_GUTTER, EDITOR_TOP_MARGIN } from 'vs/workbench/contrib/notebook/browser/constants'; +import { NotebookFindWidget } from 'vs/workbench/contrib/notebook/browser/contrib/notebookFindWidget'; +import { CellEditState, CellFocusMode, ICellViewModel, INotebookEditor, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NotebookEditorInput, NotebookEditorModel } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; +import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService'; +import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList'; +import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; +import { BackLayerWebView } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView'; +import { CodeCellRenderer, MarkdownCellRenderer, NotebookCellListDelegate } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer'; +import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; +import { NotebookEventDispatcher, NotebookLayoutChangedEvent } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; +import { CellViewModel, IModelDecorationsChangeAccessor, INotebookEditorViewState, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; +import { CellKind, CellUri, IOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { getExtraColor } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils'; +import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; + +const $ = DOM.$; +const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState'; + +export class NotebookEditorOptions extends EditorOptions { + + readonly cellOptions?: IResourceEditorInput; + + constructor(options: Partial) { + super(); + this.overwrite(options); + this.cellOptions = options.cellOptions; + } + + with(options: Partial): NotebookEditorOptions { + return new NotebookEditorOptions({ ...this, ...options }); + } +} + +export class NotebookCodeEditors implements ICompositeCodeEditor { + + private readonly _disposables = new DisposableStore(); + private readonly _onDidChangeActiveEditor = new Emitter(); + readonly onDidChangeActiveEditor: Event = this._onDidChangeActiveEditor.event; + + constructor( + private _list: NotebookCellList, + private _renderedEditors: Map + ) { + _list.onDidChangeFocus(_e => this._onDidChangeActiveEditor.fire(this), undefined, this._disposables); + } + + dispose(): void { + this._onDidChangeActiveEditor.dispose(); + this._disposables.dispose(); + } + + get activeCodeEditor(): IEditor | undefined { + const [focused] = this._list.getFocusedElements(); + return this._renderedEditors.get(focused); + } +} + +export class NotebookEditor extends BaseEditor implements INotebookEditor { + static readonly ID: string = 'workbench.editor.notebook'; + private rootElement!: HTMLElement; + private body!: HTMLElement; + private webview: BackLayerWebView | null = null; + private webviewTransparentCover: HTMLElement | null = null; + private list: NotebookCellList | undefined; + private control: ICompositeCodeEditor | undefined; + private renderedEditors: Map = new Map(); + private eventDispatcher: NotebookEventDispatcher | undefined; + private notebookViewModel: NotebookViewModel | undefined; + private localStore: DisposableStore = this._register(new DisposableStore()); + private editorMemento: IEditorMemento; + private readonly groupListener = this._register(new MutableDisposable()); + private fontInfo: BareFontInfo | undefined; + private dimension: DOM.Dimension | null = null; + private editorFocus: IContextKey | null = null; + private editorEditable: IContextKey | null = null; + private editorExecutingNotebook: IContextKey | null = null; + private outputRenderer: OutputRenderer; + private findWidget: NotebookFindWidget; + + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IThemeService themeService: IThemeService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IStorageService storageService: IStorageService, + @INotebookService private notebookService: INotebookService, + @IEditorGroupsService editorGroupService: IEditorGroupsService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + // @IEditorProgressService private readonly progressService: IEditorProgressService, + ) { + super(NotebookEditor.ID, telemetryService, themeService, storageService); + + this.editorMemento = this.getEditorMemento(editorGroupService, NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY); + this.outputRenderer = new OutputRenderer(this, this.instantiationService); + this.findWidget = this.instantiationService.createInstance(NotebookFindWidget, this); + this.findWidget.updateTheme(this.themeService.getColorTheme()); + } + + get viewModel() { + return this.notebookViewModel; + } + + get minimumWidth(): number { return 375; } + get maximumWidth(): number { return Number.POSITIVE_INFINITY; } + + // these setters need to exist because this extends from BaseEditor + set minimumWidth(value: number) { /*noop*/ } + set maximumWidth(value: number) { /*noop*/ } + + + //#region Editor Core + + + public get isNotebookEditor() { + return true; + } + + protected createEditor(parent: HTMLElement): void { + this.rootElement = DOM.append(parent, $('.notebook-editor')); + this.createBody(this.rootElement); + this.generateFontInfo(); + this.editorFocus = NOTEBOOK_EDITOR_FOCUSED.bindTo(this.contextKeyService); + this.editorFocus.set(true); + this._register(this.onDidFocus(() => { + this.editorFocus?.set(true); + })); + + this._register(this.onDidBlur(() => { + this.editorFocus?.set(false); + })); + + this.editorEditable = NOTEBOOK_EDITOR_EDITABLE.bindTo(this.contextKeyService); + this.editorEditable.set(true); + this.editorExecutingNotebook = NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK.bindTo(this.contextKeyService); + } + + private generateFontInfo(): void { + const editorOptions = this.configurationService.getValue('editor'); + this.fontInfo = BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel()); + } + + private createBody(parent: HTMLElement): void { + this.body = document.createElement('div'); + DOM.addClass(this.body, 'cell-list-container'); + this.createCellList(); + DOM.append(parent, this.body); + DOM.append(parent, this.findWidget.getDomNode()); + } + + private createCellList(): void { + DOM.addClass(this.body, 'cell-list-container'); + + const renders = [ + this.instantiationService.createInstance(CodeCellRenderer, this, this.contextKeyService, this.renderedEditors), + this.instantiationService.createInstance(MarkdownCellRenderer, this.contextKeyService, this), + ]; + + this.list = this.instantiationService.createInstance( + NotebookCellList, + 'NotebookCellList', + this.body, + this.instantiationService.createInstance(NotebookCellListDelegate), + renders, + this.contextKeyService, + { + setRowLineHeight: false, + setRowHeight: false, + supportDynamicHeights: true, + horizontalScrolling: false, + keyboardSupport: false, + mouseSupport: true, + multipleSelectionSupport: false, + enableKeyboardNavigation: true, + additionalScrollHeight: 0, + styleController: (_suffix: string) => { return this.list!; }, + overrideStyles: { + listBackground: editorBackground, + listActiveSelectionBackground: editorBackground, + listActiveSelectionForeground: foreground, + listFocusAndSelectionBackground: editorBackground, + listFocusAndSelectionForeground: foreground, + listFocusBackground: editorBackground, + listFocusForeground: foreground, + listHoverForeground: foreground, + listHoverBackground: editorBackground, + listHoverOutline: focusBorder, + listFocusOutline: focusBorder, + listInactiveSelectionBackground: editorBackground, + listInactiveSelectionForeground: foreground, + listInactiveFocusBackground: editorBackground, + listInactiveFocusOutline: editorBackground, + } + }, + ); + + this.control = new NotebookCodeEditors(this.list, this.renderedEditors); + this.webview = this.instantiationService.createInstance(BackLayerWebView, this); + this._register(this.webview.onMessage(message => { + if (this.viewModel) { + this.notebookService.onDidReceiveMessage(this.viewModel.viewType, this.viewModel.uri, message); + } + })); + this.list.rowsContainer.appendChild(this.webview.element); + + this._register(this.list); + + // transparent cover + this.webviewTransparentCover = DOM.append(this.list.rowsContainer, $('.webview-cover')); + this.webviewTransparentCover.style.display = 'none'; + + this._register(DOM.addStandardDisposableGenericMouseDownListner(this.rootElement, (e: StandardMouseEvent) => { + if (DOM.hasClass(e.target, 'slider') && this.webviewTransparentCover) { + this.webviewTransparentCover.style.display = 'block'; + } + })); + + this._register(DOM.addStandardDisposableGenericMouseUpListner(this.rootElement, (e: StandardMouseEvent) => { + if (this.webviewTransparentCover) { + // no matter when + this.webviewTransparentCover.style.display = 'none'; + } + })); + + } + + getControl() { + return this.control; + } + + getInnerWebview(): Webview | undefined { + return this.webview?.webview; + } + + onHide() { + this.editorFocus?.set(false); + if (this.webview) { + this.localStore.clear(); + this.list?.rowsContainer.removeChild(this.webview?.element); + this.webview?.dispose(); + this.webview = null; + } + + this.list?.splice(0, this.list?.length); + super.onHide(); + } + + setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { + super.setEditorVisible(visible, group); + this.groupListener.value = ((group as IEditorGroupView).onWillCloseEditor(e => this.onWillCloseEditorInGroup(e))); + } + + private onWillCloseEditorInGroup(e: IEditorCloseEvent): void { + const editor = e.editor; + if (!(editor instanceof NotebookEditorInput)) { + return; // only handle files + } + + if (editor === this.input) { + this.saveTextEditorViewState(editor); + } + } + + focus() { + super.focus(); + this.editorFocus?.set(true); + } + + async setInput(input: NotebookEditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { + if (this.input instanceof NotebookEditorInput) { + this.saveTextEditorViewState(this.input); + } + + await super.setInput(input, options, token); + const model = await input.resolve(); + + if (this.notebookViewModel === undefined || !this.notebookViewModel.equal(model) || this.webview === null) { + this.detachModel(); + await this.attachModel(input, model); + } + + // reveal cell if editor options tell to do so + if (options instanceof NotebookEditorOptions && options.cellOptions) { + const cellOptions = options.cellOptions; + const cell = this.notebookViewModel!.viewCells.find(cell => cell.uri.toString() === cellOptions.resource.toString()); + if (cell) { + this.selectElement(cell); + this.revealInCenterIfOutsideViewport(cell); + const editor = this.renderedEditors.get(cell)!; + if (editor) { + if (cellOptions.options?.selection) { + const { selection } = cellOptions.options; + editor.setSelection({ + ...selection, + endLineNumber: selection.endLineNumber || selection.startLineNumber, + endColumn: selection.endColumn || selection.startColumn + }); + } + if (!cellOptions.options?.preserveFocus) { + editor.focus(); + } + } + } + } + } + + clearInput(): void { + if (this.input && this.input instanceof NotebookEditorInput && !this.input.isDisposed()) { + this.saveTextEditorViewState(this.input); + } + + super.clearInput(); + } + + private detachModel() { + this.localStore.clear(); + this.notebookViewModel?.dispose(); + this.notebookViewModel = undefined; + this.webview?.clearInsets(); + this.webview?.clearPreloadsCache(); + this.findWidget.clear(); + this.list?.splice(0, this.list?.length || 0); + } + + private async attachModel(input: NotebookEditorInput, model: NotebookEditorModel) { + if (!this.webview) { + this.webview = this.instantiationService.createInstance(BackLayerWebView, this); + this.list?.rowsContainer.insertAdjacentElement('afterbegin', this.webview!.element); + } + + this.eventDispatcher = new NotebookEventDispatcher(); + this.notebookViewModel = this.instantiationService.createInstance(NotebookViewModel, input.viewType!, model, this.eventDispatcher, this.getLayoutInfo()); + this.editorEditable?.set(!!this.notebookViewModel.metadata?.editable); + this.eventDispatcher.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]); + const viewState = this.loadTextEditorViewState(input); + this.notebookViewModel.restoreEditorViewState(viewState); + + this.localStore.add(this.eventDispatcher.onDidChangeMetadata((e) => { + this.editorEditable?.set(e.source.editable); + })); + + this.localStore.add(this.notebookViewModel.onDidChangeViewCells((e) => { + if (e.synchronous) { + e.splices.reverse().forEach((diff) => { + // remove output in the webview + for (let i = diff[0]; i < diff[0] + diff[1]; i++) { + const cell = this.list?.element(i); + cell?.cell.outputs.forEach(output => { + this.removeInset(output); + }); + } + + this.list?.splice(diff[0], diff[1], diff[2]); + }); + } else { + DOM.scheduleAtNextAnimationFrame(() => { + e.splices.reverse().forEach((diff) => { + // remove output in the webview + for (let i = diff[0]; i < diff[0] + diff[1]; i++) { + const cell = this.list?.element(i); + cell?.cell.outputs.forEach(output => { + this.removeInset(output); + }); + } + + this.list?.splice(diff[0], diff[1], diff[2]); + }); + }); + } + })); + + this.webview?.updateRendererPreloads(this.notebookViewModel.renderers); + + this.localStore.add(this.list!.onWillScroll(e => { + this.webview!.updateViewScrollTop(-e.scrollTop, []); + this.webviewTransparentCover!.style.top = `${e.scrollTop}px`; + })); + + this.localStore.add(this.list!.onDidChangeContentHeight(() => { + const scrollTop = this.list?.scrollTop || 0; + const scrollHeight = this.list?.scrollHeight || 0; + this.webview!.element.style.height = `${scrollHeight}px`; + let updateItems: { cell: CodeCellViewModel, output: IOutput, cellTop: number }[] = []; + + if (this.webview?.insetMapping) { + this.webview?.insetMapping.forEach((value, key) => { + let cell = value.cell; + let index = this.notebookViewModel!.getViewCellIndex(cell); + let cellTop = this.list?.getAbsoluteTop(index) || 0; + if (this.webview!.shouldUpdateInset(cell, key, cellTop)) { + updateItems.push({ + cell: cell, + output: key, + cellTop: cellTop + }); + } + }); + + if (updateItems.length) { + this.webview?.updateViewScrollTop(-scrollTop, updateItems); + } + } + })); + + this.list?.splice(0, 0, this.notebookViewModel!.viewCells as CellViewModel[]); + this.list?.layout(); + + if (viewState?.scrollPosition !== undefined) { + this.list!.scrollTop = viewState!.scrollPosition.top; + this.list!.scrollLeft = viewState!.scrollPosition.left; + } else { + this.list!.scrollTop = 0; + this.list!.scrollLeft = 0; + } + } + + private saveTextEditorViewState(input: NotebookEditorInput): void { + if (this.group && this.notebookViewModel) { + const state = this.notebookViewModel.saveEditorViewState(); + if (this.list) { + state.scrollPosition = { left: this.list.scrollLeft, top: this.list.scrollTop }; + let cellHeights: { [key: number]: number } = {}; + for (let i = 0; i < this.list.length; i++) { + const elm = this.list.element(i)!; + if (elm.cellKind === CellKind.Code) { + cellHeights[i] = elm.layoutInfo.totalHeight; + } else { + cellHeights[i] = 0; + } + } + + state.cellTotalHeights = cellHeights; + } + + this.editorMemento.saveEditorState(this.group, input.resource, state); + } + } + + private loadTextEditorViewState(input: NotebookEditorInput): INotebookEditorViewState | undefined { + if (this.group) { + return this.editorMemento.loadEditorState(this.group, input.resource); + } + + return; + } + + layout(dimension: DOM.Dimension): void { + this.dimension = new DOM.Dimension(dimension.width, dimension.height); + DOM.toggleClass(this.rootElement, 'mid-width', dimension.width < 1000 && dimension.width >= 600); + DOM.toggleClass(this.rootElement, 'narrow-width', dimension.width < 600); + DOM.size(this.body, dimension.width, dimension.height); + this.list?.updateOptions({ additionalScrollHeight: dimension.height }); + this.list?.layout(dimension.height, dimension.width); + + if (this.webviewTransparentCover) { + this.webviewTransparentCover.style.height = `${dimension.height}px`; + this.webviewTransparentCover.style.width = `${dimension.width}px`; + } + + this.eventDispatcher?.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]); + } + + protected saveState(): void { + if (this.input instanceof NotebookEditorInput) { + this.saveTextEditorViewState(this.input); + } + + super.saveState(); + } + + //#endregion + + //#region Editor Features + + selectElement(cell: ICellViewModel) { + const index = this.notebookViewModel?.getViewCellIndex(cell); + + if (index !== undefined) { + this.list?.setSelection([index]); + this.list?.setFocus([index]); + } + } + + revealInView(cell: ICellViewModel) { + const index = this.notebookViewModel?.getViewCellIndex(cell); + + if (index !== undefined) { + this.list?.revealInView(index); + } + } + + revealInCenterIfOutsideViewport(cell: ICellViewModel) { + const index = this.notebookViewModel?.getViewCellIndex(cell); + + if (index !== undefined) { + this.list?.revealInCenterIfOutsideViewport(index); + } + } + + revealInCenter(cell: ICellViewModel) { + const index = this.notebookViewModel?.getViewCellIndex(cell); + + if (index !== undefined) { + this.list?.revealInCenter(index); + } + } + + revealLineInView(cell: ICellViewModel, line: number): void { + const index = this.notebookViewModel?.getViewCellIndex(cell); + + if (index !== undefined) { + this.list?.revealLineInView(index, line); + } + } + + revealLineInCenter(cell: ICellViewModel, line: number) { + const index = this.notebookViewModel?.getViewCellIndex(cell); + + if (index !== undefined) { + this.list?.revealLineInCenter(index, line); + } + } + + revealLineInCenterIfOutsideViewport(cell: ICellViewModel, line: number) { + const index = this.notebookViewModel?.getViewCellIndex(cell); + + if (index !== undefined) { + this.list?.revealLineInCenterIfOutsideViewport(index, line); + } + } + + revealRangeInView(cell: ICellViewModel, range: Range): void { + const index = this.notebookViewModel?.getViewCellIndex(cell); + + if (index !== undefined) { + this.list?.revealRangeInView(index, range); + } + } + + revealRangeInCenter(cell: ICellViewModel, range: Range): void { + const index = this.notebookViewModel?.getViewCellIndex(cell); + + if (index !== undefined) { + this.list?.revealRangeInCenter(index, range); + } + } + + revealRangeInCenterIfOutsideViewport(cell: ICellViewModel, range: Range): void { + const index = this.notebookViewModel?.getViewCellIndex(cell); + + if (index !== undefined) { + this.list?.revealRangeInCenterIfOutsideViewport(index, range); + } + } + + setCellSelection(cell: ICellViewModel, range: Range): void { + const index = this.notebookViewModel?.getViewCellIndex(cell); + + if (index !== undefined) { + this.list?.setCellSelection(index, range); + } + } + + changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => any): any { + return this.notebookViewModel?.changeDecorations(callback); + } + + //#endregion + + //#region Find Delegate + + public showFind() { + this.findWidget.reveal(); + } + + public hideFind() { + this.findWidget.hide(); + this.focus(); + } + + //#endregion + + //#region Cell operations + async layoutNotebookCell(cell: ICellViewModel, height: number): Promise { + let relayout = (cell: ICellViewModel, height: number) => { + let index = this.notebookViewModel!.getViewCellIndex(cell); + if (index >= 0) { + this.list?.updateElementHeight(index, height); + } + }; + + let r: () => void; + DOM.scheduleAtNextAnimationFrame(() => { + relayout(cell, height); + r(); + }); + + return new Promise(resolve => { r = resolve; }); + } + + async insertNotebookCell(cell: ICellViewModel, type: CellKind, direction: 'above' | 'below', initialText: string = ''): Promise { + const newLanguages = this.notebookViewModel!.languages; + const language = newLanguages && newLanguages.length ? newLanguages[0] : 'markdown'; + const index = this.notebookViewModel!.getViewCellIndex(cell); + const insertIndex = direction === 'above' ? index : index + 1; + const newCell = this.notebookViewModel!.createCell(insertIndex, initialText.split(/\r?\n/g), language, type, true); + this.list?.setFocus([insertIndex]); + + if (type === CellKind.Markdown) { + newCell.editState = CellEditState.Editing; + } + + let r: () => void; + DOM.scheduleAtNextAnimationFrame(() => { + this.list?.revealInCenterIfOutsideViewport(insertIndex); + r(); + }); + + return new Promise(resolve => { r = resolve; }); + } + + async deleteNotebookCell(cell: ICellViewModel): Promise { + (cell as CellViewModel).save(); + const index = this.notebookViewModel!.getViewCellIndex(cell); + this.notebookViewModel!.deleteCell(index, true); + } + + async moveCellDown(cell: ICellViewModel): Promise { + const index = this.notebookViewModel!.getViewCellIndex(cell); + if (index === this.notebookViewModel!.viewCells.length - 1) { + return; + } + + const newIdx = index + 1; + return this.moveCellToIndex(index, newIdx); + } + + async moveCellUp(cell: ICellViewModel): Promise { + const index = this.notebookViewModel!.getViewCellIndex(cell); + if (index === 0) { + return; + } + + const newIdx = index - 1; + return this.moveCellToIndex(index, newIdx); + } + + private async moveCellToIndex(index: number, newIdx: number): Promise { + if (!this.notebookViewModel!.moveCellToIdx(index, newIdx, true)) { + return; + } + + let r: () => void; + DOM.scheduleAtNextAnimationFrame(() => { + this.list?.revealInCenterIfOutsideViewport(index + 1); + r(); + }); + + return new Promise(resolve => { r = resolve; }); + } + + editNotebookCell(cell: CellViewModel): void { + cell.editState = CellEditState.Editing; + + this.renderedEditors.get(cell)?.focus(); + } + + saveNotebookCell(cell: ICellViewModel): void { + cell.editState = CellEditState.Preview; + } + + getActiveCell() { + let elements = this.list?.getFocusedElements(); + + if (elements && elements.length) { + return elements[0]; + } + + return undefined; + } + + cancelNotebookExecution(): void { + if (!this.notebookViewModel!.currentTokenSource) { + throw new Error('Notebook is not executing'); + } + + + this.notebookViewModel!.currentTokenSource.cancel(); + this.notebookViewModel!.currentTokenSource = undefined; + } + + async executeNotebook(): Promise { + // return this.progressService.showWhile(this._executeNotebook()); + return this._executeNotebook(); + } + + async _executeNotebook(): Promise { + if (this.notebookViewModel!.currentTokenSource) { + return; + } + + const tokenSource = new CancellationTokenSource(); + try { + this.editorExecutingNotebook!.set(true); + this.notebookViewModel!.currentTokenSource = tokenSource; + + for (let cell of this.notebookViewModel!.viewCells) { + if (cell.cellKind === CellKind.Code) { + await this._executeNotebookCell(cell, tokenSource); + } + } + } finally { + this.editorExecutingNotebook!.set(false); + this.notebookViewModel!.currentTokenSource = undefined; + tokenSource.dispose(); + } + } + + cancelNotebookCellExecution(cell: ICellViewModel): void { + if (!cell.currentTokenSource) { + throw new Error('Cell is not executing'); + } + + cell.currentTokenSource.cancel(); + cell.currentTokenSource = undefined; + } + + async executeNotebookCell(cell: ICellViewModel): Promise { + const tokenSource = new CancellationTokenSource(); + try { + this._executeNotebookCell(cell, tokenSource); + } finally { + tokenSource.dispose(); + } + } + + async _executeNotebookCell(cell: ICellViewModel, tokenSource: CancellationTokenSource): Promise { + try { + cell.currentTokenSource = tokenSource; + const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0]; + if (provider) { + const viewType = provider.id; + const notebookUri = CellUri.parse(cell.uri)?.notebook; + if (notebookUri) { + return await this.notebookService.executeNotebookCell(viewType, notebookUri, cell.handle, tokenSource.token); + } + } + } finally { + cell.currentTokenSource = undefined; + } + } + + focusNotebookCell(cell: ICellViewModel, focusEditor: boolean) { + const index = this.notebookViewModel!.getViewCellIndex(cell); + + if (focusEditor) { + this.list?.setFocus([index]); + this.list?.setSelection([index]); + this.list?.focusView(); + + cell.editState = CellEditState.Editing; + cell.focusMode = CellFocusMode.Editor; + this.revealInCenterIfOutsideViewport(cell); + } else { + let itemDOM = this.list?.domElementAtIndex(index); + if (document.activeElement && itemDOM && itemDOM.contains(document.activeElement)) { + (document.activeElement as HTMLElement).blur(); + } + + cell.editState = CellEditState.Preview; + cell.focusMode = CellFocusMode.Editor; + + this.list?.setFocus([index]); + this.list?.setSelection([index]); + this.revealInCenterIfOutsideViewport(cell); + this.list?.focusView(); + } + } + + //#endregion + + //#region MISC + + getLayoutInfo(): NotebookLayoutInfo { + if (!this.list) { + throw new Error('Editor is not initalized successfully'); + } + + return { + width: this.dimension!.width, + height: this.dimension!.height, + fontInfo: this.fontInfo! + }; + } + + triggerScroll(event: IMouseWheelEvent) { + this.list?.triggerScrollFromMouseWheelEvent(event); + } + + createInset(cell: CodeCellViewModel, output: IOutput, shadowContent: string, offset: number) { + if (!this.webview) { + return; + } + + let preloads = this.notebookViewModel!.renderers; + + if (!this.webview!.insetMapping.has(output)) { + let index = this.notebookViewModel!.getViewCellIndex(cell); + let cellTop = this.list?.getAbsoluteTop(index) || 0; + + this.webview!.createInset(cell, output, cellTop, offset, shadowContent, preloads); + } else { + let index = this.notebookViewModel!.getViewCellIndex(cell); + let cellTop = this.list?.getAbsoluteTop(index) || 0; + let scrollTop = this.list?.scrollTop || 0; + + this.webview!.updateViewScrollTop(-scrollTop, [{ cell: cell, output: output, cellTop: cellTop }]); + } + } + + removeInset(output: IOutput) { + if (!this.webview) { + return; + } + + this.webview!.removeInset(output); + } + + getOutputRenderer(): OutputRenderer { + return this.outputRenderer; + } + + postMessage(message: any) { + this.webview?.webview.sendMessage(message); + } + + //#endregion + + toJSON(): any { + return { + notebookHandle: this.viewModel?.handle + }; + } +} + +const embeddedEditorBackground = 'walkThrough.embeddedEditorBackground'; + +export const focusedCellIndicator = registerColor('notebook.focusedCellIndicator', { + light: new Color(new RGBA(102, 175, 224)), + dark: new Color(new RGBA(12, 125, 157)), + hc: new Color(new RGBA(0, 73, 122)) +}, nls.localize('notebook.focusedCellIndicator', "The color of the focused notebook cell indicator.")); + +export const notebookOutputContainerColor = registerColor('notebook.outputContainerBackgroundColor', { + dark: new Color(new RGBA(255, 255, 255, 0.06)), + light: new Color(new RGBA(228, 230, 241)), + hc: null +} + , nls.localize('notebook.outputContainerBackgroundColor', "The Color of the notebook output container background.")); + +export const CELL_TOOLBAR_SEPERATOR = registerColor('notebook.cellToolbarSeperator', { + dark: Color.fromHex('#808080').transparent(0.35), + light: Color.fromHex('#808080').transparent(0.35), + hc: contrastBorder +}, nls.localize('cellToolbarSeperator', "The color of seperator in Cell bottom toolbar")); + + +registerThemingParticipant((theme, collector) => { + const color = getExtraColor(theme, embeddedEditorBackground, { dark: 'rgba(0, 0, 0, .4)', extra_dark: 'rgba(200, 235, 255, .064)', light: '#f4f4f4', hc: null }); + if (color) { + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .monaco-editor-background, + .monaco-workbench .part.editor > .content .notebook-editor .cell .margin-view-overlays { background: ${color}; }`); + } + const link = theme.getColor(textLinkForeground); + if (link) { + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .output a { color: ${link}; }`); + } + const activeLink = theme.getColor(textLinkActiveForeground); + if (activeLink) { + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .output a:hover, + .monaco-workbench .part.editor > .content .notebook-editor .cell .output a:active { color: ${activeLink}; }`); + } + const shortcut = theme.getColor(textPreformatForeground); + if (shortcut) { + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor code, + .monaco-workbench .part.editor > .content .notebook-editor .shortcut { color: ${shortcut}; }`); + } + const border = theme.getColor(contrastBorder); + if (border) { + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-editor { border-color: ${border}; }`); + } + const quoteBackground = theme.getColor(textBlockQuoteBackground); + if (quoteBackground) { + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor blockquote { background: ${quoteBackground}; }`); + } + const quoteBorder = theme.getColor(textBlockQuoteBorder); + if (quoteBorder) { + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor blockquote { border-color: ${quoteBorder}; }`); + } + + const containerBackground = theme.getColor(notebookOutputContainerColor); + + if (containerBackground) { + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .output { background-color: ${containerBackground}; }`); + } + + const focusedCellIndicatorColor = theme.getColor(focusedCellIndicator); + if (focusedCellIndicatorColor) { + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row.focused .notebook-cell-focus-indicator { border-color: ${focusedCellIndicatorColor}; }`); + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row.selected .notebook-cell-focus-indicator { border-color: ${focusedCellIndicatorColor}; }`); + } + + const cellToolbarSeperator = theme.getColor(CELL_TOOLBAR_SEPERATOR); + if (cellToolbarSeperator) { + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell-bottom-toolbar-container .seperator { background-color: ${cellToolbarSeperator} }`); + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell-bottom-toolbar-container .seperator-short { background-color: ${cellToolbarSeperator} }`); + } + + // Cell Margin + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row > div.cell { margin: ${EDITOR_TOP_MARGIN}px ${CELL_MARGIN}px 0px ${CELL_MARGIN}px; }`); + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .output { margin: 0px ${CELL_MARGIN}px 0px ${CELL_MARGIN + CELL_RUN_GUTTER}px }`); + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell-bottom-toolbar-container { width: calc(100% - ${CELL_MARGIN * 2 + CELL_RUN_GUTTER}px); margin: 0px ${CELL_MARGIN}px 0px ${CELL_MARGIN + CELL_RUN_GUTTER}px }`); + + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .cell-editor-container { width: calc(100% - ${CELL_RUN_GUTTER}px); }`); + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .markdown-editor-container { margin-left: ${CELL_RUN_GUTTER}px; }`); + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row > div.cell.markdown { padding-left: ${CELL_RUN_GUTTER}px; }`); + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .run-button-container { width: ${CELL_RUN_GUTTER}px; }`); +}); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts new file mode 100644 index 00000000000..3749b6e33d5 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts @@ -0,0 +1,159 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { EditorInput, EditorModel, IEditorInput, GroupIdentifier, ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; +import { Emitter, Event } from 'vs/base/common/event'; +import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService'; +import { ICell, NotebookCellsSplice } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { URI } from 'vs/base/common/uri'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; +import { isEqual } from 'vs/base/common/resources'; + +export class NotebookEditorModel extends EditorModel { + private _dirty = false; + + protected readonly _onDidChangeDirty = this._register(new Emitter()); + readonly onDidChangeDirty = this._onDidChangeDirty.event; + + private readonly _onDidChangeCells = new Emitter(); + get onDidChangeCells(): Event { return this._onDidChangeCells.event; } + + + get notebook() { + return this._notebook; + } + + constructor( + private _notebook: NotebookTextModel + ) { + super(); + + if (_notebook && _notebook.onDidChangeCells) { + this._register(_notebook.onDidChangeContent(() => { + this._dirty = true; + this._onDidChangeDirty.fire(); + })); + this._register(_notebook.onDidChangeCells((e) => { + this._onDidChangeCells.fire(e); + })); + } + } + + isDirty() { + return this._dirty; + } + + getNotebook(): NotebookTextModel { + return this._notebook; + } + + insertCell(cell: ICell, index: number) { + let notebook = this.getNotebook(); + + if (notebook) { + this.notebook.insertNewCell(index, [cell as NotebookCellTextModel]); + this._dirty = true; + this._onDidChangeDirty.fire(); + + } + } + + deleteCell(index: number) { + let notebook = this.getNotebook(); + + if (notebook) { + this.notebook.removeCell(index); + } + } + + async save(): Promise { + if (this._notebook) { + this._dirty = false; + this._onDidChangeDirty.fire(); + // todo, flush all states + return true; + } + + return false; + } +} + +export class NotebookEditorInput extends EditorInput { + static readonly ID: string = 'workbench.input.notebook'; + private promise: Promise | null = null; + private textModel: NotebookEditorModel | null = null; + + constructor( + public resource: URI, + public name: string, + public readonly viewType: string | undefined, + @INotebookService private readonly notebookService: INotebookService + ) { + super(); + } + + getTypeId(): string { + return NotebookEditorInput.ID; + } + + getName(): string { + return this.name; + } + + isDirty() { + return this.textModel?.isDirty() || false; + } + + async save(group: GroupIdentifier, options?: ISaveOptions): Promise { + if (this.textModel) { + await this.notebookService.save(this.textModel.notebook.viewType, this.textModel.notebook.uri); + await this.textModel.save(); + return this; + } + + return undefined; + } + + async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { + if (this.textModel) { + // TODO@rebornix we need hashing + await this.textModel.save(); + } + } + + async resolve(): Promise { + if (!this.promise) { + await this.notebookService.canResolve(this.viewType!); + + this.promise = this.notebookService.resolveNotebook(this.viewType!, this.resource).then(notebook => { + this.textModel = new NotebookEditorModel(notebook!); + this.textModel.onDidChangeDirty(() => this._onDidChangeDirty.fire()); + return this.textModel; + }); + } + + return this.promise; + } + + matches(otherInput: unknown): boolean { + if (this === otherInput) { + return true; + } + if (otherInput instanceof NotebookEditorInput) { + return this.viewType === otherInput.viewType + && isEqual(this.resource, otherInput.resource); + } + return false; + } + + dispose() { + if (this.textModel) { + this.notebookService.destoryNotebookDocument(this.textModel!.notebook.viewType, this.textModel!.notebook); + } + + super.dispose(); + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/notebookRegistry.ts b/src/vs/workbench/contrib/notebook/browser/notebookRegistry.ts new file mode 100644 index 00000000000..f6ddc3cb0de --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/notebookRegistry.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 { CellOutputKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { BrandedService, IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation'; +import { INotebookEditor, IOutputTransformContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; + +export type IOutputTransformCtor = IConstructorSignature1; + +export interface IOutputTransformDescription { + id: string; + kind: CellOutputKind; + ctor: IOutputTransformCtor; +} + +export namespace NotebookRegistry { + export function getOutputTransformContributions(): IOutputTransformDescription[] { + return NotebookRegistryImpl.INSTANCE.getNotebookOutputTransform(); + } +} + +export function registerOutputTransform(id: string, kind: CellOutputKind, ctor: { new(editor: INotebookEditor, ...services: Services): IOutputTransformContribution }): void { + NotebookRegistryImpl.INSTANCE.registerOutputTransform(id, kind, ctor); +} + +class NotebookRegistryImpl { + + static readonly INSTANCE = new NotebookRegistryImpl(); + + private readonly outputTransforms: IOutputTransformDescription[]; + + constructor() { + this.outputTransforms = []; + } + + registerOutputTransform(id: string, kind: CellOutputKind, ctor: { new(editor: INotebookEditor, ...services: Services): IOutputTransformContribution }): void { + this.outputTransforms.push({ id: id, kind: kind, ctor: ctor as IOutputTransformCtor }); + } + + getNotebookOutputTransform(): IOutputTransformDescription[] { + return this.outputTransforms.slice(0); + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/notebookService.ts b/src/vs/workbench/contrib/notebook/browser/notebookService.ts new file mode 100644 index 00000000000..713263f432f --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/notebookService.ts @@ -0,0 +1,319 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { URI } from 'vs/base/common/uri'; +import { notebookProviderExtensionPoint, notebookRendererExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint'; +import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; +import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol'; +import { Emitter, Event } from 'vs/base/common/event'; +import { INotebookTextModel, INotebookMimeTypeSelector, INotebookRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer'; +import { Iterable } from 'vs/base/common/iterator'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; + +function MODEL_ID(resource: URI): string { + return resource.toString(); +} + +export const INotebookService = createDecorator('notebookService'); + +export interface IMainNotebookController { + resolveNotebook(viewType: string, uri: URI): Promise; + executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise; + onDidReceiveMessage(uri: URI, message: any): void; + executeNotebookCell(uri: URI, handle: number, token: CancellationToken): Promise; + destoryNotebookDocument(notebook: INotebookTextModel): Promise; + save(uri: URI): Promise; +} + +export interface INotebookService { + _serviceBrand: undefined; + canResolve(viewType: string): Promise; + onDidChangeActiveEditor: Event<{ viewType: string, uri: URI }>; + registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController): void; + unregisterNotebookProvider(viewType: string): void; + registerNotebookRenderer(handle: number, extensionData: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: URI[]): void; + unregisterNotebookRenderer(handle: number): void; + getRendererInfo(handle: number): INotebookRendererInfo | undefined; + resolveNotebook(viewType: string, uri: URI): Promise; + executeNotebook(viewType: string, uri: URI): Promise; + executeNotebookCell(viewType: string, uri: URI, handle: number, token: CancellationToken): Promise; + + getContributedNotebookProviders(resource: URI): readonly NotebookProviderInfo[]; + getNotebookProviderResourceRoots(): URI[]; + destoryNotebookDocument(viewType: string, notebook: INotebookTextModel): void; + updateActiveNotebookDocument(viewType: string, resource: URI): void; + save(viewType: string, resource: URI): Promise; + onDidReceiveMessage(viewType: string, uri: URI, message: any): void; +} + +export class NotebookProviderInfoStore { + private readonly contributedEditors = new Map(); + + clear() { + this.contributedEditors.clear(); + } + + get(viewType: string): NotebookProviderInfo | undefined { + return this.contributedEditors.get(viewType); + } + + add(info: NotebookProviderInfo): void { + if (this.contributedEditors.has(info.id)) { + console.log(`Custom editor with id '${info.id}' already registered`); + return; + } + this.contributedEditors.set(info.id, info); + } + + getContributedNotebook(resource: URI): readonly NotebookProviderInfo[] { + return [...Iterable.filter(this.contributedEditors.values(), customEditor => customEditor.matches(resource))]; + } +} + +export class NotebookOutputRendererInfoStore { + private readonly contributedRenderers = new Map(); + + clear() { + this.contributedRenderers.clear(); + } + + get(viewType: string): NotebookOutputRendererInfo | undefined { + return this.contributedRenderers.get(viewType); + } + + add(info: NotebookOutputRendererInfo): void { + if (this.contributedRenderers.has(info.id)) { + console.log(`Custom notebook output renderer with id '${info.id}' already registered`); + return; + } + this.contributedRenderers.set(info.id, info); + } + + getContributedRenderer(mimeType: string): readonly NotebookOutputRendererInfo[] { + return Array.from(this.contributedRenderers.values()).filter(customEditor => + customEditor.matches(mimeType)); + } +} + +class ModelData implements IDisposable { + private readonly _modelEventListeners = new DisposableStore(); + + constructor( + public model: NotebookTextModel, + onWillDispose: (model: INotebookTextModel) => void + ) { + this._modelEventListeners.add(model.onWillDispose(() => onWillDispose(model))); + } + + dispose(): void { + this._modelEventListeners.dispose(); + } +} + + +export class NotebookService extends Disposable implements INotebookService { + _serviceBrand: undefined; + private readonly _notebookProviders = new Map(); + private readonly _notebookRenderers = new Map(); + notebookProviderInfoStore: NotebookProviderInfoStore = new NotebookProviderInfoStore(); + notebookRenderersInfoStore: NotebookOutputRendererInfoStore = new NotebookOutputRendererInfoStore(); + private readonly _models: { [modelId: string]: ModelData; }; + private _onDidChangeActiveEditor = new Emitter<{ viewType: string, uri: URI }>(); + onDidChangeActiveEditor: Event<{ viewType: string, uri: URI }> = this._onDidChangeActiveEditor.event; + private _resolvePool = new Map void)[]>(); + + constructor( + @IExtensionService private readonly extensionService: IExtensionService + ) { + super(); + + this._models = {}; + notebookProviderExtensionPoint.setHandler((extensions) => { + this.notebookProviderInfoStore.clear(); + + for (const extension of extensions) { + for (const notebookContribution of extension.value) { + this.notebookProviderInfoStore.add(new NotebookProviderInfo({ + id: notebookContribution.viewType, + displayName: notebookContribution.displayName, + selector: notebookContribution.selector || [], + })); + } + } + // console.log(this._notebookProviderInfoStore); + }); + + notebookRendererExtensionPoint.setHandler((renderers) => { + this.notebookRenderersInfoStore.clear(); + + for (const extension of renderers) { + for (const notebookContribution of extension.value) { + this.notebookRenderersInfoStore.add(new NotebookOutputRendererInfo({ + id: notebookContribution.viewType, + displayName: notebookContribution.displayName, + mimeTypes: notebookContribution.mimeTypes || [] + })); + } + } + + // console.log(this.notebookRenderersInfoStore); + }); + } + + async canResolve(viewType: string): Promise { + if (this._notebookProviders.has(viewType)) { + return; + } + + this.extensionService.activateByEvent(`onNotebookEditor:${viewType}`); + + let resolve: () => void; + const promise = new Promise(r => { resolve = r; }); + if (!this._resolvePool.has(viewType)) { + this._resolvePool.set(viewType, []); + } + + let resolves = this._resolvePool.get(viewType)!; + resolves.push(resolve!); + this._resolvePool.set(viewType, resolves); + return promise; + } + + registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController) { + this._notebookProviders.set(viewType, { extensionData, controller }); + + let resolves = this._resolvePool.get(viewType); + if (resolves) { + resolves.forEach(resolve => resolve()); + this._resolvePool.delete(viewType); + } + } + + unregisterNotebookProvider(viewType: string): void { + this._notebookProviders.delete(viewType); + } + + registerNotebookRenderer(handle: number, extensionData: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: URI[]) { + this._notebookRenderers.set(handle, { extensionData, type, selectors, preloads }); + } + + unregisterNotebookRenderer(handle: number) { + this._notebookRenderers.delete(handle); + } + + getRendererInfo(handle: number): INotebookRendererInfo | undefined { + const renderer = this._notebookRenderers.get(handle); + + if (renderer) { + return { + id: renderer.extensionData.id, + extensionLocation: URI.revive(renderer.extensionData.location), + preloads: renderer.preloads + }; + } + + return; + } + + async resolveNotebook(viewType: string, uri: URI): Promise { + const provider = this._notebookProviders.get(viewType); + if (!provider) { + return undefined; + } + + const notebookModel = await provider.controller.resolveNotebook(viewType, uri); + if (!notebookModel) { + return undefined; + } + + // new notebook model created + const modelId = MODEL_ID(uri); + const modelData = new ModelData( + notebookModel, + (model) => this._onWillDispose(model), + ); + this._models[modelId] = modelData; + return modelData.model; + } + + async executeNotebook(viewType: string, uri: URI): Promise { + let provider = this._notebookProviders.get(viewType); + + if (provider) { + return provider.controller.executeNotebook(viewType, uri, new CancellationTokenSource().token); // Cancellation for notebooks - TODO + } + + return; + } + + async executeNotebookCell(viewType: string, uri: URI, handle: number, token: CancellationToken): Promise { + const provider = this._notebookProviders.get(viewType); + if (provider) { + await provider.controller.executeNotebookCell(uri, handle, token); + } + } + + getContributedNotebookProviders(resource: URI): readonly NotebookProviderInfo[] { + return this.notebookProviderInfoStore.getContributedNotebook(resource); + } + + getContributedNotebookOutputRenderers(mimeType: string): readonly NotebookOutputRendererInfo[] { + return this.notebookRenderersInfoStore.getContributedRenderer(mimeType); + } + + getNotebookProviderResourceRoots(): URI[] { + let ret: URI[] = []; + this._notebookProviders.forEach(val => { + ret.push(URI.revive(val.extensionData.location)); + }); + + return ret; + } + + destoryNotebookDocument(viewType: string, notebook: INotebookTextModel): void { + let provider = this._notebookProviders.get(viewType); + + if (provider) { + provider.controller.destoryNotebookDocument(notebook); + } + } + + updateActiveNotebookDocument(viewType: string, resource: URI): void { + this._onDidChangeActiveEditor.fire({ viewType, uri: resource }); + } + + async save(viewType: string, resource: URI): Promise { + let provider = this._notebookProviders.get(viewType); + + if (provider) { + return provider.controller.save(resource); + } + + return false; + } + + onDidReceiveMessage(viewType: string, uri: URI, message: any): void { + let provider = this._notebookProviders.get(viewType); + + if (provider) { + return provider.controller.onDidReceiveMessage(uri, message); + } + } + + private _onWillDispose(model: INotebookTextModel): void { + let modelId = MODEL_ID(model.uri); + let modelData = this._models[modelId]; + + delete this._models[modelId]; + modelData?.dispose(); + + // this._onModelRemoved.fire(model); + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts new file mode 100644 index 00000000000..975c8136d44 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -0,0 +1,495 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as DOM from 'vs/base/browser/dom'; +import { IListRenderer, IListVirtualDelegate, ListError } from 'vs/base/browser/ui/list/list'; +import { Event } from 'vs/base/common/event'; +import { ScrollEvent } from 'vs/base/common/scrollable'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IListService, IWorkbenchListOptions, WorkbenchList } from 'vs/platform/list/browser/listService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; +import { isMacintosh } from 'vs/base/common/platform'; +import { NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { Range } from 'vs/editor/common/core/range'; +import { CellRevealType, CellRevealPosition, CursorAtBoundary } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; +import { IStyleController, IListStyles } from 'vs/base/browser/ui/list/listWidget'; + +export class NotebookCellList extends WorkbenchList implements IDisposable, IStyleController { + get onWillScroll(): Event { return this.view.onWillScroll; } + + get rowsContainer(): HTMLElement { + return this.view.containerDomNode; + } + private _previousSelectedElements: CellViewModel[] = []; + private _localDisposableStore = new DisposableStore(); + private styleElement?: HTMLStyleElement; + constructor( + private listUser: string, + container: HTMLElement, + delegate: IListVirtualDelegate, + renderers: IListRenderer[], + contextKeyService: IContextKeyService, + options: IWorkbenchListOptions, + @IListService listService: IListService, + @IThemeService themeService: IThemeService, + @IConfigurationService configurationService: IConfigurationService, + @IKeybindingService keybindingService: IKeybindingService + + ) { + super(listUser, container, delegate, renderers, options, contextKeyService, listService, themeService, configurationService, keybindingService); + + this._previousSelectedElements = this.getSelectedElements(); + this._localDisposableStore.add(this.onDidChangeSelection((e) => { + this._previousSelectedElements.forEach(element => { + if (e.elements.indexOf(element) < 0) { + element.onDeselect(); + } + }); + this._previousSelectedElements = e.elements; + })); + + const notebookEditorCursorAtBoundaryContext = NOTEBOOK_EDITOR_CURSOR_BOUNDARY.bindTo(contextKeyService); + notebookEditorCursorAtBoundaryContext.set('none'); + + let cursorSelectionListener: IDisposable | null = null; + let textEditorAttachListener: IDisposable | null = null; + + const recomputeContext = (element: CellViewModel) => { + switch (element.cursorAtBoundary()) { + case CursorAtBoundary.Both: + notebookEditorCursorAtBoundaryContext.set('both'); + break; + case CursorAtBoundary.Top: + notebookEditorCursorAtBoundaryContext.set('top'); + break; + case CursorAtBoundary.Bottom: + notebookEditorCursorAtBoundaryContext.set('bottom'); + break; + default: + notebookEditorCursorAtBoundaryContext.set('none'); + break; + } + return; + }; + + // Cursor Boundary context + this._localDisposableStore.add(this.onDidChangeSelection((e) => { + if (e.elements.length) { + cursorSelectionListener?.dispose(); + textEditorAttachListener?.dispose(); + // we only validate the first focused element + const focusedElement = e.elements[0]; + + cursorSelectionListener = focusedElement.onDidChangeCursorSelection(() => { + recomputeContext(focusedElement); + }); + + textEditorAttachListener = focusedElement.onDidChangeEditorAttachState(() => { + if (focusedElement.editorAttached) { + recomputeContext(focusedElement); + } + }); + + recomputeContext(focusedElement); + return; + } + + // reset context + notebookEditorCursorAtBoundaryContext.set('none'); + })); + + } + + domElementAtIndex(index: number): HTMLElement | null { + return this.view.domElement(index); + } + + focusView() { + this.view.domNode.focus(); + } + + getAbsoluteTop(index: number): number { + if (index < 0 || index >= this.length) { + throw new ListError(this.listUser, `Invalid index ${index}`); + } + + return this.view.elementTop(index); + } + + getElementHeight(index: number): number { + if (index < 0 || index >= this.length) { + throw new ListError(this.listUser, `Invalid index ${index}`); + } + + return this.view.elementHeight(index); + } + + triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) { + this.view.triggerScrollFromMouseWheelEvent(browserEvent); + } + + updateElementHeight(index: number, size: number): void { + const focused = this.getSelection(); + this.view.updateElementHeight(index, size, focused.length ? focused[0] : null); + // this.view.updateElementHeight(index, size, null); + } + + // override + domFocus() { + if (document.activeElement && this.view.domNode.contains(document.activeElement)) { + // for example, when focus goes into monaco editor, if we refocus the list view, the editor will lose focus. + return; + } + + if (!isMacintosh && document.activeElement && isContextMenuFocused()) { + return; + } + + super.domFocus(); + } + + private _revealRange(index: number, range: Range, revealType: CellRevealType, newlyCreated: boolean, alignToBottom: boolean) { + const element = this.view.element(index); + const scrollTop = this.view.getScrollTop(); + const wrapperBottom = scrollTop + this.view.renderHeight; + const startLineNumber = range.startLineNumber; + const lineOffset = element.getLineScrollTopOffset(startLineNumber); + const elementTop = this.view.elementTop(index); + const lineTop = elementTop + lineOffset; + + // TODO@rebornix 30 ---> line height * 1.5 + if (lineTop < scrollTop) { + this.view.setScrollTop(lineTop - 30); + } else if (lineTop > wrapperBottom) { + this.view.setScrollTop(scrollTop + lineTop - wrapperBottom + 30); + } else if (newlyCreated) { + // newly scrolled into view + if (alignToBottom) { + // align to the bottom + this.view.setScrollTop(scrollTop + lineTop - wrapperBottom + 30); + } else { + // align to to top + this.view.setScrollTop(lineTop - 30); + } + } + + if (revealType === CellRevealType.Range) { + element.revealRangeInCenter(range); + } + } + + // TODO@rebornix TEST & Fix potential bugs + // List items have real dynamic heights, which means after we set `scrollTop` based on the `elementTop(index)`, the element at `index` might still be removed from the view once all relayouting tasks are done. + // For example, we scroll item 10 into the view upwards, in the first round, items 7, 8, 9, 10 are all in the viewport. Then item 7 and 8 resize themselves to be larger and finally item 10 is removed from the view. + // To ensure that item 10 is always there, we need to scroll item 10 to the top edge of the viewport. + private _revealRangeInternal(index: number, range: Range, revealType: CellRevealType) { + const scrollTop = this.view.getScrollTop(); + const wrapperBottom = scrollTop + this.view.renderHeight; + const elementTop = this.view.elementTop(index); + const element = this.view.element(index); + + if (element.editorAttached) { + this._revealRange(index, range, revealType, false, false); + } else { + const elementHeight = this.view.elementHeight(index); + let upwards = false; + + if (elementTop + elementHeight < scrollTop) { + // scroll downwards + this.view.setScrollTop(elementTop); + upwards = false; + } else if (elementTop > wrapperBottom) { + // scroll upwards + this.view.setScrollTop(elementTop - this.view.renderHeight / 2); + upwards = true; + } + + const editorAttachedPromise = new Promise((resolve, reject) => { + element.onDidChangeEditorAttachState(state => state ? resolve() : reject()); + }); + + editorAttachedPromise.then(() => { + this._revealRange(index, range, revealType, true, upwards); + }); + } + } + + revealLineInView(index: number, line: number) { + this._revealRangeInternal(index, new Range(line, 1, line, 1), CellRevealType.Line); + } + + revealRangeInView(index: number, range: Range): void { + this._revealRangeInternal(index, range, CellRevealType.Range); + } + + private _revealRangeInCenterInternal(index: number, range: Range, revealType: CellRevealType) { + const reveal = (index: number, range: Range, revealType: CellRevealType) => { + const element = this.view.element(index); + let lineOffset = element.getLineScrollTopOffset(range.startLineNumber); + let lineOffsetInView = this.view.elementTop(index) + lineOffset; + this.view.setScrollTop(lineOffsetInView - this.view.renderHeight / 2); + + if (revealType === CellRevealType.Range) { + element.revealRangeInCenter(range); + } + }; + + const elementTop = this.view.elementTop(index); + const viewItemOffset = elementTop; + this.view.setScrollTop(viewItemOffset - this.view.renderHeight / 2); + const element = this.view.element(index); + + if (!element.editorAttached) { + getEditorAttachedPromise(element).then(() => reveal(index, range, revealType)); + } else { + reveal(index, range, revealType); + } + } + + revealLineInCenter(index: number, line: number) { + this._revealRangeInCenterInternal(index, new Range(line, 1, line, 1), CellRevealType.Line); + } + + revealRangeInCenter(index: number, range: Range): void { + this._revealRangeInCenterInternal(index, range, CellRevealType.Range); + } + + private _revealRangeInCenterIfOutsideViewportInternal(index: number, range: Range, revealType: CellRevealType) { + const reveal = (index: number, range: Range, revealType: CellRevealType) => { + const element = this.view.element(index); + let lineOffset = element.getLineScrollTopOffset(range.startLineNumber); + let lineOffsetInView = this.view.elementTop(index) + lineOffset; + this.view.setScrollTop(lineOffsetInView - this.view.renderHeight / 2); + + if (revealType === CellRevealType.Range) { + setTimeout(() => { + element.revealRangeInCenter(range); + }, 240); + } + }; + + const scrollTop = this.view.getScrollTop(); + const wrapperBottom = scrollTop + this.view.renderHeight; + const elementTop = this.view.elementTop(index); + const viewItemOffset = elementTop; + const element = this.view.element(index); + + if (viewItemOffset < scrollTop || viewItemOffset > wrapperBottom) { + // let it render + this.view.setScrollTop(viewItemOffset - this.view.renderHeight / 2); + + // after rendering, it might be pushed down due to markdown cell dynamic height + const elementTop = this.view.elementTop(index); + this.view.setScrollTop(elementTop - this.view.renderHeight / 2); + + // reveal editor + if (!element.editorAttached) { + getEditorAttachedPromise(element).then(() => reveal(index, range, revealType)); + } else { + // for example markdown + } + } else { + if (element.editorAttached) { + element.revealRangeInCenter(range); + } else { + // for example, markdown cell in preview mode + getEditorAttachedPromise(element).then(() => reveal(index, range, revealType)); + } + } + } + + revealLineInCenterIfOutsideViewport(index: number, line: number) { + this._revealRangeInCenterIfOutsideViewportInternal(index, new Range(line, 1, line, 1), CellRevealType.Line); + } + + revealRangeInCenterIfOutsideViewport(index: number, range: Range): void { + this._revealRangeInCenterIfOutsideViewportInternal(index, range, CellRevealType.Range); + } + + private _revealInternal(index: number, ignoreIfInsideViewport: boolean, revealPosition: CellRevealPosition) { + if (index >= this.view.length) { + return; + } + + const scrollTop = this.view.getScrollTop(); + const wrapperBottom = scrollTop + this.view.renderHeight; + const elementTop = this.view.elementTop(index); + + if (ignoreIfInsideViewport && elementTop >= scrollTop && elementTop < wrapperBottom) { + // inside the viewport + return; + } + + // first render + const viewItemOffset = revealPosition === CellRevealPosition.Top ? elementTop : (elementTop - this.view.renderHeight / 2); + this.view.setScrollTop(viewItemOffset); + + // second scroll as markdown cell is dynamic + const newElementTop = this.view.elementTop(index); + const newViewItemOffset = revealPosition === CellRevealPosition.Top ? newElementTop : (newElementTop - this.view.renderHeight / 2); + this.view.setScrollTop(newViewItemOffset); + } + + revealInView(index: number) { + this._revealInternal(index, true, CellRevealPosition.Top); + } + + revealInCenter(index: number) { + this._revealInternal(index, false, CellRevealPosition.Center); + } + + revealInCenterIfOutsideViewport(index: number) { + this._revealInternal(index, true, CellRevealPosition.Center); + } + + setCellSelection(index: number, range: Range) { + const element = this.view.element(index); + if (element.editorAttached) { + element.setSelection(range); + } else { + getEditorAttachedPromise(element).then(() => { element.setSelection(range); }); + } + } + + + style(styles: IListStyles) { + const selectorSuffix = this.view.domId; + if (!this.styleElement) { + this.styleElement = DOM.createStyleSheet(this.view.domNode); + } + const suffix = selectorSuffix && `.${selectorSuffix}`; + const content: string[] = []; + + if (styles.listBackground) { + if (styles.listBackground.isOpaque()) { + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows { background: ${styles.listBackground}; }`); + } else if (!isMacintosh) { // subpixel AA doesn't exist in macOS + console.warn(`List with id '${selectorSuffix}' was styled with a non-opaque background color. This will break sub-pixel antialiasing.`); + } + } + + if (styles.listFocusBackground) { + content.push(`.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused { background-color: ${styles.listFocusBackground}; }`); + content.push(`.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused:hover { background-color: ${styles.listFocusBackground}; }`); // overwrite :hover style in this case! + } + + if (styles.listFocusForeground) { + content.push(`.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused { color: ${styles.listFocusForeground}; }`); + } + + if (styles.listActiveSelectionBackground) { + content.push(`.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected { background-color: ${styles.listActiveSelectionBackground}; }`); + content.push(`.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected:hover { background-color: ${styles.listActiveSelectionBackground}; }`); // overwrite :hover style in this case! + } + + if (styles.listActiveSelectionForeground) { + content.push(`.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected { color: ${styles.listActiveSelectionForeground}; }`); + } + + if (styles.listFocusAndSelectionBackground) { + content.push(` + .monaco-drag-image, + .monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected.focused { background-color: ${styles.listFocusAndSelectionBackground}; } + `); + } + + if (styles.listFocusAndSelectionForeground) { + content.push(` + .monaco-drag-image, + .monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected.focused { color: ${styles.listFocusAndSelectionForeground}; } + `); + } + + if (styles.listInactiveFocusBackground) { + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused { background-color: ${styles.listInactiveFocusBackground}; }`); + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused:hover { background-color: ${styles.listInactiveFocusBackground}; }`); // overwrite :hover style in this case! + } + + if (styles.listInactiveSelectionBackground) { + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected { background-color: ${styles.listInactiveSelectionBackground}; }`); + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected:hover { background-color: ${styles.listInactiveSelectionBackground}; }`); // overwrite :hover style in this case! + } + + if (styles.listInactiveSelectionForeground) { + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected { color: ${styles.listInactiveSelectionForeground}; }`); + } + + if (styles.listHoverBackground) { + content.push(`.monaco-list${suffix}:not(.drop-target) > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover:not(.selected):not(.focused) { background-color: ${styles.listHoverBackground}; }`); + } + + if (styles.listHoverForeground) { + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover:not(.selected):not(.focused) { color: ${styles.listHoverForeground}; }`); + } + + if (styles.listSelectionOutline) { + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected { outline: 1px dotted ${styles.listSelectionOutline}; outline-offset: -1px; }`); + } + + if (styles.listFocusOutline) { + content.push(` + .monaco-drag-image, + .monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; } + `); + } + + if (styles.listInactiveFocusOutline) { + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused { outline: 1px dotted ${styles.listInactiveFocusOutline}; outline-offset: -1px; }`); + } + + if (styles.listHoverOutline) { + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover { outline: 1px dashed ${styles.listHoverOutline}; outline-offset: -1px; }`); + } + + if (styles.listDropBackground) { + content.push(` + .monaco-list${suffix}.drop-target, + .monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows.drop-target, + .monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-row.drop-target { background-color: ${styles.listDropBackground} !important; color: inherit !important; } + `); + } + + 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}; }`); + } + + const newStyles = content.join('\n'); + if (newStyles !== this.styleElement.innerHTML) { + this.styleElement.innerHTML = newStyles; + } + } + + dispose() { + this._localDisposableStore.dispose(); + super.dispose(); + } +} + +function getEditorAttachedPromise(element: CellViewModel) { + return new Promise((resolve, reject) => { + Event.once(element.onDidChangeEditorAttachState)(state => state ? resolve() : reject()); + }); +} + +function isContextMenuFocused() { + return !!DOM.findParentWithClass(document.activeElement, 'context-view'); +} diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/outputRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/output/outputRenderer.ts new file mode 100644 index 00000000000..b6d6134632f --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/view/output/outputRenderer.ts @@ -0,0 +1,55 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IOutput, IRenderOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookRegistry } from 'vs/workbench/contrib/notebook/browser/notebookRegistry'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { INotebookEditor, IOutputTransformContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; + +export class OutputRenderer { + protected readonly _contributions: { [key: string]: IOutputTransformContribution; }; + protected readonly _mimeTypeMapping: { [key: number]: IOutputTransformContribution; }; + + constructor( + notebookEditor: INotebookEditor, + private readonly instantiationService: IInstantiationService + ) { + this._contributions = {}; + this._mimeTypeMapping = {}; + + let contributions = NotebookRegistry.getOutputTransformContributions(); + + for (const desc of contributions) { + try { + const contribution = this.instantiationService.createInstance(desc.ctor, notebookEditor); + this._contributions[desc.id] = contribution; + this._mimeTypeMapping[desc.kind] = contribution; + } catch (err) { + onUnexpectedError(err); + } + } + } + + renderNoop(output: IOutput, container: HTMLElement): IRenderOutput { + const contentNode = document.createElement('p'); + + contentNode.innerText = `No renderer could be found for output. It has the following output type: ${output.outputKind}`; + container.appendChild(contentNode); + return { + hasDynamicHeight: false + }; + } + + render(output: IOutput, container: HTMLElement, preferredMimeType: string | undefined): IRenderOutput { + let transform = this._mimeTypeMapping[output.outputKind]; + + if (transform) { + return transform.render(output, container, preferredMimeType); + } else { + return this.renderNoop(output, container); + } + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform.ts new file mode 100644 index 00000000000..569490c1987 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform.ts @@ -0,0 +1,394 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IRenderOutput, CellOutputKind, IErrorOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { registerOutputTransform } from 'vs/workbench/contrib/notebook/browser/notebookRegistry'; +import * as DOM from 'vs/base/browser/dom'; +import { RGBA, Color } from 'vs/base/common/color'; +import { ansiColorIdentifiers } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { INotebookEditor, IOutputTransformContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; + +class ErrorTransform implements IOutputTransformContribution { + constructor( + public editor: INotebookEditor, + @IThemeService private readonly themeService: IThemeService + ) { + } + + render(output: IErrorOutput, container: HTMLElement): IRenderOutput { + const header = document.createElement('div'); + const headerMessage = output.ename && output.evalue + ? `${output.ename}: ${output.evalue}` + : output.ename || output.evalue; + if (headerMessage) { + header.innerText = headerMessage; + container.appendChild(header); + } + const traceback = document.createElement('pre'); + DOM.addClasses(traceback, 'traceback'); + if (output.traceback) { + for (let j = 0; j < output.traceback.length; j++) { + traceback.appendChild(handleANSIOutput(output.traceback[j], this.themeService)); + } + } + container.appendChild(traceback); + return { + hasDynamicHeight: false + }; + } + + dispose(): void { + } +} + +registerOutputTransform('notebook.output.error', CellOutputKind.Error, ErrorTransform); + +/** + * @param text The content to stylize. + * @returns An {@link HTMLSpanElement} that contains the potentially stylized text. + */ +export function handleANSIOutput(text: string, themeService: IThemeService): HTMLSpanElement { + + const root: HTMLSpanElement = document.createElement('span'); + const textLength: number = text.length; + + let styleNames: string[] = []; + let customFgColor: RGBA | undefined; + let customBgColor: RGBA | undefined; + let currentPos: number = 0; + let buffer: string = ''; + + while (currentPos < textLength) { + + let sequenceFound: boolean = false; + + // Potentially an ANSI escape sequence. + // See http://ascii-table.com/ansi-escape-sequences.php & https://en.wikipedia.org/wiki/ANSI_escape_code + if (text.charCodeAt(currentPos) === 27 && text.charAt(currentPos + 1) === '[') { + + const startPos: number = currentPos; + currentPos += 2; // Ignore 'Esc[' as it's in every sequence. + + let ansiSequence: string = ''; + + while (currentPos < textLength) { + const char: string = text.charAt(currentPos); + ansiSequence += char; + + currentPos++; + + // Look for a known sequence terminating character. + if (char.match(/^[ABCDHIJKfhmpsu]$/)) { + sequenceFound = true; + break; + } + + } + + if (sequenceFound) { + + // Flush buffer with previous styles. + appendStylizedStringToContainer(root, buffer, styleNames, customFgColor, customBgColor); + + buffer = ''; + + /* + * Certain ranges that are matched here do not contain real graphics rendition sequences. For + * the sake of having a simpler expression, they have been included anyway. + */ + if (ansiSequence.match(/^(?:[34][0-8]|9[0-7]|10[0-7]|[013]|4|[34]9)(?:;[349][0-7]|10[0-7]|[013]|[245]|[34]9)?(?:;[012]?[0-9]?[0-9])*;?m$/)) { + + const styleCodes: number[] = ansiSequence.slice(0, -1) // Remove final 'm' character. + .split(';') // Separate style codes. + .filter(elem => elem !== '') // Filter empty elems as '34;m' -> ['34', '']. + .map(elem => parseInt(elem, 10)); // Convert to numbers. + + if (styleCodes[0] === 38 || styleCodes[0] === 48) { + // Advanced color code - can't be combined with formatting codes like simple colors can + // Ignores invalid colors and additional info beyond what is necessary + const colorType = (styleCodes[0] === 38) ? 'foreground' : 'background'; + + if (styleCodes[1] === 5) { + set8BitColor(styleCodes, colorType); + } else if (styleCodes[1] === 2) { + set24BitColor(styleCodes, colorType); + } + } else { + setBasicFormatters(styleCodes); + } + + } else { + // Unsupported sequence so simply hide it. + } + + } else { + currentPos = startPos; + } + } + + if (sequenceFound === false) { + buffer += text.charAt(currentPos); + currentPos++; + } + } + + // Flush remaining text buffer if not empty. + if (buffer) { + appendStylizedStringToContainer(root, buffer, styleNames, customFgColor, customBgColor); + } + + return root; + + /** + * Change the foreground or background color by clearing the current color + * and adding the new one. + * @param colorType If `'foreground'`, will change the foreground color, if + * `'background'`, will change the background color. + * @param color Color to change to. If `undefined` or not provided, + * will clear current color without adding a new one. + */ + function changeColor(colorType: 'foreground' | 'background', color?: RGBA | undefined): void { + if (colorType === 'foreground') { + customFgColor = color; + } else if (colorType === 'background') { + customBgColor = color; + } + styleNames = styleNames.filter(style => style !== `code-${colorType}-colored`); + if (color !== undefined) { + styleNames.push(`code-${colorType}-colored`); + } + } + + /** + * Calculate and set basic ANSI formatting. Supports bold, italic, underline, + * normal foreground and background colors, and bright foreground and + * background colors. Not to be used for codes containing advanced colors. + * Will ignore invalid codes. + * @param styleCodes Array of ANSI basic styling numbers, which will be + * applied in order. New colors and backgrounds clear old ones; new formatting + * does not. + * @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code } + */ + function setBasicFormatters(styleCodes: number[]): void { + for (let code of styleCodes) { + switch (code) { + case 0: { + styleNames = []; + customFgColor = undefined; + customBgColor = undefined; + break; + } + case 1: { + styleNames.push('code-bold'); + break; + } + case 3: { + styleNames.push('code-italic'); + break; + } + case 4: { + styleNames.push('code-underline'); + break; + } + case 39: { + changeColor('foreground', undefined); + break; + } + case 49: { + changeColor('background', undefined); + break; + } + default: { + setBasicColor(code); + break; + } + } + } + } + + /** + * Calculate and set styling for complicated 24-bit ANSI color codes. + * @param styleCodes Full list of integer codes that make up the full ANSI + * sequence, including the two defining codes and the three RGB codes. + * @param colorType If `'foreground'`, will set foreground color, if + * `'background'`, will set background color. + * @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit } + */ + function set24BitColor(styleCodes: number[], colorType: 'foreground' | 'background'): void { + if (styleCodes.length >= 5 && + styleCodes[2] >= 0 && styleCodes[2] <= 255 && + styleCodes[3] >= 0 && styleCodes[3] <= 255 && + styleCodes[4] >= 0 && styleCodes[4] <= 255) { + const customColor = new RGBA(styleCodes[2], styleCodes[3], styleCodes[4]); + changeColor(colorType, customColor); + } + } + + /** + * Calculate and set styling for advanced 8-bit ANSI color codes. + * @param styleCodes Full list of integer codes that make up the ANSI + * sequence, including the two defining codes and the one color code. + * @param colorType If `'foreground'`, will set foreground color, if + * `'background'`, will set background color. + * @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit } + */ + function set8BitColor(styleCodes: number[], colorType: 'foreground' | 'background'): void { + let colorNumber = styleCodes[2]; + const color = calcANSI8bitColor(colorNumber); + + if (color) { + changeColor(colorType, color); + } else if (colorNumber >= 0 && colorNumber <= 15) { + // Need to map to one of the four basic color ranges (30-37, 90-97, 40-47, 100-107) + colorNumber += 30; + if (colorNumber >= 38) { + // Bright colors + colorNumber += 52; + } + if (colorType === 'background') { + colorNumber += 10; + } + setBasicColor(colorNumber); + } + } + + /** + * Calculate and set styling for basic bright and dark ANSI color codes. Uses + * theme colors if available. Automatically distinguishes between foreground + * and background colors; does not support color-clearing codes 39 and 49. + * @param styleCode Integer color code on one of the following ranges: + * [30-37, 90-97, 40-47, 100-107]. If not on one of these ranges, will do + * nothing. + */ + function setBasicColor(styleCode: number): void { + const theme = themeService.getColorTheme(); + let colorType: 'foreground' | 'background' | undefined; + let colorIndex: number | undefined; + + if (styleCode >= 30 && styleCode <= 37) { + colorIndex = styleCode - 30; + colorType = 'foreground'; + } else if (styleCode >= 90 && styleCode <= 97) { + colorIndex = (styleCode - 90) + 8; // High-intensity (bright) + colorType = 'foreground'; + } else if (styleCode >= 40 && styleCode <= 47) { + colorIndex = styleCode - 40; + colorType = 'background'; + } else if (styleCode >= 100 && styleCode <= 107) { + colorIndex = (styleCode - 100) + 8; // High-intensity (bright) + colorType = 'background'; + } + + if (colorIndex !== undefined && colorType) { + const colorName = ansiColorIdentifiers[colorIndex]; + const color = theme.getColor(colorName); + if (color) { + changeColor(colorType, color.rgba); + } + } + } +} + +/** + * @param root The {@link HTMLElement} to append the content to. + * @param stringContent The text content to be appended. + * @param cssClasses The list of CSS styles to apply to the text content. + * @param linkDetector The {@link LinkDetector} responsible for generating links from {@param stringContent}. + * @param customTextColor If provided, will apply custom color with inline style. + * @param customBackgroundColor If provided, will apply custom color with inline style. + */ +export function appendStylizedStringToContainer( + root: HTMLElement, + stringContent: string, + cssClasses: string[], + customTextColor?: RGBA, + customBackgroundColor?: RGBA +): void { + if (!root || !stringContent) { + return; + } + + const container = linkify(stringContent, true); + container.className = cssClasses.join(' '); + if (customTextColor) { + container.style.color = + Color.Format.CSS.formatRGB(new Color(customTextColor)); + } + if (customBackgroundColor) { + container.style.backgroundColor = + Color.Format.CSS.formatRGB(new Color(customBackgroundColor)); + } + + root.appendChild(container); +} + +function linkify(text: string, splitLines?: boolean): HTMLElement { + if (splitLines) { + const lines = text.split('\n'); + for (let i = 0; i < lines.length - 1; i++) { + lines[i] = lines[i] + '\n'; + } + if (!lines[lines.length - 1]) { + // Remove the last element ('') that split added. + lines.pop(); + } + const elements = lines.map(line => linkify(line)); + if (elements.length === 1) { + // Do not wrap single line with extra span. + return elements[0]; + } + const container = document.createElement('span'); + elements.forEach(e => container.appendChild(e)); + return container; + } + + const container = document.createElement('span'); + container.appendChild(document.createTextNode(text)); + return container; +} + + + +/** + * Calculate the color from the color set defined in the ANSI 8-bit standard. + * Standard and high intensity colors are not defined in the standard as specific + * colors, so these and invalid colors return `undefined`. + * @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit } for info. + * @param colorNumber The number (ranging from 16 to 255) referring to the color + * desired. + */ +export function calcANSI8bitColor(colorNumber: number): RGBA | undefined { + if (colorNumber % 1 !== 0) { + // Should be integer + // {{SQL CARBON EDIT}} @todo anthonydresser 4/12/19 this is necessary because we don't use strict null checks + return undefined; + } if (colorNumber >= 16 && colorNumber <= 231) { + // Converts to one of 216 RGB colors + colorNumber -= 16; + + let blue: number = colorNumber % 6; + colorNumber = (colorNumber - blue) / 6; + let green: number = colorNumber % 6; + colorNumber = (colorNumber - green) / 6; + let red: number = colorNumber; + + // red, green, blue now range on [0, 5], need to map to [0,255] + const convFactor: number = 255 / 5; + blue = Math.round(blue * convFactor); + green = Math.round(green * convFactor); + red = Math.round(red * convFactor); + + return new RGBA(red, green, blue); + } else if (colorNumber >= 232 && colorNumber <= 255) { + // Converts to a grayscale value + colorNumber -= 232; + const colorLevel: number = Math.round(colorNumber / 23 * 255); + return new RGBA(colorLevel, colorLevel, colorLevel); + } else { + // {{SQL CARBON EDIT}} @todo anthonydresser 4/12/19 this is necessary because we don't use strict null checks + return undefined; + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts new file mode 100644 index 00000000000..49227bc1356 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts @@ -0,0 +1,249 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IRenderOutput, CellOutputKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { registerOutputTransform } from 'vs/workbench/contrib/notebook/browser/notebookRegistry'; +import * as DOM from 'vs/base/browser/dom'; +import { INotebookEditor, IOutputTransformContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { isArray } from 'vs/base/common/types'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { URI } from 'vs/base/common/uri'; +import { MarkdownRenderer } from 'vs/workbench/contrib/notebook/browser/view/renderers/mdRenderer'; + +class RichRenderer implements IOutputTransformContribution { + private _mdRenderer: MarkdownRenderer; + private _richMimeTypeRenderers = new Map IRenderOutput>(); + + constructor( + public notebookEditor: INotebookEditor, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IModelService private readonly modelService: IModelService, + @IModeService private readonly modeService: IModeService + ) { + this._mdRenderer = instantiationService.createInstance(MarkdownRenderer); + this._richMimeTypeRenderers.set('application/json', this.renderJSON.bind(this)); + this._richMimeTypeRenderers.set('application/javascript', this.renderJavaScript.bind(this)); + this._richMimeTypeRenderers.set('text/html', this.renderHTML.bind(this)); + this._richMimeTypeRenderers.set('image/svg+xml', this.renderSVG.bind(this)); + this._richMimeTypeRenderers.set('text/markdown', this.renderMarkdown.bind(this)); + this._richMimeTypeRenderers.set('image/png', this.renderPNG.bind(this)); + this._richMimeTypeRenderers.set('image/jpeg', this.renderJavaScript.bind(this)); + this._richMimeTypeRenderers.set('text/plain', this.renderPlainText.bind(this)); + this._richMimeTypeRenderers.set('text/x-javascript', this.renderCode.bind(this)); + } + + render(output: any, container: HTMLElement, preferredMimeType: string | undefined): IRenderOutput { + if (!output.data) { + const contentNode = document.createElement('p'); + contentNode.innerText = `No data could be found for output.`; + container.appendChild(contentNode); + + return { + hasDynamicHeight: false + }; + } + + if (!preferredMimeType || !this._richMimeTypeRenderers.has(preferredMimeType)) { + const contentNode = document.createElement('p'); + let mimeTypes = []; + for (const property in output.data) { + mimeTypes.push(property); + } + + let mimeTypesMessage = mimeTypes.join(', '); + + contentNode.innerText = `No renderer could be found for output. It has the following MIME types: ${mimeTypesMessage}`; + container.appendChild(contentNode); + + return { + hasDynamicHeight: false + }; + } + + let renderer = this._richMimeTypeRenderers.get(preferredMimeType); + return renderer!(output, container); + } + + renderJSON(output: any, container: HTMLElement) { + let data = output.data['application/json']; + let str = JSON.stringify(data, null, '\t'); + + const editor = this.instantiationService.createInstance(CodeEditorWidget, container, { + ...getOutputSimpleEditorOptions(), + dimension: { + width: 0, + height: 0 + } + }, { + isSimpleWidget: true + }); + + let mode = this.modeService.create('json'); + let resource = URI.parse(`notebook-output-${Date.now()}.json`); + const textModel = this.modelService.createModel(str, mode, resource, false); + editor.setModel(textModel); + + let width = this.notebookEditor.getLayoutInfo().width; + let fontInfo = this.notebookEditor.getLayoutInfo().fontInfo; + let height = Math.min(textModel.getLineCount(), 16) * (fontInfo.lineHeight || 18); + + editor.layout({ + height, + width + }); + + container.style.height = `${height + 16}px`; + + return { + hasDynamicHeight: true + }; + } + + renderCode(output: any, container: HTMLElement) { + let data = output.data['text/x-javascript']; + let str = isArray(data) ? data.join('') : data; + + const editor = this.instantiationService.createInstance(CodeEditorWidget, container, { + ...getOutputSimpleEditorOptions(), + dimension: { + width: 0, + height: 0 + } + }, { + isSimpleWidget: true + }); + + let mode = this.modeService.create('javascript'); + let resource = URI.parse(`notebook-output-${Date.now()}.js`); + const textModel = this.modelService.createModel(str, mode, resource, false); + editor.setModel(textModel); + + let width = this.notebookEditor.getLayoutInfo().width; + let fontInfo = this.notebookEditor.getLayoutInfo().fontInfo; + let height = Math.min(textModel.getLineCount(), 16) * (fontInfo.lineHeight || 18); + + editor.layout({ + height, + width + }); + + container.style.height = `${height + 16}px`; + + return { + hasDynamicHeight: true + }; + } + + renderJavaScript(output: any, container: HTMLElement) { + let data = output.data['application/javascript']; + let str = isArray(data) ? data.join('') : data; + let scriptVal = ``; + return { + shadowContent: scriptVal, + hasDynamicHeight: false + }; + } + + renderHTML(output: any, container: HTMLElement) { + let data = output.data['text/html']; + let str = isArray(data) ? data.join('') : data; + return { + shadowContent: str, + hasDynamicHeight: false + }; + + } + + renderSVG(output: any, container: HTMLElement) { + let data = output.data['image/svg+xml']; + let str = isArray(data) ? data.join('') : data; + return { + shadowContent: str, + hasDynamicHeight: false + }; + } + + renderMarkdown(output: any, container: HTMLElement) { + let data = output.data['text/markdown']; + const str = isArray(data) ? data.join('') : data; + const mdOutput = document.createElement('div'); + mdOutput.appendChild(this._mdRenderer.render({ value: str, isTrusted: false, supportThemeIcons: true }).element); + container.appendChild(mdOutput); + + return { + hasDynamicHeight: true + }; + } + + renderPNG(output: any, container: HTMLElement) { + const image = document.createElement('img'); + image.src = `data:image/png;base64,${output.data['image/png']}`; + const display = document.createElement('div'); + DOM.addClasses(display, 'display'); + display.appendChild(image); + container.appendChild(display); + return { + hasDynamicHeight: true + }; + + } + + renderJPEG(output: any, container: HTMLElement) { + const image = document.createElement('img'); + image.src = `data:image/jpeg;base64,${output.data['image/jpeg']}`; + const display = document.createElement('div'); + DOM.addClasses(display, 'display'); + display.appendChild(image); + container.appendChild(display); + return { + hasDynamicHeight: true + }; + } + + renderPlainText(output: any, container: HTMLElement) { + let data = output.data['text/plain']; + let str = isArray(data) ? data.join('') : data; + const contentNode = document.createElement('p'); + contentNode.innerText = str; + container.appendChild(contentNode); + + return { + hasDynamicHeight: false + }; + } + + dispose(): void { + } +} + +registerOutputTransform('notebook.output.rich', CellOutputKind.Rich, RichRenderer); + + +export function getOutputSimpleEditorOptions(): IEditorOptions { + return { + readOnly: true, + wordWrap: 'on', + overviewRulerLanes: 0, + glyphMargin: false, + selectOnLineNumbers: false, + hideCursorInOverviewRuler: true, + selectionHighlight: false, + lineDecorationsWidth: 0, + overviewRulerBorder: false, + scrollBeyondLastLine: false, + renderLineHighlight: 'none', + minimap: { + enabled: false + }, + lineNumbers: 'off', + scrollbar: { + alwaysConsumeMouseWheel: false + } + }; +} diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/streamTransform.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/streamTransform.ts new file mode 100644 index 00000000000..32ed484dd50 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/streamTransform.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. + *--------------------------------------------------------------------------------------------*/ + +import { IRenderOutput, CellOutputKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { registerOutputTransform } from 'vs/workbench/contrib/notebook/browser/notebookRegistry'; +import { INotebookEditor, IOutputTransformContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; + +class StreamRenderer implements IOutputTransformContribution { + constructor( + editor: INotebookEditor + ) { + } + + render(output: any, container: HTMLElement): IRenderOutput { + const contentNode = document.createElement('p'); + contentNode.innerText = output.text; + container.appendChild(contentNode); + return { + hasDynamicHeight: false + }; + + } + + dispose(): void { + } +} + +registerOutputTransform('notebook.output.stream', CellOutputKind.Text, StreamRenderer); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts new file mode 100644 index 00000000000..7733cf12ff2 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -0,0 +1,490 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as DOM from 'vs/base/browser/dom'; +import { Disposable } from 'vs/base/common/lifecycle'; +import * as path from 'vs/base/common/path'; +import { URI } from 'vs/base/common/uri'; +import * as UUID from 'vs/base/common/uuid'; +import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService'; +import { IOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IWebviewService, WebviewElement } from 'vs/workbench/contrib/webview/browser/webview'; +import { WebviewResourceScheme } from 'vs/workbench/contrib/webview/common/resourceLoader'; +import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; +import { CELL_MARGIN, CELL_RUN_GUTTER } from 'vs/workbench/contrib/notebook/browser/constants'; +import { Emitter, Event } from 'vs/base/common/event'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; + +export interface IDimentionMessage { + __vscode_notebook_message: boolean; + type: 'dimension'; + id: string; + data: DOM.Dimension; +} + +export interface IWheelMessage { + __vscode_notebook_message: boolean; + type: 'did-scroll-wheel'; + payload: any; +} + + +export interface IScrollAckMessage { + __vscode_notebook_message: boolean; + type: 'scroll-ack'; + data: { top: number }; + version: number; +} + +export interface IClearMessage { + type: 'clear'; +} + +export interface ICreationRequestMessage { + type: 'html'; + content: string; + id: string; + outputId: string; + top: number; + left: number; +} + +export interface IContentWidgetTopRequest { + id: string; + top: number; + left: number; +} + +export interface IViewScrollTopRequestMessage { + type: 'view-scroll'; + top?: number; + widgets: IContentWidgetTopRequest[]; + version: number; +} + +export interface IScrollRequestMessage { + type: 'scroll'; + id: string; + top: number; + widgetTop?: number; + version: number; +} + +export interface IUpdatePreloadResourceMessage { + type: 'preload'; + resources: string[]; +} + +type IMessage = IDimentionMessage | IScrollAckMessage | IWheelMessage; + +let version = 0; +export class BackLayerWebView extends Disposable { + element: HTMLElement; + webview: WebviewElement; + insetMapping: Map = new Map(); + reversedInsetMapping: Map = new Map(); + preloadsCache: Map = new Map(); + localResourceRootsCache: URI[] | undefined = undefined; + rendererRootsCache: URI[] = []; + private readonly _onMessage = this._register(new Emitter()); + public readonly onMessage: Event = this._onMessage.event; + + + constructor( + public notebookEditor: INotebookEditor, + @IWebviewService webviewService: IWebviewService, + @IOpenerService openerService: IOpenerService, + @INotebookService private readonly notebookService: INotebookService, + ) { + super(); + this.element = document.createElement('div'); + + this.element.style.width = `calc(100% - ${CELL_MARGIN * 2}px)`; + this.element.style.height = '1400px'; + this.element.style.position = 'absolute'; + this.element.style.margin = `0px 0 0px ${CELL_MARGIN}px`; + + const pathsPath = getPathFromAmdModule(require, 'vs/loader.js'); + const loader = URI.file(pathsPath).with({ scheme: WebviewResourceScheme }); + + const outputNodePadding = 8; + let content = /* html */` + + + + + + + + +
+
+ + +`; + + this.webview = this._createInset(webviewService, content); + this.webview.mountTo(this.element); + + this._register(this.webview.onDidClickLink(link => { + openerService.open(link, { fromUserGesture: true }); + })); + + this._register(this.webview.onMessage((data: IMessage) => { + if (data.__vscode_notebook_message) { + if (data.type === 'dimension') { + let output = this.reversedInsetMapping.get(data.id); + + if (!output) { + return; + } + + let cell = this.insetMapping.get(output)!.cell; + let height = data.data.height; + let outputHeight = height; + + if (cell) { + let outputIndex = cell.outputs.indexOf(output); + cell.updateOutputHeight(outputIndex, outputHeight); + this.notebookEditor.layoutNotebookCell(cell, cell.layoutInfo.totalHeight); + } + } else if (data.type === 'scroll-ack') { + // const date = new Date(); + // const top = data.data.top; + // console.log('ack top ', top, ' version: ', data.version, ' - ', date.getMinutes() + ':' + date.getSeconds() + ':' + date.getMilliseconds()); + } else if (data.type === 'did-scroll-wheel') { + this.notebookEditor.triggerScroll(data.payload); + } + return; + } + + this._onMessage.fire(data); + })); + } + + private _createInset(webviewService: IWebviewService, content: string) { + const rootPath = URI.file(path.dirname(getPathFromAmdModule(require, ''))); + this.localResourceRootsCache = [...this.notebookService.getNotebookProviderResourceRoots(), rootPath]; + const webview = webviewService.createWebviewElement('' + UUID.generateUuid(), { + enableFindWidget: false, + }, { + allowMultipleAPIAcquire: true, + allowScripts: true, + localResourceRoots: this.localResourceRootsCache + }); + webview.html = content; + return webview; + } + + shouldUpdateInset(cell: CodeCellViewModel, output: IOutput, cellTop: number) { + let outputCache = this.insetMapping.get(output)!; + let outputIndex = cell.outputs.indexOf(output); + let outputOffset = cellTop + cell.getOutputOffset(outputIndex); + + if (outputOffset === outputCache.cacheOffset) { + return false; + } + + return true; + } + + updateViewScrollTop(top: number, items: { cell: CodeCellViewModel, output: IOutput, cellTop: number }[]) { + let widgets: IContentWidgetTopRequest[] = items.map(item => { + let outputCache = this.insetMapping.get(item.output)!; + let id = outputCache.outputId; + let outputIndex = item.cell.outputs.indexOf(item.output); + + let outputOffset = item.cellTop + item.cell.getOutputOffset(outputIndex); + outputCache.cacheOffset = outputOffset; + + return { + id: id, + top: outputOffset, + left: CELL_RUN_GUTTER + }; + }); + + let message: IViewScrollTopRequestMessage = { + top, + type: 'view-scroll', + version: version++, + widgets: widgets + }; + + this.webview.sendMessage(message); + } + + createInset(cell: CodeCellViewModel, output: IOutput, cellTop: number, offset: number, shadowContent: string, preloads: Set) { + this.updateRendererPreloads(preloads); + let initialTop = cellTop + offset; + let outputId = UUID.generateUuid(); + + let message: ICreationRequestMessage = { + type: 'html', + content: shadowContent, + id: cell.id, + outputId: outputId, + top: initialTop, + left: CELL_RUN_GUTTER + }; + + this.webview.sendMessage(message); + this.insetMapping.set(output, { outputId: outputId, cell: cell, cacheOffset: initialTop }); + this.reversedInsetMapping.set(outputId, output); + } + + removeInset(output: IOutput) { + let outputCache = this.insetMapping.get(output); + if (!outputCache) { + return; + } + + let id = outputCache.outputId; + + this.webview.sendMessage({ + type: 'clearOutput', + id: id + }); + this.insetMapping.delete(output); + this.reversedInsetMapping.delete(id); + } + + clearInsets() { + this.webview.sendMessage({ + type: 'clear' + }); + + this.insetMapping = new Map(); + this.reversedInsetMapping = new Map(); + } + + updateRendererPreloads(preloads: Set) { + let resources: string[] = []; + let extensionLocations: URI[] = []; + preloads.forEach(preload => { + let rendererInfo = this.notebookService.getRendererInfo(preload); + + if (rendererInfo) { + let preloadResources = rendererInfo.preloads.map(preloadResource => preloadResource.with({ scheme: WebviewResourceScheme })); + extensionLocations.push(rendererInfo.extensionLocation); + preloadResources.forEach(e => { + if (!this.preloadsCache.has(e.toString())) { + resources.push(e.toString()); + this.preloadsCache.set(e.toString(), true); + } + }); + } + }); + + this.rendererRootsCache = extensionLocations; + const mixedResourceRoots = [...(this.localResourceRootsCache || []), ...this.rendererRootsCache]; + + this.webview.contentOptions = { + allowMultipleAPIAcquire: true, + allowScripts: true, + enableCommandUris: true, + localResourceRoots: mixedResourceRoots + }; + + let message: IUpdatePreloadResourceMessage = { + type: 'preload', + resources: resources + }; + + this.webview.sendMessage(message); + } + + clearPreloadsCache() { + this.preloadsCache.clear(); + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellMenus.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellMenus.ts new file mode 100644 index 00000000000..324d509dafa --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellMenus.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IAction } from 'vs/base/common/actions'; +import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; + +export class CellMenus { + constructor( + @IMenuService private readonly menuService: IMenuService, + @IContextMenuService private readonly contextMenuService: IContextMenuService + ) { } + + getCellTitleMenu(contextKeyService: IContextKeyService): IMenu { + return this.getMenu(MenuId.NotebookCellTitle, contextKeyService); + } + + private getMenu(menuId: MenuId, contextKeyService: IContextKeyService): IMenu { + const menu = this.menuService.createMenu(menuId, contextKeyService); + + const primary: IAction[] = []; + const secondary: IAction[] = []; + const result = { primary, secondary }; + + createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, this.contextMenuService, g => /^inline/.test(g)); + + return menu; + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts new file mode 100644 index 00000000000..3a108786792 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -0,0 +1,508 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { getZoomLevel } from 'vs/base/browser/browser'; +import * as DOM from 'vs/base/browser/dom'; +import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; +import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; +import { IAction, ActionRunner } from 'vs/base/common/actions'; +import { escape } from 'vs/base/common/strings'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { deepClone } from 'vs/base/common/objects'; +import 'vs/css!vs/workbench/contrib/notebook/browser/notebook'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; +import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenu, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING, NOTEBOOK_CELL_EDITABLE_CONTEXT_KEY, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE_CONTEXT_KEY, NOTEBOOK_CELL_TYPE_CONTEXT_KEY, NOTEBOOK_CELL_RUN_STATE_CONTEXT_KEY, NOTEBOOK_VIEW_TYPE, BOTTOM_CELL_TOOLBAR_HEIGHT } from 'vs/workbench/contrib/notebook/browser/constants'; +import { ExecuteCellAction, INotebookCellActionContext, CancelCellAction, InsertCodeCellAction, InsertMarkdownCellAction } from 'vs/workbench/contrib/notebook/browser/contrib/notebookActions'; +import { BaseCellRenderTemplate, CellEditState, CellRunState, CodeCellRenderTemplate, ICellViewModel, INotebookEditor, MarkdownCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellMenus } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellMenus'; +import { CodeCell } from 'vs/workbench/contrib/notebook/browser/view/renderers/codeCell'; +import { StatefullMarkdownCell } from 'vs/workbench/contrib/notebook/browser/view/renderers/markdownCell'; +import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; +import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; +import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; +import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { renderCodicons } from 'vs/base/common/codicons'; + +const $ = DOM.$; + +export class NotebookCellListDelegate implements IListVirtualDelegate { + private _lineHeight: number; + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService + ) { + const editorOptions = this.configurationService.getValue('editor'); + this._lineHeight = BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel()).lineHeight; + } + + getHeight(element: CellViewModel): number { + return element.getHeight(this._lineHeight); + } + + hasDynamicHeight(element: CellViewModel): boolean { + return element.hasDynamicHeight(); + } + + getTemplateId(element: CellViewModel): string { + if (element.cellKind === CellKind.Markdown) { + return MarkdownCellRenderer.TEMPLATE_ID; + } else { + return CodeCellRenderer.TEMPLATE_ID; + } + } +} + +export class CodiconActionViewItem extends ContextAwareMenuEntryActionViewItem { + constructor( + readonly _action: MenuItemAction, + _keybindingService: IKeybindingService, + _notificationService: INotificationService, + _contextMenuService: IContextMenuService + ) { + super(_action, _keybindingService, _notificationService, _contextMenuService); + } + updateLabel(): void { + if (this.options.label && this.label) { + this.label.innerHTML = renderCodicons(this._commandAction.label ?? ''); + } + } +} + +abstract class AbstractCellRenderer { + protected editorOptions: IEditorOptions; + private actionRunner = new ActionRunner(); + + constructor( + protected readonly instantiationService: IInstantiationService, + protected readonly notebookEditor: INotebookEditor, + protected readonly contextMenuService: IContextMenuService, + private readonly configurationService: IConfigurationService, + private readonly keybindingService: IKeybindingService, + private readonly notificationService: INotificationService, + protected readonly contextKeyService: IContextKeyService, + language: string, + ) { + const editorOptions = deepClone(this.configurationService.getValue('editor', { overrideIdentifier: language })); + this.editorOptions = { + ...editorOptions, + padding: { + top: EDITOR_TOP_PADDING, + bottom: EDITOR_BOTTOM_PADDING + }, + scrollBeyondLastLine: false, + scrollbar: { + verticalScrollbarSize: 14, + horizontal: 'auto', + useShadows: true, + verticalHasArrows: false, + horizontalHasArrows: false, + alwaysConsumeMouseWheel: false + }, + renderLineHighlightOnlyWhenFocus: true, + overviewRulerLanes: 0, + selectOnLineNumbers: false, + lineNumbers: 'off', + lineDecorationsWidth: 0, + glyphMargin: false, + fixedOverflowWidgets: false, + minimap: { enabled: false }, + }; + } + + protected createBottomCellToolbar(container: HTMLElement): ToolBar { + const toolbar = new ToolBar(container, this.contextMenuService, { + actionViewItemProvider: action => { + if (action instanceof MenuItemAction) { + const item = new CodiconActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService); + return item; + } + + return undefined; + } + }); + + toolbar.getContainer().style.height = `${BOTTOM_CELL_TOOLBAR_HEIGHT}px`; + return toolbar; + } + + protected setupBetweenCellToolbarActions(element: CodeCellViewModel | MarkdownCellViewModel, templateData: BaseCellRenderTemplate, disposables: DisposableStore, context: INotebookCellActionContext): void { + const container = templateData.bottomCellContainer; + container.innerHTML = ''; + container.style.height = `${BOTTOM_CELL_TOOLBAR_HEIGHT}px`; + + DOM.append(container, $('.seperator')); + const addCodeCell = DOM.append(container, $('span.button')); + addCodeCell.innerHTML = renderCodicons(escape(`$(add) Code `)); + const insertCellBelow = this.instantiationService.createInstance(InsertCodeCellAction); + + disposables.add(DOM.addDisposableListener(addCodeCell, DOM.EventType.CLICK, () => { + this.actionRunner.run(insertCellBelow, context); + })); + + DOM.append(container, $('.seperator-short')); + const addMarkdownCell = DOM.append(container, $('span.button')); + addMarkdownCell.innerHTML = renderCodicons(escape('$(add) Markdown ')); + const insertMarkdownBelow = this.instantiationService.createInstance(InsertMarkdownCellAction); + disposables.add(DOM.addDisposableListener(addMarkdownCell, DOM.EventType.CLICK, () => { + this.actionRunner.run(insertMarkdownBelow, context); + })); + + DOM.append(container, $('.seperator')); + + if (element instanceof CodeCellViewModel) { + const bottomToolbarOffset = element.layoutInfo.bottomToolbarOffset; + container.style.top = `${bottomToolbarOffset}px`; + + disposables.add(element.onDidChangeLayout(() => { + const bottomToolbarOffset = element.layoutInfo.bottomToolbarOffset; + container.style.top = `${bottomToolbarOffset}px`; + })); + } else { + container.style.position = 'static'; + container.style.height = '22px'; + } + + } + + protected createToolbar(container: HTMLElement): ToolBar { + const toolbar = new ToolBar(container, this.contextMenuService, { + actionViewItemProvider: action => { + if (action instanceof MenuItemAction) { + const item = new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService); + return item; + } + + return undefined; + } + }); + + toolbar.getContainer().style.height = `${EDITOR_TOOLBAR_HEIGHT}px`; + + return toolbar; + } + + private getCellToolbarActions(menu: IMenu): IAction[] { + const actions: IAction[] = []; + for (let [, menuActions] of menu.getActions({ shouldForwardArgs: true })) { + actions.push(...menuActions); + } + + return actions; + } + + protected setupCellToolbarActions(scopedContextKeyService: IContextKeyService, templateData: BaseCellRenderTemplate, disposables: DisposableStore): void { + const cellMenu = this.instantiationService.createInstance(CellMenus); + const menu = disposables.add(cellMenu.getCellTitleMenu(scopedContextKeyService)); + + const updateActions = () => { + const actions = this.getCellToolbarActions(menu); + + templateData.toolbar.setActions(actions)(); + + if (templateData.focusIndicator) { + if (actions.length) { + templateData.focusIndicator.style.top = `${EDITOR_TOOLBAR_HEIGHT + EDITOR_TOP_MARGIN}px`; + } else { + templateData.focusIndicator.style.top = `${EDITOR_TOP_MARGIN}px`; + } + } + }; + + updateActions(); + disposables.add(menu.onDidChange(() => { + updateActions(); + })); + } +} + +export class MarkdownCellRenderer extends AbstractCellRenderer implements IListRenderer { + static readonly TEMPLATE_ID = 'markdown_cell'; + private disposables: Map = new Map(); + + constructor( + contextKeyService: IContextKeyService, + notehookEditor: INotebookEditor, + @IInstantiationService instantiationService: IInstantiationService, + @IConfigurationService configurationService: IConfigurationService, + @IContextMenuService contextMenuService: IContextMenuService, + @IKeybindingService keybindingService: IKeybindingService, + @INotificationService notificationService: INotificationService, + ) { + super(instantiationService, notehookEditor, contextMenuService, configurationService, keybindingService, notificationService, contextKeyService, 'markdown'); + } + + get templateId() { + return MarkdownCellRenderer.TEMPLATE_ID; + } + + renderTemplate(container: HTMLElement): MarkdownCellRenderTemplate { + const codeInnerContent = document.createElement('div'); + DOM.addClasses(codeInnerContent, 'cell', 'code'); + const editingContainer = DOM.append(codeInnerContent, $('.markdown-editor-container')); + editingContainer.style.display = 'none'; + + const disposables = new DisposableStore(); + const toolbar = this.createToolbar(container); + disposables.add(toolbar); + + container.appendChild(codeInnerContent); + + const innerContent = document.createElement('div'); + DOM.addClasses(innerContent, 'cell', 'markdown'); + container.appendChild(innerContent); + + const focusIndicator = DOM.append(container, DOM.$('.notebook-cell-focus-indicator')); + const bottomCellContainer = DOM.append(container, $('.cell-bottom-toolbar-container')); + + return { + container, + cellContainer: innerContent, + editingContainer, + focusIndicator, + disposables, + toolbar, + bottomCellContainer: bottomCellContainer, + toJSON: () => { return {}; } + }; + } + + renderElement(element: MarkdownCellViewModel, index: number, templateData: MarkdownCellRenderTemplate, height: number | undefined): void { + templateData.editingContainer!.style.display = 'none'; + templateData.cellContainer.innerHTML = ''; + let renderedHTML = element.getHTML(); + if (renderedHTML) { + templateData.cellContainer.appendChild(renderedHTML); + } + + if (height) { + this.disposables.get(element)?.clear(); + if (!this.disposables.has(element)) { + this.disposables.set(element, new DisposableStore()); + } + const elementDisposable = this.disposables.get(element)!; + + elementDisposable.add(new StatefullMarkdownCell(this.notebookEditor, element, templateData, this.editorOptions, this.instantiationService)); + + const contextKeyService = this.contextKeyService.createScoped(templateData.container); + contextKeyService.createKey(NOTEBOOK_CELL_TYPE_CONTEXT_KEY, 'markdown'); + contextKeyService.createKey(NOTEBOOK_VIEW_TYPE, element.viewType); + + const cellEditableKey = contextKeyService.createKey(NOTEBOOK_CELL_EDITABLE_CONTEXT_KEY, !!(element.metadata?.editable)); + const updateForMetadata = () => { + const metadata = element.getEvaluatedMetadata(this.notebookEditor.viewModel!.notebookDocument.metadata); + cellEditableKey.set(!!metadata.editable); + }; + + updateForMetadata(); + elementDisposable.add(element.onDidChangeMetadata(() => { + updateForMetadata(); + })); + + const editModeKey = contextKeyService.createKey(NOTEBOOK_CELL_MARKDOWN_EDIT_MODE_CONTEXT_KEY, element.editState === CellEditState.Editing); + elementDisposable.add(element.onDidChangeCellEditState(() => { + editModeKey.set(element.editState === CellEditState.Editing); + })); + + this.setupCellToolbarActions(contextKeyService, templateData, elementDisposable); + + const toolbarContext = { + cell: element, + notebookEditor: this.notebookEditor, + $mid: 12 + }; + templateData.toolbar.context = toolbarContext; + + this.setupBetweenCellToolbarActions(element, templateData, elementDisposable, toolbarContext); + element.totalHeight = height; + } + + } + + disposeTemplate(templateData: MarkdownCellRenderTemplate): void { + templateData.disposables.clear(); + } + + disposeElement(element: ICellViewModel, index: number, templateData: MarkdownCellRenderTemplate, height: number | undefined): void { + if (height) { + this.disposables.get(element)?.clear(); + } + } +} + +export class CodeCellRenderer extends AbstractCellRenderer implements IListRenderer { + static readonly TEMPLATE_ID = 'code_cell'; + private disposables: Map = new Map(); + + constructor( + protected notebookEditor: INotebookEditor, + protected contextKeyService: IContextKeyService, + private renderedEditors: Map, + @IContextMenuService contextMenuService: IContextMenuService, + @IConfigurationService configurationService: IConfigurationService, + @IInstantiationService instantiationService: IInstantiationService, + @IKeybindingService keybindingService: IKeybindingService, + @INotificationService notificationService: INotificationService, + ) { + super(instantiationService, notebookEditor, contextMenuService, configurationService, keybindingService, notificationService, contextKeyService, 'python'); + } + + get templateId() { + return CodeCellRenderer.TEMPLATE_ID; + } + + renderTemplate(container: HTMLElement): CodeCellRenderTemplate { + const disposables = new DisposableStore(); + const toolbar = this.createToolbar(container); + disposables.add(toolbar); + + const cellContainer = DOM.append(container, $('.cell.code')); + const runButtonContainer = DOM.append(cellContainer, $('.run-button-container')); + const runToolbar = this.createToolbar(runButtonContainer); + disposables.add(runToolbar); + + const bottomCellContainer = DOM.append(container, $('.cell-bottom-toolbar-container')); + + const executionOrderLabel = DOM.append(runButtonContainer, $('div.execution-count-label')); + + const editorContainer = DOM.append(cellContainer, $('.cell-editor-container')); + const editor = this.instantiationService.createInstance(CodeEditorWidget, editorContainer, { + ...this.editorOptions, + dimension: { + width: 0, + height: 0 + } + }, {}); + + const focusIndicator = DOM.append(container, DOM.$('.notebook-cell-focus-indicator')); + + const outputContainer = document.createElement('div'); + DOM.addClasses(outputContainer, 'output'); + container.appendChild(outputContainer); + + const progressBar = new ProgressBar(editorContainer); + progressBar.hide(); + disposables.add(progressBar); + + return { + container, + cellContainer, + editorContainer, + progressBar, + focusIndicator, + toolbar, + runToolbar, + runButtonContainer, + executionOrderLabel, + outputContainer, + editor, + disposables, + bottomCellContainer: bottomCellContainer, + toJSON: () => { return {}; } + }; + } + + private updateForRunState(element: CodeCellViewModel, templateData: CodeCellRenderTemplate, runStateKey: IContextKey): void { + runStateKey.set(CellRunState[element.runState]); + if (element.runState === CellRunState.Running) { + templateData.progressBar.infinite().show(500); + + templateData.runToolbar.setActions([ + this.instantiationService.createInstance(CancelCellAction) + ])(); + } else { + templateData.progressBar.hide(); + + templateData.runToolbar.setActions([ + this.instantiationService.createInstance(ExecuteCellAction) + ])(); + } + } + + renderElement(element: CodeCellViewModel, index: number, templateData: CodeCellRenderTemplate, height: number | undefined): void { + if (height === undefined) { + return; + } + + templateData.outputContainer.innerHTML = ''; + + this.disposables.get(element)?.clear(); + if (!this.disposables.has(element)) { + this.disposables.set(element, new DisposableStore()); + } + + const elementDisposable = this.disposables.get(element)!; + + elementDisposable.add(this.instantiationService.createInstance(CodeCell, this.notebookEditor, element, templateData)); + this.renderedEditors.set(element, templateData.editor); + + elementDisposable.add(element.onDidChangeLayout(() => { + templateData.focusIndicator.style.height = `${element.layoutInfo.indicatorHeight}px`; + })); + + const contextKeyService = this.contextKeyService.createScoped(templateData.container); + + const runStateKey = contextKeyService.createKey(NOTEBOOK_CELL_RUN_STATE_CONTEXT_KEY, CellRunState[element.runState]); + this.updateForRunState(element, templateData, runStateKey); + elementDisposable.add(element.onDidChangeCellRunState(() => this.updateForRunState(element, templateData, runStateKey))); + + const renderExecutionOrder = () => { + const hasExecutionOrder = this.notebookEditor.viewModel!.notebookDocument.metadata?.hasExecutionOrder; + if (hasExecutionOrder) { + const executionOrdeerLabel = typeof element.metadata?.executionOrder === 'number' ? `[ ${element.metadata.executionOrder} ]` : + '[ ]'; + templateData.executionOrderLabel.innerText = executionOrdeerLabel; + } else { + templateData.executionOrderLabel.innerText = ''; + } + }; + + contextKeyService.createKey(NOTEBOOK_CELL_TYPE_CONTEXT_KEY, 'code'); + contextKeyService.createKey(NOTEBOOK_VIEW_TYPE, element.viewType); + const cellEditableKey = contextKeyService.createKey(NOTEBOOK_CELL_EDITABLE_CONTEXT_KEY, !!(element.metadata?.editable)); + const updateForMetadata = () => { + const metadata = element.getEvaluatedMetadata(this.notebookEditor.viewModel!.notebookDocument.metadata); + DOM.toggleClass(templateData.cellContainer, 'runnable', !!metadata.runnable); + renderExecutionOrder(); + cellEditableKey.set(!!metadata.editable); + }; + updateForMetadata(); + elementDisposable.add(element.onDidChangeMetadata(() => updateForMetadata())); + + this.setupCellToolbarActions(contextKeyService, templateData, elementDisposable); + + const toolbarContext = { + cell: element, + cellTemplate: templateData, + notebookEditor: this.notebookEditor, + $mid: 12 + }; + templateData.toolbar.context = toolbarContext; + templateData.runToolbar.context = toolbarContext; + + this.setupBetweenCellToolbarActions(element, templateData, elementDisposable, toolbarContext); + } + + disposeTemplate(templateData: CodeCellRenderTemplate): void { + templateData.disposables.clear(); + } + + disposeElement(element: ICellViewModel, index: number, templateData: CodeCellRenderTemplate, height: number | undefined): void { + this.disposables.get(element)?.clear(); + this.renderedEditors.delete(element); + templateData.focusIndicator.style.height = 'initial'; + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts new file mode 100644 index 00000000000..2e0d243b9ea --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts @@ -0,0 +1,432 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as DOM from 'vs/base/browser/dom'; +import { raceCancellation } from 'vs/base/common/async'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import * as nls from 'vs/nls'; +import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { EDITOR_BOTTOM_PADDING, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; +import { CellFocusMode, CodeCellRenderTemplate, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService'; +import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/sizeObserver'; +import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; +import { CellOutputKind, IOutput, IRenderOutput, ITransformedDisplayOutputDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; + +interface IMimeTypeRenderer extends IQuickPickItem { + index: number; +} + +export class CodeCell extends Disposable { + private outputResizeListeners = new Map(); + private outputElements = new Map(); + constructor( + private notebookEditor: INotebookEditor, + private viewCell: CodeCellViewModel, + private templateData: CodeCellRenderTemplate, + @INotebookService private notebookService: INotebookService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IModeService private readonly _modeService: IModeService + ) { + super(); + + const width = this.viewCell.layoutInfo.editorWidth; + const lineNum = this.viewCell.lineCount; + const lineHeight = this.viewCell.layoutInfo.fontInfo?.lineHeight || 17; + const totalHeight = lineNum * lineHeight + EDITOR_TOP_PADDING + EDITOR_BOTTOM_PADDING; + templateData.editor?.layout( + { + width: width, + height: totalHeight + } + ); + // viewCell.editorHeight = totalHeight; + + const cts = new CancellationTokenSource(); + this._register({ dispose() { cts.dispose(true); } }); + raceCancellation(viewCell.resolveTextModel(), cts.token).then(model => { + if (model && templateData.editor) { + templateData.editor.setModel(model); + viewCell.attachTextEditor(templateData.editor); + if (notebookEditor.getActiveCell() === viewCell && viewCell.focusMode === CellFocusMode.Editor) { + templateData.editor?.focus(); + } + + const realContentHeight = templateData.editor?.getContentHeight(); + const width = this.viewCell.layoutInfo.editorWidth; + + if (realContentHeight !== undefined && realContentHeight !== totalHeight) { + templateData.editor?.layout( + { + width: width, + height: realContentHeight + } + ); + + viewCell.editorHeight = realContentHeight; + } + + if (this.notebookEditor.getActiveCell() === this.viewCell && viewCell.focusMode === CellFocusMode.Editor) { + templateData.editor?.focus(); + } + } + }); + + this._register(viewCell.onDidChangeFocusMode(() => { + if (viewCell.focusMode === CellFocusMode.Editor) { + templateData.editor?.focus(); + } + })); + + templateData.editor?.updateOptions({ readOnly: !(viewCell.getEvaluatedMetadata(notebookEditor.viewModel?.metadata).editable) }); + this._register(viewCell.onDidChangeMetadata(() => { + templateData.editor?.updateOptions({ readOnly: !(viewCell.getEvaluatedMetadata(notebookEditor.viewModel?.metadata).editable) }); + })); + + this._register(viewCell.onDidChangeLanguage((e) => { + const mode = this._modeService.create(e); + templateData.editor?.getModel()?.setMode(mode.languageIdentifier); + })); + + this._register(viewCell.onDidChangeLayout((e) => { + if (e.outerWidth === undefined) { + return; + } + + const layoutInfo = templateData.editor!.getLayoutInfo(); + const realContentHeight = templateData.editor!.getContentHeight(); + + if (layoutInfo.width !== viewCell.layoutInfo.editorWidth) { + templateData.editor?.layout( + { + width: viewCell.layoutInfo.editorWidth, + height: realContentHeight + } + ); + + viewCell.editorHeight = realContentHeight; + this.relayoutCell(); + } + })); + + this._register(templateData.editor!.onDidContentSizeChange((e) => { + if (e.contentHeightChanged) { + if (this.viewCell.layoutInfo.editorHeight !== e.contentHeight) { + let viewLayout = templateData.editor!.getLayoutInfo(); + + templateData.editor?.layout( + { + width: viewLayout.width, + height: e.contentHeight + } + ); + + this.viewCell.editorHeight = e.contentHeight; + this.relayoutCell(); + } + + } + })); + + this._register(templateData.editor!.onDidChangeCursorSelection((e) => { + if (e.source === 'restoreState') { + // do not reveal the cell into view if this selection change was caused by restoring editors... + return; + } + + const primarySelection = templateData.editor!.getSelection(); + + if (primarySelection) { + this.notebookEditor.revealLineInView(viewCell, primarySelection!.positionLineNumber); + } + })); + + this._register(viewCell.onDidChangeOutputs((splices) => { + if (!splices.length) { + return; + } + + const previousOutputHeight = this.viewCell.layoutInfo.outputTotalHeight; + + if (this.viewCell.outputs.length) { + this.templateData.outputContainer!.style.display = 'block'; + } else { + this.templateData.outputContainer!.style.display = 'none'; + } + + let reversedSplices = splices.reverse(); + + reversedSplices.forEach(splice => { + viewCell.spliceOutputHeights(splice[0], splice[1], splice[2].map(_ => 0)); + }); + + let removedKeys: IOutput[] = []; + + this.outputElements.forEach((value, key) => { + if (viewCell.outputs.indexOf(key) < 0) { + // already removed + removedKeys.push(key); + // remove element from DOM + this.templateData?.outputContainer?.removeChild(value); + this.notebookEditor.removeInset(key); + } + }); + + removedKeys.forEach(key => { + // remove element cache + this.outputElements.delete(key); + // remove elment resize listener if there is one + this.outputResizeListeners.delete(key); + }); + + let prevElement: HTMLElement | undefined = undefined; + + this.viewCell.outputs.reverse().forEach(output => { + if (this.outputElements.has(output)) { + // already exist + prevElement = this.outputElements.get(output); + return; + } + + // newly added element + let currIndex = this.viewCell.outputs.indexOf(output); + this.renderOutput(output, currIndex, prevElement); + prevElement = this.outputElements.get(output); + }); + + let editorHeight = templateData.editor!.getContentHeight(); + viewCell.editorHeight = editorHeight; + + if (previousOutputHeight === 0 || this.viewCell.outputs.length === 0) { + // first execution or removing all outputs + this.relayoutCell(); + } else { + this.relayoutCellDebounced(); + } + })); + + if (viewCell.outputs.length > 0) { + let layoutCache = false; + if (this.viewCell.layoutInfo.totalHeight !== 0) { + layoutCache = true; + this.relayoutCell(); + } + + this.templateData.outputContainer!.style.display = 'block'; + // there are outputs, we need to calcualte their sizes and trigger relayout + // @todo, if there is no resizable output, we should not check their height individually, which hurts the performance + for (let index = 0; index < this.viewCell.outputs.length; index++) { + const currOutput = this.viewCell.outputs[index]; + + // always add to the end + this.renderOutput(currOutput, index, undefined); + } + + viewCell.editorHeight = totalHeight; + if (layoutCache) { + this.relayoutCellDebounced(); + } else { + this.relayoutCell(); + } + } else { + // noop + viewCell.editorHeight = totalHeight; + this.relayoutCell(); + this.templateData.outputContainer!.style.display = 'none'; + } + } + + renderOutput(currOutput: IOutput, index: number, beforeElement?: HTMLElement) { + if (!this.outputResizeListeners.has(currOutput)) { + this.outputResizeListeners.set(currOutput, new DisposableStore()); + } + + let outputItemDiv = document.createElement('div'); + let result: IRenderOutput | undefined = undefined; + + if (currOutput.outputKind === CellOutputKind.Rich) { + let transformedDisplayOutput = currOutput as ITransformedDisplayOutputDto; + + if (transformedDisplayOutput.orderedMimeTypes.length > 1) { + outputItemDiv.style.position = 'relative'; + const mimeTypePicker = DOM.$('.multi-mimetype-output'); + DOM.addClasses(mimeTypePicker, 'codicon', 'codicon-code'); + outputItemDiv.appendChild(mimeTypePicker); + this.outputResizeListeners.get(currOutput)!.add(DOM.addStandardDisposableListener(mimeTypePicker, 'mousedown', async e => { + e.preventDefault(); + e.stopPropagation(); + await this.pickActiveMimeTypeRenderer(transformedDisplayOutput); + })); + } + let pickedMimeTypeRenderer = currOutput.orderedMimeTypes[currOutput.pickedMimeTypeIndex]; + + if (pickedMimeTypeRenderer.isResolved) { + // html + result = this.notebookEditor.getOutputRenderer().render({ outputKind: CellOutputKind.Rich, data: { 'text/html': pickedMimeTypeRenderer.output! } } as any, outputItemDiv, 'text/html'); + } else { + result = this.notebookEditor.getOutputRenderer().render(currOutput, outputItemDiv, pickedMimeTypeRenderer.mimeType); + } + } else { + // for text and error, there is no mimetype + result = this.notebookEditor.getOutputRenderer().render(currOutput, outputItemDiv, undefined); + } + + if (!result) { + this.viewCell.updateOutputHeight(index, 0); + return; + } + + this.outputElements.set(currOutput, outputItemDiv); + + if (beforeElement) { + this.templateData.outputContainer?.insertBefore(outputItemDiv, beforeElement); + } else { + this.templateData.outputContainer?.appendChild(outputItemDiv); + } + + if (result.shadowContent) { + this.viewCell.selfSizeMonitoring = true; + this.notebookEditor.createInset(this.viewCell, currOutput, result.shadowContent, this.viewCell.getOutputOffset(index)); + } else { + DOM.addClass(outputItemDiv, 'foreground'); + } + + let hasDynamicHeight = result.hasDynamicHeight; + + if (hasDynamicHeight) { + let clientHeight = outputItemDiv.clientHeight; + let dimension = { + width: this.viewCell.layoutInfo.editorWidth, + height: clientHeight + }; + const elementSizeObserver = getResizesObserver(outputItemDiv, dimension, () => { + if (this.templateData.outputContainer && document.body.contains(this.templateData.outputContainer!)) { + let height = elementSizeObserver.getHeight() + 8 * 2; // include padding + + if (clientHeight === height) { + // console.log(this.viewCell.outputs); + return; + } + + const currIndex = this.viewCell.outputs.indexOf(currOutput); + if (currIndex < 0) { + return; + } + + this.viewCell.updateOutputHeight(currIndex, height); + this.relayoutCell(); + } + }); + elementSizeObserver.startObserving(); + this.outputResizeListeners.get(currOutput)!.add(elementSizeObserver); + this.viewCell.updateOutputHeight(index, clientHeight); + } else { + if (result.shadowContent) { + // webview + // noop + // let cachedHeight = this.viewCell.getOutputHeight(currOutput); + } else { + // static output + + // @TODO, if we stop checking output height, we need to evaluate it later when checking the height of output container + let clientHeight = outputItemDiv.clientHeight; + this.viewCell.updateOutputHeight(index, clientHeight); + } + } + } + + generateRendererInfo(renderId: number | undefined): string { + if (renderId === undefined || renderId === -1) { + return 'builtin'; + } + + let renderInfo = this.notebookService.getRendererInfo(renderId); + + if (renderInfo) { + return renderInfo.id.value; + } + + return 'builtin'; + } + + async pickActiveMimeTypeRenderer(output: ITransformedDisplayOutputDto) { + let currIndex = output.pickedMimeTypeIndex; + const items = output.orderedMimeTypes.map((mimeType, index): IMimeTypeRenderer => ({ + label: mimeType.mimeType, + id: mimeType.mimeType, + index: index, + picked: index === currIndex, + description: this.generateRendererInfo(mimeType.rendererId) + (index === currIndex + ? nls.localize('curruentActiveMimeType', " (Currently Active)") + : ''), + })); + + const picker = this.quickInputService.createQuickPick(); + picker.items = items; + picker.activeItems = items.filter(item => !!item.picked); + picker.placeholder = nls.localize('promptChooseMimeType.placeHolder', "Select output mimetype to render for current output"); + + const pick = await new Promise(resolve => { + picker.onDidAccept(() => { + resolve(picker.selectedItems.length === 1 ? (picker.selectedItems[0] as IMimeTypeRenderer).index : undefined); + picker.dispose(); + }); + picker.show(); + }); + + if (pick === undefined) { + return; + } + + if (pick !== currIndex) { + // user chooses another mimetype + let index = this.viewCell.outputs.indexOf(output); + let nextElement = index + 1 < this.viewCell.outputs.length ? this.outputElements.get(this.viewCell.outputs[index + 1]) : undefined; + this.outputResizeListeners.get(output)?.clear(); + let element = this.outputElements.get(output); + if (element) { + this.templateData?.outputContainer?.removeChild(element); + this.notebookEditor.removeInset(output); + } + + output.pickedMimeTypeIndex = pick; + + this.renderOutput(output, index, nextElement); + this.relayoutCell(); + } + } + + relayoutCell() { + if (this._timer !== null) { + clearTimeout(this._timer); + } + + this.notebookEditor.layoutNotebookCell(this.viewCell, this.viewCell.layoutInfo.totalHeight); + } + + private _timer: any = null; + + relayoutCellDebounced() { + clearTimeout(this._timer); + this._timer = setTimeout(() => { + this.notebookEditor.layoutNotebookCell(this.viewCell, this.viewCell.layoutInfo.totalHeight); + this._timer = null; + }, 500); + } + + dispose() { + this.viewCell.detachTextEditor(); + this.outputResizeListeners.forEach((value) => { + value.dispose(); + }); + + this.templateData.focusIndicator!.style.height = 'initial'; + + super.dispose(); + } +} + diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts new file mode 100644 index 00000000000..c44f131b65b --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts @@ -0,0 +1,237 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/sizeObserver'; +import { INotebookEditor, MarkdownCellRenderTemplate, CellFocusMode, CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { raceCancellation } from 'vs/base/common/async'; +import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; +import { EDITOR_TOP_PADDING, EDITOR_BOTTOM_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; +import { ITextModel } from 'vs/editor/common/model'; +import { IDimension } from 'vs/base/browser/dom'; + +export class StatefullMarkdownCell extends Disposable { + private editor: CodeEditorWidget | null = null; + private cellContainer: HTMLElement; + private editingContainer?: HTMLElement; + + private localDisposables: DisposableStore; + + constructor( + private notebookEditor: INotebookEditor, + private viewCell: MarkdownCellViewModel, + private templateData: MarkdownCellRenderTemplate, + editorOptions: IEditorOptions, + instantiationService: IInstantiationService + ) { + super(); + + this.cellContainer = templateData.cellContainer; + this.editingContainer = templateData.editingContainer; + this.localDisposables = new DisposableStore(); + this._register(this.localDisposables); + + const viewUpdate = () => { + if (viewCell.editState === CellEditState.Editing) { + // switch to editing mode + let width = viewCell.layoutInfo.editorWidth; + const lineNum = viewCell.lineCount; + const lineHeight = viewCell.layoutInfo.fontInfo?.lineHeight || 17; + const totalHeight = Math.max(lineNum, 1) * lineHeight + EDITOR_TOP_PADDING + EDITOR_BOTTOM_PADDING; + + if (this.editor) { + // not first time, we don't need to create editor or bind listeners + this.editingContainer!.style.display = 'flex'; + viewCell.attachTextEditor(this.editor!); + if (notebookEditor.getActiveCell() === viewCell) { + this.editor!.focus(); + } + + this.bindEditorListeners(this.editor!.getModel()!); + } else { + this.editingContainer!.style.display = 'flex'; + this.editingContainer!.innerHTML = ''; + this.editor = instantiationService.createInstance(CodeEditorWidget, this.editingContainer!, { + ...editorOptions, + dimension: { + width: width, + height: totalHeight + } + }, {}); + + const cts = new CancellationTokenSource(); + this._register({ dispose() { cts.dispose(true); } }); + raceCancellation(viewCell.resolveTextModel(), cts.token).then(model => { + if (!model) { + return; + } + + this.editor!.setModel(model); + if (notebookEditor.getActiveCell() === viewCell) { + this.editor!.focus(); + } + + const realContentHeight = this.editor!.getContentHeight(); + if (realContentHeight !== totalHeight) { + this.editor!.layout( + { + width: width, + height: realContentHeight + } + ); + } + + viewCell.attachTextEditor(this.editor!); + + if (viewCell.editState === CellEditState.Editing) { + this.editor!.focus(); + } + + this.bindEditorListeners(model, { + width: width, + height: totalHeight + }); + }); + } + + const clientHeight = this.cellContainer.clientHeight; + this.viewCell.totalHeight = totalHeight + 32 + clientHeight; + notebookEditor.layoutNotebookCell(viewCell, totalHeight + 32 + clientHeight); + this.editor.focus(); + } else { + this.viewCell.detachTextEditor(); + if (this.editor) { + // switch from editing mode + this.editingContainer!.style.display = 'none'; + const clientHeight = templateData.container.clientHeight; + this.viewCell.totalHeight = clientHeight; + notebookEditor.layoutNotebookCell(viewCell, clientHeight); + } else { + // first time, readonly mode + this.editingContainer!.style.display = 'none'; + + this.cellContainer.innerHTML = ''; + let markdownRenderer = viewCell.getMarkdownRenderer(); + let renderedHTML = viewCell.getHTML(); + if (renderedHTML) { + this.cellContainer.appendChild(renderedHTML); + } + + this.localDisposables.add(markdownRenderer.onDidUpdateRender(() => { + const clientHeight = templateData.container.clientHeight; + this.viewCell.totalHeight = clientHeight; + notebookEditor.layoutNotebookCell(viewCell, clientHeight); + })); + + this.localDisposables.add(viewCell.onDidChangeContent(() => { + this.cellContainer.innerHTML = ''; + let renderedHTML = viewCell.getHTML(); + if (renderedHTML) { + this.cellContainer.appendChild(renderedHTML); + } + })); + } + } + }; + + this._register(viewCell.onDidChangeCellEditState(() => { + this.localDisposables.clear(); + viewUpdate(); + })); + + this._register(viewCell.onDidChangeFocusMode(() => { + if (viewCell.focusMode === CellFocusMode.Editor) { + this.editor?.focus(); + } + })); + + viewUpdate(); + } + + bindEditorListeners(model: ITextModel, dimension?: IDimension) { + this.localDisposables.add(model.onDidChangeContent(() => { + this.viewCell.setText(model.getLinesContent()); + let clientHeight = this.cellContainer.clientHeight; + this.cellContainer.innerHTML = ''; + let renderedHTML = this.viewCell.getHTML(); + if (renderedHTML) { + this.cellContainer.appendChild(renderedHTML); + clientHeight = this.cellContainer.clientHeight; + } + + this.viewCell.totalHeight = this.editor!.getContentHeight() + 32 + clientHeight; + this.notebookEditor.layoutNotebookCell(this.viewCell, this.viewCell.layoutInfo.totalHeight); + })); + + this.localDisposables.add(this.editor!.onDidContentSizeChange(e => { + let viewLayout = this.editor!.getLayoutInfo(); + + if (e.contentHeightChanged) { + this.editor!.layout( + { + width: viewLayout.width, + height: e.contentHeight + } + ); + const clientHeight = this.cellContainer.clientHeight; + this.viewCell.totalHeight = e.contentHeight + 32 + clientHeight; + this.notebookEditor.layoutNotebookCell(this.viewCell, this.viewCell.layoutInfo.totalHeight); + } + })); + + this.localDisposables.add(this.editor!.onDidChangeCursorSelection((e) => { + if (e.source === 'restoreState') { + // do not reveal the cell into view if this selection change was caused by restoring editors... + return; + } + + const primarySelection = this.editor!.getSelection(); + + if (primarySelection) { + this.notebookEditor.revealLineInView(this.viewCell, primarySelection!.positionLineNumber); + } + })); + + let cellWidthResizeObserver = getResizesObserver(this.templateData.editingContainer!, dimension, () => { + let newWidth = cellWidthResizeObserver.getWidth(); + let realContentHeight = this.editor!.getContentHeight(); + let layoutInfo = this.editor!.getLayoutInfo(); + + // the dimension generated by the resize observer are float numbers, let's round it a bit to avoid relayout. + if (newWidth < layoutInfo.width - 0.3 || layoutInfo.width + 0.3 < newWidth) { + this.editor!.layout( + { + width: newWidth, + height: realContentHeight + } + ); + } + }); + + cellWidthResizeObserver.startObserving(); + this.localDisposables.add(cellWidthResizeObserver); + + let markdownRenderer = this.viewCell.getMarkdownRenderer(); + this.cellContainer.innerHTML = ''; + let renderedHTML = this.viewCell.getHTML(); + if (renderedHTML) { + this.cellContainer.appendChild(renderedHTML); + this.localDisposables.add(markdownRenderer.onDidUpdateRender(() => { + const clientHeight = this.cellContainer.clientHeight; + this.viewCell.totalHeight = clientHeight; + this.notebookEditor.layoutNotebookCell(this.viewCell, this.viewCell.layoutInfo.totalHeight); + })); + } + } + + dispose() { + this.viewCell.detachTextEditor(); + super.dispose(); + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/mdRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/mdRenderer.ts new file mode 100644 index 00000000000..4806000fadf --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/mdRenderer.ts @@ -0,0 +1,78 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { renderMarkdown, MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer'; +import { Event, Emitter } from 'vs/base/common/event'; +import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; +import { TokenizationRegistry } from 'vs/editor/common/modes'; + +export interface IMarkdownRenderResult extends IDisposable { + element: HTMLElement; +} + +export class MarkdownRenderer extends Disposable { + + private _onDidUpdateRender = this._register(new Emitter()); + readonly onDidUpdateRender: Event = this._onDidUpdateRender.event; + + constructor( + @IModeService private readonly _modeService: IModeService, + @IOpenerService private readonly _openerService: IOpenerService + ) { + super(); + } + + private getOptions(disposeables: DisposableStore): MarkdownRenderOptions { + return { + codeBlockRenderer: (languageAlias, value) => { + // In markdown, + // it is possible that we stumble upon language aliases (e.g.js instead of javascript) + // it is possible no alias is given in which case we fall back to the current editor lang + let modeId: string | null = null; + modeId = this._modeService.getModeIdForLanguageName(languageAlias || ''); + + this._modeService.triggerMode(modeId || ''); + return Promise.resolve(true).then(_ => { + const promise = TokenizationRegistry.getPromise(modeId || ''); + if (promise) { + return promise.then(support => tokenizeToString(value, support)); + } + return tokenizeToString(value, undefined); + }).then(code => { + return `${code}`; + }); + }, + codeBlockRenderCallback: () => this._onDidUpdateRender.fire(), + actionHandler: { + callback: (content) => { + this._openerService.open(content, { fromUserGesture: true }).catch(onUnexpectedError); + }, + disposeables + } + }; + } + + render(markdown: IMarkdownString | undefined): IMarkdownRenderResult { + const disposeables = new DisposableStore(); + + let element: HTMLElement; + if (!markdown) { + element = document.createElement('span'); + } else { + element = renderMarkdown(markdown, this.getOptions(disposeables), { gfm: true }); + } + + return { + element, + dispose: () => disposeables.dispose() + }; + } +} + diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/sizeObserver.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/sizeObserver.ts new file mode 100644 index 00000000000..6a5b6ba6041 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/sizeObserver.ts @@ -0,0 +1,78 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as DOM from 'vs/base/browser/dom'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IDimension } from 'vs/editor/common/editorCommon'; +import { ElementSizeObserver } from 'vs/editor/browser/config/elementSizeObserver'; + +declare const ResizeObserver: any; + +export interface IResizeObserver { + startObserving: () => void; + stopObserving: () => void; + getWidth(): number; + getHeight(): number; + dispose(): void; +} + +export class BrowserResizeObserver extends Disposable implements IResizeObserver { + private readonly referenceDomElement: HTMLElement | null; + + private readonly observer: any; + private width: number; + private height: number; + + constructor(referenceDomElement: HTMLElement | null, dimension: IDimension | undefined, changeCallback: () => void) { + super(); + + this.referenceDomElement = referenceDomElement; + this.width = -1; + this.height = -1; + + this.observer = new ResizeObserver((entries: any) => { + for (let entry of entries) { + if (entry.target === referenceDomElement && entry.contentRect) { + if (this.width !== entry.contentRect.width || this.height !== entry.contentRect.height) { + this.width = entry.contentRect.width; + this.height = entry.contentRect.height; + DOM.scheduleAtNextAnimationFrame(() => { + changeCallback(); + }); + } + } + } + }); + } + + getWidth(): number { + return this.width; + } + + getHeight(): number { + return this.height; + } + + startObserving(): void { + this.observer.observe(this.referenceDomElement!); + } + + stopObserving(): void { + this.observer.unobserve(this.referenceDomElement!); + } + + dispose(): void { + this.observer.disconnect(); + super.dispose(); + } +} + +export function getResizesObserver(referenceDomElement: HTMLElement | null, dimension: IDimension | undefined, changeCallback: () => void): IResizeObserver { + if (ResizeObserver) { + return new BrowserResizeObserver(referenceDomElement, dimension, changeCallback); + } else { + return new ElementSizeObserver(referenceDomElement, dimension, changeCallback); + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts new file mode 100644 index 00000000000..f58ab3d9089 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts @@ -0,0 +1,349 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { Range } from 'vs/editor/common/core/range'; +import * as editorCommon from 'vs/editor/common/editorCommon'; +import * as model from 'vs/editor/common/model'; +import { SearchParams } from 'vs/editor/common/model/textModelSearch'; +import { EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_MARGIN } from 'vs/workbench/contrib/notebook/browser/constants'; +import { CellEditState, CellFocusMode, CellRunState, CursorAtBoundary, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellKind, ICell, NotebookCellMetadata, NotebookDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; + +export const NotebookCellMetadataDefaults = { + editable: true, + runnable: true +}; + +export abstract class BaseCellViewModel extends Disposable implements ICellViewModel { + protected readonly _onDidDispose = new Emitter(); + readonly onDidDispose = this._onDidDispose.event; + protected readonly _onDidChangeCellEditState = new Emitter(); + readonly onDidChangeCellEditState = this._onDidChangeCellEditState.event; + protected readonly _onDidChangeCellRunState = new Emitter(); + readonly onDidChangeCellRunState = this._onDidChangeCellRunState.event; + protected readonly _onDidChangeFocusMode = new Emitter(); + readonly onDidChangeFocusMode = this._onDidChangeFocusMode.event; + protected readonly _onDidChangeEditorAttachState = new Emitter(); + readonly onDidChangeEditorAttachState = this._onDidChangeEditorAttachState.event; + protected readonly _onDidChangeCursorSelection: Emitter = this._register(new Emitter()); + public readonly onDidChangeCursorSelection: Event = this._onDidChangeCursorSelection.event; + protected readonly _onDidChangeMetadata: Emitter = this._register(new Emitter()); + public readonly onDidChangeMetadata: Event = this._onDidChangeMetadata.event; + protected readonly _onDidChangeLanguage: Emitter = this._register(new Emitter()); + public readonly onDidChangeLanguage: Event = this._onDidChangeLanguage.event; + get handle() { + return this.cell.handle; + } + get uri() { + return this.cell.uri; + } + get lineCount() { + return this.cell.source.length; + } + get metadata() { + return this.cell.metadata; + } + + abstract cellKind: CellKind; + + private _editState: CellEditState = CellEditState.Preview; + + get editState(): CellEditState { + return this._editState; + } + + set editState(newState: CellEditState) { + if (newState === this._editState) { + return; + } + + this._editState = newState; + this._onDidChangeCellEditState.fire(); + } + + private _currentTokenSource: CancellationTokenSource | undefined; + public set currentTokenSource(v: CancellationTokenSource | undefined) { + this._currentTokenSource = v; + this._onDidChangeCellRunState.fire(); + } + + public get currentTokenSource(): CancellationTokenSource | undefined { + return this._currentTokenSource; + } + + get runState(): CellRunState { + return this._currentTokenSource ? CellRunState.Running : CellRunState.Idle; + } + + private _focusMode: CellFocusMode = CellFocusMode.Container; + get focusMode() { + return this._focusMode; + } + set focusMode(newMode: CellFocusMode) { + this._focusMode = newMode; + this._onDidChangeFocusMode.fire(); + } + + protected _textEditor?: ICodeEditor; + get editorAttached(): boolean { + return !!this._textEditor; + } + private _cursorChangeListener: IDisposable | null = null; + private _editorViewStates: editorCommon.ICodeEditorViewState | null = null; + private _resolvedDecorations = new Map(); + private _lastDecorationId: number = 0; + protected _textModel?: model.ITextModel; + + constructor(readonly viewType: string, readonly notebookHandle: number, readonly cell: ICell, public id: string) { + super(); + + this._register(cell.onDidChangeLanguage((e) => { + this._onDidChangeLanguage.fire(e); + })); + + this._register(cell.onDidChangeMetadata(() => { + this._onDidChangeMetadata.fire(); + })); + } + + abstract hasDynamicHeight(): boolean; + abstract getHeight(lineHeight: number): number; + abstract onDeselect(): void; + + assertTextModelAttached(): boolean { + if (this._textModel && this._textEditor && this._textEditor.getModel() === this._textModel) { + return true; + } + + return false; + } + + attachTextEditor(editor: ICodeEditor) { + if (!editor.hasModel()) { + throw new Error('Invalid editor: model is missing'); + } + + if (this._textEditor === editor) { + if (this._cursorChangeListener === null) { + this._cursorChangeListener = this._textEditor.onDidChangeCursorSelection(() => this._onDidChangeCursorSelection.fire()); + this._onDidChangeCursorSelection.fire(); + } + return; + } + + this._textEditor = editor; + + if (this._editorViewStates) { + this.restoreViewState(this._editorViewStates); + } + + this._resolvedDecorations.forEach((value, key) => { + if (key.startsWith('_lazy_')) { + // lazy ones + const ret = this._textEditor!.deltaDecorations([], [value.options]); + this._resolvedDecorations.get(key)!.id = ret[0]; + } + else { + const ret = this._textEditor!.deltaDecorations([], [value.options]); + this._resolvedDecorations.get(key)!.id = ret[0]; + } + }); + + this._cursorChangeListener = this._textEditor.onDidChangeCursorSelection(() => this._onDidChangeCursorSelection.fire()); + this._onDidChangeCursorSelection.fire(); + this._onDidChangeEditorAttachState.fire(true); + } + + detachTextEditor() { + this._editorViewStates = this.saveViewState(); + // decorations need to be cleared first as editors can be resued. + this._resolvedDecorations.forEach(value => { + let resolvedid = value.id; + + if (resolvedid) { + this._textEditor?.deltaDecorations([resolvedid], []); + } + }); + + this._textEditor = undefined; + this._cursorChangeListener?.dispose(); + this._cursorChangeListener = null; + this._onDidChangeEditorAttachState.fire(false); + } + + getText(): string { + if (this._textModel) { + return this._textModel.getValue(); + } + + return this.cell.source.join('\n'); + } + + private saveViewState(): editorCommon.ICodeEditorViewState | null { + if (!this._textEditor) { + return null; + } + + return this._textEditor.saveViewState(); + } + + saveEditorViewState() { + if (this._textEditor) { + this._editorViewStates = this.saveViewState(); + } + + return this._editorViewStates; + } + + restoreEditorViewState(editorViewStates: editorCommon.ICodeEditorViewState | null, totalHeight?: number) { + this._editorViewStates = editorViewStates; + } + + private restoreViewState(state: editorCommon.ICodeEditorViewState | null): void { + if (state) { + this._textEditor?.restoreViewState(state); + } + } + + addDecoration(decoration: model.IModelDeltaDecoration): string { + if (!this._textEditor) { + const id = ++this._lastDecorationId; + const decorationId = `_lazy_${this.id};${id}`; + this._resolvedDecorations.set(decorationId, { options: decoration }); + return decorationId; + } + + const result = this._textEditor.deltaDecorations([], [decoration]); + this._resolvedDecorations.set(result[0], { id: result[0], options: decoration }); + return result[0]; + } + + removeDecoration(decorationId: string) { + const realDecorationId = this._resolvedDecorations.get(decorationId); + + if (this._textEditor && realDecorationId && realDecorationId.id !== undefined) { + this._textEditor.deltaDecorations([realDecorationId.id!], []); + } + + // lastly, remove all the cache + this._resolvedDecorations.delete(decorationId); + } + + deltaDecorations(oldDecorations: string[], newDecorations: model.IModelDeltaDecoration[]): string[] { + oldDecorations.forEach(id => { + this.removeDecoration(id); + }); + + const ret = newDecorations.map(option => { + return this.addDecoration(option); + }); + + return ret; + } + + revealRangeInCenter(range: Range) { + this._textEditor?.revealRangeInCenter(range, editorCommon.ScrollType.Immediate); + } + + setSelection(range: Range) { + this._textEditor?.setSelection(range); + } + + getLineScrollTopOffset(line: number): number { + if (!this._textEditor) { + return 0; + } + + return this._textEditor.getTopForLineNumber(line) + EDITOR_TOP_MARGIN + EDITOR_TOOLBAR_HEIGHT; + } + + cursorAtBoundary(): CursorAtBoundary { + if (!this._textEditor) { + return CursorAtBoundary.None; + } + + // only validate primary cursor + const selection = this._textEditor.getSelection(); + + // only validate empty cursor + if (!selection || !selection.isEmpty()) { + return CursorAtBoundary.None; + } + + // we don't allow attaching text editor without a model + const lineCnt = this._textEditor.getModel()!.getLineCount(); + + if (selection.startLineNumber === lineCnt) { + // bottom + if (selection.startLineNumber === 1) { + return CursorAtBoundary.Both; + } + else { + return CursorAtBoundary.Bottom; + } + } + + if (selection.startLineNumber === 1) { + return CursorAtBoundary.Top; + } + + return CursorAtBoundary.None; + } + + protected _buffer: model.ITextBuffer | null = null; + + protected cellStartFind(value: string): model.FindMatch[] | null { + let cellMatches: model.FindMatch[] = []; + + if (this.assertTextModelAttached()) { + cellMatches = this._textModel!.findMatches(value, false, false, false, null, false); + } else { + if (!this._buffer) { + this._buffer = this.cell.resolveTextBufferFactory().create(model.DefaultEndOfLine.LF); + } + + const lineCount = this._buffer.getLineCount(); + const fullRange = new Range(1, 1, lineCount, this._buffer.getLineLength(lineCount) + 1); + const searchParams = new SearchParams(value, false, false, null); + const searchData = searchParams.parseSearchRequest(); + + if (!searchData) { + return null; + } + + cellMatches = this._buffer.findMatchesLineByLine(fullRange, searchData, false, 1000); + } + + return cellMatches; + } + + getEvaluatedMetadata(documentMetadata: NotebookDocumentMetadata | undefined): NotebookCellMetadata { + const editable: boolean = this.metadata?.editable === undefined + ? (documentMetadata?.cellEditable === undefined ? NotebookCellMetadataDefaults.editable : documentMetadata?.cellEditable) + : this.metadata?.editable; + + const runnable: boolean = this.metadata?.runnable === undefined + ? (documentMetadata?.cellRunnable === undefined ? NotebookCellMetadataDefaults.runnable : documentMetadata?.cellRunnable) + : this.metadata?.runnable; + + return { + editable, + runnable + }; + } + + toJSON(): any { + return { + handle: this.handle + }; + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/cellEdit.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/cellEdit.ts new file mode 100644 index 00000000000..85e74cabf6d --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/cellEdit.ts @@ -0,0 +1,110 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ICell } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IResourceUndoRedoElement, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo'; +import { URI } from 'vs/base/common/uri'; +import { BaseCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel'; + +/** + * It should not modify Undo/Redo stack + */ +export interface ICellEditingDelegate { + insertCell?(index: number, viewCell: BaseCellViewModel): void; + deleteCell?(index: number, cell: ICell): void; + moveCell?(fromIndex: number, toIndex: number): void; + createCellViewModel?(cell: ICell): BaseCellViewModel; +} + +export class InsertCellEdit implements IResourceUndoRedoElement { + type: UndoRedoElementType.Resource = UndoRedoElementType.Resource; + label: string = 'Insert Cell'; + constructor( + public resource: URI, + private insertIndex: number, + private cell: BaseCellViewModel, + private editingDelegate: ICellEditingDelegate + ) { + } + + undo(): void | Promise { + if (!this.editingDelegate.deleteCell) { + throw new Error('Notebook Delete Cell not implemented for Undo/Redo'); + } + + this.editingDelegate.deleteCell(this.insertIndex, this.cell.cell); + } + redo(): void | Promise { + if (!this.editingDelegate.insertCell) { + throw new Error('Notebook Insert Cell not implemented for Undo/Redo'); + } + + this.editingDelegate.insertCell(this.insertIndex, this.cell); + } +} + +export class DeleteCellEdit implements IResourceUndoRedoElement { + type: UndoRedoElementType.Resource = UndoRedoElementType.Resource; + label: string = 'Delete Cell'; + + private _rawCell: ICell; + constructor( + public resource: URI, + private insertIndex: number, + cell: BaseCellViewModel, + private editingDelegate: ICellEditingDelegate + ) { + this._rawCell = cell.cell; + + // save inmem text to `ICell` + this._rawCell.source = [cell.getText()]; + } + + undo(): void | Promise { + if (!this.editingDelegate.insertCell || !this.editingDelegate.createCellViewModel) { + throw new Error('Notebook Insert Cell not implemented for Undo/Redo'); + } + + const cell = this.editingDelegate.createCellViewModel(this._rawCell); + this.editingDelegate.insertCell(this.insertIndex, cell); + } + + redo(): void | Promise { + if (!this.editingDelegate.deleteCell) { + throw new Error('Notebook Delete Cell not implemented for Undo/Redo'); + } + + this.editingDelegate.deleteCell(this.insertIndex, this._rawCell); + } +} + +export class MoveCellEdit implements IResourceUndoRedoElement { + type: UndoRedoElementType.Resource = UndoRedoElementType.Resource; + label: string = 'Delete Cell'; + + constructor( + public resource: URI, + private fromIndex: number, + private toIndex: number, + private editingDelegate: ICellEditingDelegate + ) { + } + + undo(): void | Promise { + if (!this.editingDelegate.moveCell) { + throw new Error('Notebook Move Cell not implemented for Undo/Redo'); + } + + this.editingDelegate.moveCell(this.toIndex, this.fromIndex); + } + + redo(): void | Promise { + if (!this.editingDelegate.moveCell) { + throw new Error('Notebook Move Cell not implemented for Undo/Redo'); + } + + this.editingDelegate.moveCell(this.fromIndex, this.toIndex); + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts new file mode 100644 index 00000000000..3cbc9ff88ad --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts @@ -0,0 +1,270 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import * as UUID from 'vs/base/common/uuid'; +import * as model from 'vs/editor/common/model'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; +import { EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_PADDING, CELL_MARGIN, CELL_RUN_GUTTER, EDITOR_TOP_MARGIN, BOTTOM_CELL_TOOLBAR_HEIGHT } from 'vs/workbench/contrib/notebook/browser/constants'; +import { CellEditState, ICellViewModel, CellFindMatch, CodeCellLayoutChangeEvent, CodeCellLayoutInfo, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellKind, ICell, NotebookCellOutputsSplice } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { BaseCellViewModel } from './baseCellViewModel'; +import { NotebookEventDispatcher } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; +import * as editorCommon from 'vs/editor/common/editorCommon'; + +export class CodeCellViewModel extends BaseCellViewModel implements ICellViewModel { + cellKind: CellKind.Code = CellKind.Code; + protected readonly _onDidChangeOutputs = new Emitter(); + readonly onDidChangeOutputs = this._onDidChangeOutputs.event; + private _outputCollection: number[] = []; + private _selfSizeMonitoring: boolean = false; + set selfSizeMonitoring(newVal: boolean) { + this._selfSizeMonitoring = newVal; + } + + get selfSizeMonitoring() { + return this._selfSizeMonitoring; + } + + private _outputsTop: PrefixSumComputer | null = null; + get outputs() { + return this.cell.outputs; + } + + private readonly _onDidChangeContent: Emitter = this._register(new Emitter()); + public readonly onDidChangeContent: Event = this._onDidChangeContent.event; + + protected readonly _onDidChangeLayout = new Emitter(); + readonly onDidChangeLayout = this._onDidChangeLayout.event; + + private _editorHeight = 0; + set editorHeight(height: number) { + this._editorHeight = height; + + this.layoutChange({ editorHeight: true }); + } + + get editorHeight() { + return this._editorHeight; + } + + private _layoutInfo: CodeCellLayoutInfo; + + get layoutInfo() { + return this._layoutInfo; + } + + constructor( + readonly viewType: string, + readonly notebookHandle: number, + readonly cell: ICell, + readonly eventDispatcher: NotebookEventDispatcher, + initialNotebookLayoutInfo: NotebookLayoutInfo | null, + @ITextModelService private readonly _modelService: ITextModelService, + ) { + super(viewType, notebookHandle, cell, UUID.generateUuid()); + if (this.cell.onDidChangeOutputs) { + this._register(this.cell.onDidChangeOutputs((splices) => { + this._outputCollection = new Array(this.cell.outputs.length); + this._outputsTop = null; + this._onDidChangeOutputs.fire(splices); + })); + } + + this._register(this.cell.onDidChangeMetadata(() => { + this._onDidChangeMetadata.fire(); + })); + + this._outputCollection = new Array(this.cell.outputs.length); + this._buffer = null; + + this._layoutInfo = { + fontInfo: initialNotebookLayoutInfo?.fontInfo || null, + editorHeight: 0, + editorWidth: initialNotebookLayoutInfo ? initialNotebookLayoutInfo!.width - CELL_MARGIN * 2 - CELL_RUN_GUTTER : 0, + outputContainerOffset: 0, + outputTotalHeight: 0, + totalHeight: 0, + indicatorHeight: 0, + bottomToolbarOffset: 0 + }; + + this._register(eventDispatcher.onDidChangeLayout((e) => { + if (e.source.width !== undefined) { + this.layoutChange({ outerWidth: e.value.width, font: e.value.fontInfo }); + } + })); + + this._register(this.onDidChangeLanguage((e) => { + if (this._textModel && !this._textModel.isDisposed()) { + + } + })); + } + + layoutChange(state: CodeCellLayoutChangeEvent) { + // recompute + this._ensureOutputsTop(); + const outputTotalHeight = this._outputsTop!.getTotalValue(); + const totalHeight = EDITOR_TOOLBAR_HEIGHT + this.editorHeight + EDITOR_TOP_MARGIN + outputTotalHeight + BOTTOM_CELL_TOOLBAR_HEIGHT; + const indicatorHeight = this.editorHeight + outputTotalHeight; + const outputContainerOffset = EDITOR_TOOLBAR_HEIGHT + EDITOR_TOP_MARGIN + this.editorHeight; + const bottomToolbarOffset = totalHeight - BOTTOM_CELL_TOOLBAR_HEIGHT; + const editorWidth = state.outerWidth !== undefined ? state.outerWidth - CELL_MARGIN * 2 - CELL_RUN_GUTTER : this._layoutInfo?.editorWidth; + this._layoutInfo = { + fontInfo: state.font || null, + editorHeight: this._editorHeight, + editorWidth, + outputContainerOffset, + outputTotalHeight, + totalHeight, + indicatorHeight, + bottomToolbarOffset: bottomToolbarOffset + }; + + if (state.editorHeight || state.outputHeight) { + state.totalHeight = true; + } + + this._fireOnDidChangeLayout(state); + } + + private _fireOnDidChangeLayout(state: CodeCellLayoutChangeEvent) { + this._onDidChangeLayout.fire(state); + } + + restoreEditorViewState(editorViewStates: editorCommon.ICodeEditorViewState | null, totalHeight?: number) { + super.restoreEditorViewState(editorViewStates); + if (totalHeight !== undefined) { + this._layoutInfo = { + fontInfo: this._layoutInfo.fontInfo, + editorHeight: this._layoutInfo.editorHeight, + editorWidth: this._layoutInfo.editorWidth, + outputContainerOffset: this._layoutInfo.outputContainerOffset, + outputTotalHeight: this._layoutInfo.outputTotalHeight, + totalHeight: totalHeight, + indicatorHeight: this._layoutInfo.indicatorHeight, + bottomToolbarOffset: this._layoutInfo.bottomToolbarOffset + }; + } + } + + hasDynamicHeight() { + if (this.selfSizeMonitoring) { + // if there is an output rendered in the webview, it should always be false + return false; + } + + if (this.outputs && this.outputs.length > 0) { + // if it contains output, it will be marked as dynamic height + // thus when it's being rendered, the list view will `probeHeight` + // inside which, we will check domNode's height directly instead of doing another `renderElement` with height undefined. + return true; + } + else { + return false; + } + } + + getHeight(lineHeight: number) { + if (this._layoutInfo.totalHeight === 0) { + return EDITOR_TOOLBAR_HEIGHT + EDITOR_TOP_MARGIN + this.lineCount * lineHeight + EDITOR_TOP_PADDING + EDITOR_BOTTOM_PADDING + BOTTOM_CELL_TOOLBAR_HEIGHT; + } else { + return this._layoutInfo.totalHeight; + } + } + + save() { + if (this._textModel && !this._textModel.isDisposed() && this.editState === CellEditState.Editing) { + let cnt = this._textModel.getLineCount(); + this.cell.source = this._textModel.getLinesContent().map((str, index) => str + (index !== cnt - 1 ? '\n' : '')); + } + } + + async resolveTextModel(): Promise { + if (!this._textModel) { + const ref = await this._modelService.createModelReference(this.cell.uri); + this._textModel = ref.object.textEditorModel; + this._buffer = this._textModel.getTextBuffer(); + this._register(ref); + this._register(this._textModel.onDidChangeContent(() => { + this.cell.contentChange(); + this._onDidChangeContent.fire(); + })); + } + + return this._textModel; + } + + onDeselect() { + this.editState = CellEditState.Preview; + } + + updateOutputHeight(index: number, height: number) { + if (index >= this._outputCollection.length) { + throw new Error('Output index out of range!'); + } + + this._outputCollection[index] = height; + this._ensureOutputsTop(); + this._outputsTop!.changeValue(index, height); + this.layoutChange({ outputHeight: true }); + } + + getOutputOffset(index: number): number { + this._ensureOutputsTop(); + + if (index >= this._outputCollection.length) { + throw new Error('Output index out of range!'); + } + + const offset = this._outputsTop!.getAccumulatedValue(index - 1); + return this.layoutInfo.outputContainerOffset + offset; + } + + spliceOutputHeights(start: number, deleteCnt: number, heights: number[]) { + this._ensureOutputsTop(); + + this._outputsTop!.removeValues(start, deleteCnt); + if (heights.length) { + const values = new Uint32Array(heights.length); + for (let i = 0; i < heights.length; i++) { + values[i] = heights[i]; + } + + this._outputsTop!.insertValues(start, values); + } + + this.layoutChange({ outputHeight: true }); + } + + private _ensureOutputsTop(): void { + if (!this._outputsTop) { + const values = new Uint32Array(this._outputCollection.length); + for (let i = 0; i < this._outputCollection.length; i++) { + values[i] = this._outputCollection[i]; + } + + this._outputsTop = new PrefixSumComputer(values); + } + } + + private readonly _hasFindResult = this._register(new Emitter()); + public readonly hasFindResult: Event = this._hasFindResult.event; + + startFind(value: string): CellFindMatch | null { + const matches = super.cellStartFind(value); + + if (matches === null) { + return null; + } + + return { + cell: this, + matches + }; + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher.ts new file mode 100644 index 00000000000..e67499fa1d6 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter } from 'vs/base/common/event'; +import { NotebookDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookLayoutChangeEvent, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; + +export enum NotebookViewEventType { + LayoutChanged = 1, + MetadataChanged = 2 +} + +export class NotebookLayoutChangedEvent { + public readonly type = NotebookViewEventType.LayoutChanged; + + constructor(readonly source: NotebookLayoutChangeEvent, readonly value: NotebookLayoutInfo) { + + } +} + + +export class NotebookMetadataChangedEvent { + public readonly type = NotebookViewEventType.MetadataChanged; + + constructor(readonly source: NotebookDocumentMetadata) { + + } +} + + +export type NotebookViewEvent = NotebookLayoutChangedEvent | NotebookMetadataChangedEvent; + +export class NotebookEventDispatcher { + protected readonly _onDidChangeLayout = new Emitter(); + readonly onDidChangeLayout = this._onDidChangeLayout.event; + protected readonly _onDidChangeMetadata = new Emitter(); + readonly onDidChangeMetadata = this._onDidChangeMetadata.event; + + constructor() { + } + + emit(events: NotebookViewEvent[]) { + for (let i = 0, len = events.length; i < len; i++) { + let e = events[i]; + + switch (e.type) { + case NotebookViewEventType.LayoutChanged: + this._onDidChangeLayout.fire(e); + break; + case NotebookViewEventType.MetadataChanged: + this._onDidChangeMetadata.fire(e); + break; + } + } + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts new file mode 100644 index 00000000000..69d7f227789 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts @@ -0,0 +1,170 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import * as UUID from 'vs/base/common/uuid'; +import * as model from 'vs/editor/common/model'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ICellViewModel, CellFindMatch, MarkdownCellLayoutInfo, MarkdownCellLayoutChangeEvent, CellEditState, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { MarkdownRenderer } from 'vs/workbench/contrib/notebook/browser/view/renderers/mdRenderer'; +import { BaseCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel'; +import { CellKind, ICell } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CELL_MARGIN, CELL_RUN_GUTTER, BOTTOM_CELL_TOOLBAR_HEIGHT } from 'vs/workbench/contrib/notebook/browser/constants'; +import { NotebookEventDispatcher } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; +import * as editorCommon from 'vs/editor/common/editorCommon'; + +export class MarkdownCellViewModel extends BaseCellViewModel implements ICellViewModel { + cellKind: CellKind.Markdown = CellKind.Markdown; + private _mdRenderer: MarkdownRenderer | null = null; + private _html: HTMLElement | null = null; + private readonly _onDidChangeContent: Emitter = this._register(new Emitter()); + public readonly onDidChangeContent: Event = this._onDidChangeContent.event; + + private _layoutInfo: MarkdownCellLayoutInfo; + + get layoutInfo() { + return this._layoutInfo; + } + + set totalHeight(newHeight: number) { + this.layoutChange({ totalHeight: newHeight }); + } + + get totalHeight() { + throw new Error('MarkdownCellViewModel.totalHeight is write only'); + } + + protected readonly _onDidChangeLayout = new Emitter(); + readonly onDidChangeLayout = this._onDidChangeLayout.event; + + constructor( + readonly viewType: string, + readonly notebookHandle: number, + readonly cell: ICell, + readonly eventDispatcher: NotebookEventDispatcher, + initialNotebookLayoutInfo: NotebookLayoutInfo | null, + @IInstantiationService private readonly _instaService: IInstantiationService, + @ITextModelService private readonly _modelService: ITextModelService) { + super(viewType, notebookHandle, cell, UUID.generateUuid()); + + this._layoutInfo = { + fontInfo: initialNotebookLayoutInfo?.fontInfo || null, + editorWidth: initialNotebookLayoutInfo?.width || 0, + bottomToolbarOffset: BOTTOM_CELL_TOOLBAR_HEIGHT, + totalHeight: 0 + }; + + this._register(eventDispatcher.onDidChangeLayout((e) => { + if (e.source.width || e.source.fontInfo) { + this.layoutChange({ outerWidth: e.value.width, font: e.value.fontInfo }); + } + })); + } + + layoutChange(state: MarkdownCellLayoutChangeEvent) { + // recompute + const editorWidth = state.outerWidth !== undefined ? state.outerWidth - CELL_MARGIN * 2 - CELL_RUN_GUTTER : 0; + + this._layoutInfo = { + fontInfo: state.font || null, + editorWidth, + bottomToolbarOffset: BOTTOM_CELL_TOOLBAR_HEIGHT, + totalHeight: state.totalHeight === undefined ? this._layoutInfo.totalHeight : state.totalHeight + }; + + this._onDidChangeLayout.fire(state); + } + + restoreEditorViewState(editorViewStates: editorCommon.ICodeEditorViewState | null, totalHeight?: number) { + super.restoreEditorViewState(editorViewStates); + if (totalHeight !== undefined) { + this._layoutInfo = { + fontInfo: this._layoutInfo.fontInfo, + editorWidth: this._layoutInfo.editorWidth, + bottomToolbarOffset: this._layoutInfo.bottomToolbarOffset, + totalHeight: totalHeight + }; + } + } + + hasDynamicHeight() { + return true; + } + + getHeight(lineHeight: number) { + if (this._layoutInfo.totalHeight === 0) { + return 100; + } else { + return this._layoutInfo.totalHeight; + } + } + + setText(strs: string[]) { + this.cell.source = strs; + this._html = null; + } + + save() { + if (this._textModel && !this._textModel.isDisposed() && this.editState === CellEditState.Editing) { + let cnt = this._textModel.getLineCount(); + this.cell.source = this._textModel.getLinesContent().map((str, index) => str + (index !== cnt - 1 ? '\n' : '')); + } + } + + getHTML(): HTMLElement | null { + if (this.cellKind === CellKind.Markdown) { + if (this._html) { + return this._html; + } + let renderer = this.getMarkdownRenderer(); + this._html = renderer.render({ value: this.getText(), isTrusted: true }).element; + return this._html; + } + return null; + } + + async resolveTextModel(): Promise { + if (!this._textModel) { + const ref = await this._modelService.createModelReference(this.cell.uri); + this._textModel = ref.object.textEditorModel; + this._buffer = this._textModel.getTextBuffer(); + this._register(ref); + this._register(this._textModel.onDidChangeContent(() => { + this.cell.contentChange(); + this._html = null; + this._onDidChangeContent.fire(); + })); + } + return this._textModel; + } + + onDeselect() { + this.editState = CellEditState.Preview; + } + + getMarkdownRenderer() { + if (!this._mdRenderer) { + this._mdRenderer = this._instaService.createInstance(MarkdownRenderer); + } + return this._mdRenderer; + } + + private readonly _hasFindResult = this._register(new Emitter()); + public readonly hasFindResult: Event = this._hasFindResult.event; + + startFind(value: string): CellFindMatch | null { + const matches = super.cellStartFind(value); + + if (matches === null) { + return null; + } + + return { + cell: this, + matches + }; + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts new file mode 100644 index 00000000000..5b861ea0306 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts @@ -0,0 +1,442 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { onUnexpectedError } from 'vs/base/common/errors'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; +import { Range } from 'vs/editor/common/core/range'; +import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IModelDeltaDecoration } from 'vs/editor/common/model'; +import { WorkspaceTextEdit } from 'vs/editor/common/modes'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; +import { CellFindMatch, CellEditState, ICellViewModel, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; +import { DeleteCellEdit, InsertCellEdit, MoveCellEdit } from 'vs/workbench/contrib/notebook/browser/viewModel/cellEdit'; +import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; +import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; +import { CellKind, ICell } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookEventDispatcher, NotebookMetadataChangedEvent } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; + +export interface INotebookEditorViewState { + editingCells: { [key: number]: boolean }; + editorViewStates: { [key: number]: editorCommon.ICodeEditorViewState | null }; + cellTotalHeights?: { [key: number]: number }; + scrollPosition?: { left: number; top: number; }; +} + +export interface ICellModelDecorations { + ownerId: number; + decorations: string[]; +} + +export interface ICellModelDeltaDecorations { + ownerId: number; + decorations: IModelDeltaDecoration[]; +} + +export interface IModelDecorationsChangeAccessor { + deltaDecorations(oldDecorations: ICellModelDecorations[], newDecorations: ICellModelDeltaDecorations[]): ICellModelDecorations[]; +} + +const invalidFunc = () => { throw new Error(`Invalid change accessor`); }; + + +export type NotebookViewCellsSplice = [ + number /* start */, + number /* delete count */, + CellViewModel[] +]; + +export interface INotebookViewCellsUpdateEvent { + synchronous: boolean; + splices: NotebookViewCellsSplice[]; +} + +export class NotebookViewModel extends Disposable { + private _localStore: DisposableStore = this._register(new DisposableStore()); + private _viewCells: CellViewModel[] = []; + + private _currentTokenSource: CancellationTokenSource | undefined; + + get currentTokenSource(): CancellationTokenSource | undefined { + return this._currentTokenSource; + } + + set currentTokenSource(v: CancellationTokenSource | undefined) { + this._currentTokenSource = v; + } + + get viewCells(): ICellViewModel[] { + return this._viewCells; + } + + get notebookDocument() { + return this._model.notebook; + } + + get renderers() { + return this._model.notebook!.renderers; + } + + get handle() { + return this._model.notebook.handle; + } + + get languages() { + return this._model.notebook.languages; + } + + get uri() { + return this._model.notebook.uri; + } + + get metadata() { + return this._model.notebook.metadata; + } + + private readonly _onDidChangeViewCells = new Emitter(); + get onDidChangeViewCells(): Event { return this._onDidChangeViewCells.event; } + + private _lastNotebookEditResource: URI[] = []; + + get lastNotebookEditResource(): URI | null { + if (this._lastNotebookEditResource.length) { + return this._lastNotebookEditResource[this._lastNotebookEditResource.length - 1]; + } + return null; + } + + get layoutInfo(): NotebookLayoutInfo | null { + return this._layoutInfo; + } + + constructor( + public viewType: string, + private _model: NotebookEditorModel, + readonly eventDispatcher: NotebookEventDispatcher, + private _layoutInfo: NotebookLayoutInfo | null, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IBulkEditService private readonly bulkEditService: IBulkEditService, + @IUndoRedoService private readonly undoService: IUndoRedoService + ) { + super(); + + this._register(this._model.onDidChangeCells(e => { + this._onDidChangeViewCells.fire({ + synchronous: true, + splices: e.map(splice => { + return [splice[0], splice[1], splice[2].map(cell => { + return createCellViewModel(this.instantiationService, this, cell); + })]; + }) + }); + })); + + this._register(this._model.notebook.onDidChangeMetadata(e => { + this.eventDispatcher.emit([new NotebookMetadataChangedEvent(e)]); + })); + + this._register(this.eventDispatcher.onDidChangeLayout((e) => { + this._layoutInfo = e.value; + })); + + this._viewCells = this._model!.notebook!.cells.map(cell => { + return createCellViewModel(this.instantiationService, this, cell); + }); + } + + isDirty() { + return this._model.isDirty(); + } + + hide() { + this._viewCells.forEach(cell => { + if (cell.getText() !== '') { + cell.editState = CellEditState.Preview; + } + }); + } + + getViewCellIndex(cell: ICellViewModel) { + return this._viewCells.indexOf(cell as CellViewModel); + } + + private _insertCellDelegate(insertIndex: number, insertCell: CellViewModel) { + this._viewCells!.splice(insertIndex, 0, insertCell); + this._model.insertCell(insertCell.cell, insertIndex); + this._localStore.add(insertCell); + this._onDidChangeViewCells.fire({ synchronous: true, splices: [[insertIndex, 0, [insertCell]]] }); + } + + private _deleteCellDelegate(deleteIndex: number, cell: ICell) { + this._viewCells.splice(deleteIndex, 1); + this._model.deleteCell(deleteIndex); + this._onDidChangeViewCells.fire({ synchronous: true, splices: [[deleteIndex, 1, []]] }); + } + + createCell(index: number, source: string[], language: string, type: CellKind, synchronous: boolean) { + const cell = this._model.notebook.createCellTextModel(source, language, type, [], undefined); + let newCell: CellViewModel = createCellViewModel(this.instantiationService, this, cell); + this._viewCells!.splice(index, 0, newCell); + this._model.insertCell(cell, index); + this._localStore.add(newCell); + this.undoService.pushElement(new InsertCellEdit(this.uri, index, newCell, { + insertCell: this._insertCellDelegate.bind(this), + deleteCell: this._deleteCellDelegate.bind(this) + })); + + this._onDidChangeViewCells.fire({ synchronous: synchronous, splices: [[index, 0, [newCell]]] }); + return newCell; + } + + insertCell(index: number, cell: ICell, synchronous: boolean): CellViewModel { + let newCell: CellViewModel = createCellViewModel(this.instantiationService, this, cell); + this._viewCells!.splice(index, 0, newCell); + this._model.insertCell(newCell.cell, index); + this._localStore.add(newCell); + this.undoService.pushElement(new InsertCellEdit(this.uri, index, newCell, { + insertCell: this._insertCellDelegate.bind(this), + deleteCell: this._deleteCellDelegate.bind(this) + })); + + this._onDidChangeViewCells.fire({ synchronous: synchronous, splices: [[index, 0, [newCell]]] }); + return newCell; + } + + deleteCell(index: number, synchronous: boolean) { + let viewCell = this._viewCells[index]; + this._viewCells.splice(index, 1); + this._model.deleteCell(index); + + this.undoService.pushElement(new DeleteCellEdit(this.uri, index, viewCell, { + insertCell: this._insertCellDelegate.bind(this), + deleteCell: this._deleteCellDelegate.bind(this), + createCellViewModel: (cell: ICell) => { + return createCellViewModel(this.instantiationService, this, cell); + } + })); + + this._onDidChangeViewCells.fire({ synchronous: synchronous, splices: [[index, 1, []]] }); + viewCell.dispose(); + } + + moveCellToIdx(index: number, newIdx: number, synchronous: boolean, pushedToUndoStack: boolean = true): boolean { + const viewCell = this.viewCells[index] as CellViewModel; + if (!viewCell) { + return false; + } + + this.viewCells.splice(index, 1); + this._model.deleteCell(index); + + this.viewCells!.splice(newIdx, 0, viewCell); + this._model.insertCell(viewCell.cell, newIdx); + + if (pushedToUndoStack) { + this.undoService.pushElement(new MoveCellEdit(this.uri, index, newIdx, { + moveCell: (fromIndex: number, toIndex: number) => { + this.moveCellToIdx(fromIndex, toIndex, true, false); + } + })); + } + + this._onDidChangeViewCells.fire({ synchronous: synchronous, splices: [[index, 1, []]] }); + this._onDidChangeViewCells.fire({ synchronous: synchronous, splices: [[newIdx, 0, [viewCell]]] }); + + return true; + } + + saveEditorViewState(): INotebookEditorViewState { + const state: { [key: number]: boolean } = {}; + this._viewCells.filter(cell => cell.editState === CellEditState.Editing).forEach(cell => state[cell.cell.handle] = true); + const editorViewStates: { [key: number]: editorCommon.ICodeEditorViewState } = {}; + this._viewCells.map(cell => ({ handle: cell.cell.handle, state: cell.saveEditorViewState() })).forEach(viewState => { + if (viewState.state) { + editorViewStates[viewState.handle] = viewState.state; + } + }); + + return { + editingCells: state, + editorViewStates: editorViewStates + }; + } + + restoreEditorViewState(viewState: INotebookEditorViewState | undefined): void { + if (!viewState) { + return; + } + + this._viewCells.forEach((cell, index) => { + const isEditing = viewState.editingCells && viewState.editingCells[cell.handle]; + const editorViewState = viewState.editorViewStates && viewState.editorViewStates[cell.handle]; + + cell.editState = isEditing ? CellEditState.Editing : CellEditState.Preview; + const cellHeight = viewState.cellTotalHeights ? viewState.cellTotalHeights[index] : undefined; + cell.restoreEditorViewState(editorViewState, cellHeight); + }); + } + + /** + * Editor decorations across cells. For example, find decorations for multiple code cells + * The reason that we can't completely delegate this to CodeEditorWidget is most of the time, the editors for cells are not created yet but we already have decorations for them. + */ + changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T | null { + const changeAccessor: IModelDecorationsChangeAccessor = { + deltaDecorations: (oldDecorations: ICellModelDecorations[], newDecorations: ICellModelDeltaDecorations[]): ICellModelDecorations[] => { + return this.deltaDecorationsImpl(oldDecorations, newDecorations); + } + }; + + let result: T | null = null; + try { + result = callback(changeAccessor); + } catch (e) { + onUnexpectedError(e); + } + + changeAccessor.deltaDecorations = invalidFunc; + + return result; + } + + deltaDecorationsImpl(oldDecorations: ICellModelDecorations[], newDecorations: ICellModelDeltaDecorations[]): ICellModelDecorations[] { + + const mapping = new Map(); + oldDecorations.forEach(oldDecoration => { + const ownerId = oldDecoration.ownerId; + + if (!mapping.has(ownerId)) { + const cell = this._viewCells.find(cell => cell.handle === ownerId); + if (cell) { + mapping.set(ownerId, { cell: cell, oldDecorations: [], newDecorations: [] }); + } + } + + const data = mapping.get(ownerId)!; + if (data) { + data.oldDecorations = oldDecoration.decorations; + } + }); + + newDecorations.forEach(newDecoration => { + const ownerId = newDecoration.ownerId; + + if (!mapping.has(ownerId)) { + const cell = this._viewCells.find(cell => cell.handle === ownerId); + + if (cell) { + mapping.set(ownerId, { cell: cell, oldDecorations: [], newDecorations: [] }); + } + } + + const data = mapping.get(ownerId)!; + if (data) { + data.newDecorations = newDecoration.decorations; + } + }); + + const ret: ICellModelDecorations[] = []; + mapping.forEach((value, ownerId) => { + const cellRet = value.cell.deltaDecorations(value.oldDecorations, value.newDecorations); + ret.push({ + ownerId: ownerId, + decorations: cellRet + }); + }); + + return ret; + } + + + /** + * Search in notebook text model + * @param value + */ + find(value: string): CellFindMatch[] { + const matches: CellFindMatch[] = []; + this._viewCells.forEach(cell => { + const cellMatches = cell.startFind(value); + if (cellMatches) { + matches.push(cellMatches); + } + }); + + return matches; + } + + replaceOne(cell: ICellViewModel, range: Range, text: string): Promise { + const viewCell = cell as CellViewModel; + this._lastNotebookEditResource.push(viewCell.uri); + return viewCell.resolveTextModel().then(() => { + this.bulkEditService.apply({ edits: [{ edit: { range: range, text: text }, resource: cell.uri }] }, { quotableLabel: 'Notebook Replace' }); + }); + } + + async replaceAll(matches: CellFindMatch[], text: string): Promise { + if (!matches.length) { + return; + } + + let textEdits: WorkspaceTextEdit[] = []; + this._lastNotebookEditResource.push(matches[0].cell.uri); + + matches.forEach(match => { + match.matches.forEach(singleMatch => { + textEdits.push({ + edit: { range: singleMatch.range, text: text }, + resource: match.cell.uri + }); + }); + }); + + return Promise.all(matches.map(match => { + return match.cell.resolveTextModel(); + })).then(async () => { + this.bulkEditService.apply({ edits: textEdits }, { quotableLabel: 'Notebook Replace All' }); + return; + }); + } + + canUndo(): boolean { + return this.undoService.canUndo(this.uri); + } + + undo() { + this.undoService.undo(this.uri); + } + + redo() { + this.undoService.redo(this.uri); + } + + equal(model: NotebookEditorModel) { + return this._model === model; + } + + dispose() { + this._localStore.clear(); + this._viewCells.forEach(cell => { + cell.save(); + cell.dispose(); + }); + + super.dispose(); + } +} + +export type CellViewModel = CodeCellViewModel | MarkdownCellViewModel; + +export function createCellViewModel(instantiationService: IInstantiationService, notebookViewModel: NotebookViewModel, cell: ICell) { + if (cell.cellKind === CellKind.Code) { + return instantiationService.createInstance(CodeCellViewModel, notebookViewModel.viewType, notebookViewModel.handle, cell, notebookViewModel.eventDispatcher, notebookViewModel.layoutInfo); + } else { + return instantiationService.createInstance(MarkdownCellViewModel, notebookViewModel.viewType, notebookViewModel.handle, cell, notebookViewModel.eventDispatcher, notebookViewModel.layoutInfo); + } +} diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts new file mode 100644 index 00000000000..9e80924fae3 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts @@ -0,0 +1,97 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import { ICell, IOutput, NotebookCellOutputsSplice, CellKind, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { PieceTreeTextBufferFactory, PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder'; +import { URI } from 'vs/base/common/uri'; + +export class NotebookCellTextModel implements ICell { + private _onDidChangeOutputs = new Emitter(); + onDidChangeOutputs: Event = this._onDidChangeOutputs.event; + + private _onDidChangeContent = new Emitter(); + onDidChangeContent: Event = this._onDidChangeContent.event; + + private _onDidChangeMetadata = new Emitter(); + onDidChangeMetadata: Event = this._onDidChangeMetadata.event; + + private _onDidChangeLanguage = new Emitter(); + onDidChangeLanguage: Event = this._onDidChangeLanguage.event; + + private _outputs: IOutput[]; + + get outputs(): IOutput[] { + return this._outputs; + } + + get source() { + return this._source; + } + + set source(newValue: string[]) { + this._source = newValue; + this._buffer = null; + } + + private _metadata: NotebookCellMetadata | undefined; + + get metadata() { + return this._metadata; + } + + set metadata(newMetadata: NotebookCellMetadata | undefined) { + this._metadata = newMetadata; + this._onDidChangeMetadata.fire(); + } + + get language() { + return this._language; + } + + set language(newLanguage: string) { + this._language = newLanguage; + this._onDidChangeLanguage.fire(newLanguage); + } + + private _buffer: PieceTreeTextBufferFactory | null = null; + + constructor( + readonly uri: URI, + public handle: number, + private _source: string[], + private _language: string, + public cellKind: CellKind, + outputs: IOutput[], + metadata: NotebookCellMetadata | undefined + ) { + this._outputs = outputs; + this._metadata = metadata; + } + + contentChange() { + this._onDidChangeContent.fire(); + + } + + spliceNotebookCellOutputs(splices: NotebookCellOutputsSplice[]): void { + splices.reverse().forEach(splice => { + this.outputs.splice(splice[0], splice[1], ...splice[2]); + }); + + this._onDidChangeOutputs.fire(splices); + } + + resolveTextBufferFactory(): PieceTreeTextBufferFactory { + if (this._buffer) { + return this._buffer; + } + + let builder = new PieceTreeTextBufferBuilder(); + builder.acceptChunk(this.source.join('\n')); + this._buffer = builder.finish(true); + return this._buffer; + } +} diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts new file mode 100644 index 00000000000..dd089ca4434 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -0,0 +1,210 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; +import { INotebookTextModel, NotebookCellOutputsSplice, NotebookCellsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, ICellInsertEdit, NotebookCellsChangedEvent, CellKind, IOutput, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; + +export class NotebookTextModel extends Disposable implements INotebookTextModel { + private static _cellhandlePool: number = 0; + + private readonly _onWillDispose: Emitter = this._register(new Emitter()); + readonly onWillDispose: Event = this._onWillDispose.event; + private readonly _onDidChangeCells = new Emitter(); + get onDidChangeCells(): Event { return this._onDidChangeCells.event; } + private _onDidModelChange = new Emitter(); + get onDidModelChange(): Event { return this._onDidModelChange.event; } + private _onDidChangeContent = new Emitter(); + onDidChangeContent: Event = this._onDidChangeContent.event; + private _onDidChangeMetadata = new Emitter(); + onDidChangeMetadata: Event = this._onDidChangeMetadata.event; + private _mapping: Map = new Map(); + private _cellListeners: Map = new Map(); + cells: NotebookCellTextModel[]; + languages: string[] = []; + metadata: NotebookDocumentMetadata = notebookDocumentMetadataDefaults; + renderers = new Set(); + private _isUntitled: boolean | undefined = undefined; + private _versionId = 0; + + constructor( + public handle: number, + public viewType: string, + public uri: URI + ) { + super(); + this.cells = []; + } + + createCellTextModel( + source: string[], + language: string, + cellKind: CellKind, + outputs: IOutput[], + metadata: NotebookCellMetadata | undefined + ) { + const cellHandle = NotebookTextModel._cellhandlePool++; + const cellUri = CellUri.generate(this.uri, cellHandle); + return new NotebookCellTextModel(URI.revive(cellUri), cellHandle, source, language, cellKind, outputs || [], metadata); + } + + applyEdit(modelVersionId: number, edits: ICellEditOperation[]): boolean { + if (modelVersionId !== this._versionId) { + return false; + } + + for (let i = 0; i < edits.length; i++) { + switch (edits[i].editType) { + case CellEditType.Insert: + const insertEdit = edits[i] as ICellInsertEdit; + const mainCells = insertEdit.cells.map(cell => { + const cellHandle = NotebookTextModel._cellhandlePool++; + const cellUri = CellUri.generate(this.uri, cellHandle); + return new NotebookCellTextModel(URI.revive(cellUri), cellHandle, cell.source, cell.language, cell.cellKind, cell.outputs || [], cell.metadata); + }); + this.insertNewCell(insertEdit.index, mainCells); + break; + case CellEditType.Delete: + this.removeCell(edits[i].index); + break; + } + } + + return true; + } + + private _increaseVersionId(): void { + this._versionId = this._versionId + 1; + } + + updateLanguages(languages: string[]) { + this.languages = languages; + + // TODO@rebornix metadata: default language for cell + if (this._isUntitled && languages.length && this.cells.length) { + this.cells[0].language = languages[0]; + } + } + + updateNotebookMetadata(metadata: NotebookDocumentMetadata) { + this.metadata = metadata; + this._onDidChangeMetadata.fire(this.metadata); + } + + updateNotebookCellMetadata(handle: number, metadata: NotebookCellMetadata) { + const cell = this.cells.find(cell => cell.handle === handle); + + if (cell) { + cell.metadata = metadata; + } + } + + updateRenderers(renderers: number[]) { + renderers.forEach(render => { + this.renderers.add(render); + }); + } + + insertTemplateCell(cell: NotebookCellTextModel) { + if (this.cells.length > 0 || this._isUntitled !== undefined) { + return; + } + + this._isUntitled = true; + this.cells = [cell]; + this._mapping.set(cell.handle, cell); + + let dirtyStateListener = Event.any(cell.onDidChangeContent, cell.onDidChangeOutputs)(() => { + this._isUntitled = false; + this._onDidChangeContent.fire(); + }); + + this._cellListeners.set(cell.handle, dirtyStateListener); + this._onDidChangeContent.fire(); + + this._onDidModelChange.fire({ + versionId: this._versionId, changes: [ + [ + 0, + 0, + [{ + handle: cell.handle, + uri: cell.uri, + source: cell.source, + language: cell.language, + cellKind: cell.cellKind, + outputs: cell.outputs, + metadata: cell.metadata + }] + ] + ] + }); + + return; + } + + insertNewCell(index: number, cells: NotebookCellTextModel[]): void { + this._isUntitled = false; + + for (let i = 0; i < cells.length; i++) { + this._mapping.set(cells[i].handle, cells[i]); + let dirtyStateListener = Event.any(cells[i].onDidChangeContent, cells[i].onDidChangeOutputs)(() => { + this._onDidChangeContent.fire(); + }); + + this._cellListeners.set(cells[i].handle, dirtyStateListener); + } + + this.cells.splice(index, 0, ...cells); + this._onDidChangeContent.fire(); + this._increaseVersionId(); + this._onDidModelChange.fire({ + versionId: this._versionId, changes: [ + [ + index, + 0, + cells.map(cell => ({ + handle: cell.handle, + uri: cell.uri, + source: cell.source, + language: cell.language, + cellKind: cell.cellKind, + outputs: cell.outputs, + metadata: cell.metadata + })) + ] + ] + }); + + return; + } + + removeCell(index: number) { + this._isUntitled = false; + + let cell = this.cells[index]; + this._cellListeners.get(cell.handle)?.dispose(); + this._cellListeners.delete(cell.handle); + this.cells.splice(index, 1); + this._onDidChangeContent.fire(); + + this._increaseVersionId(); + this._onDidModelChange.fire({ versionId: this._versionId, changes: [[index, 1, []]] }); + } + + // TODO@rebornix should this trigger content change event? + $spliceNotebookCellOutputs(cellHandle: number, splices: NotebookCellOutputsSplice[]): void { + let cell = this._mapping.get(cellHandle); + cell?.spliceNotebookCellOutputs(splices); + } + + dispose() { + this._onWillDispose.fire(); + this._cellListeners.forEach(val => val.dispose()); + super.dispose(); + } +} diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts new file mode 100644 index 00000000000..53aee9c9eae --- /dev/null +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -0,0 +1,406 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import * as glob from 'vs/base/common/glob'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { isWindows } from 'vs/base/common/platform'; +import { ISplice } from 'vs/base/common/sequence'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import * as editorCommon from 'vs/editor/common/editorCommon'; +import { PieceTreeTextBufferFactory } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; + +export enum CellKind { + Markdown = 1, + Code = 2 +} + +export enum CellOutputKind { + Text = 1, + Error = 2, + Rich = 3 +} + +export const NOTEBOOK_DISPLAY_ORDER = [ + 'application/json', + 'application/javascript', + 'text/html', + 'image/svg+xml', + 'text/markdown', + 'image/png', + 'image/jpeg', + 'text/plain' +]; + +export const notebookDocumentMetadataDefaults: NotebookDocumentMetadata = { + editable: true, + cellEditable: true, + cellRunnable: true, + hasExecutionOrder: true +}; + +export interface NotebookDocumentMetadata { + editable: boolean; + cellEditable: boolean; + cellRunnable: boolean; + hasExecutionOrder: boolean; +} + +export interface NotebookCellMetadata { + editable?: boolean; + runnable?: boolean; + executionOrder?: number; +} + +export interface INotebookDisplayOrder { + defaultOrder: string[]; + userOrder?: string[]; +} + +export interface INotebookMimeTypeSelector { + type: string; + subTypes?: string[]; +} + +export interface INotebookRendererInfo { + id: ExtensionIdentifier; + extensionLocation: URI, + preloads: URI[] +} + +export interface INotebookSelectors { + readonly filenamePattern?: string; +} + +export interface IStreamOutput { + outputKind: CellOutputKind.Text; + text: string; +} + +export interface IErrorOutput { + outputKind: CellOutputKind.Error; + /** + * Exception Name + */ + ename?: string; + /** + * Exception Value + */ + evalue?: string; + /** + * Exception call stacks + */ + traceback?: string[]; +} + +export interface IDisplayOutput { + outputKind: CellOutputKind.Rich; + /** + * { mime_type: value } + */ + data: { [key: string]: any; } +} + +export enum MimeTypeRendererResolver { + Core, + Active, + Lazy +} + +export interface IOrderedMimeType { + mimeType: string; + isResolved: boolean; + rendererId?: number; + output?: string; +} + +export interface ITransformedDisplayOutputDto { + outputKind: CellOutputKind.Rich; + data: { [key: string]: any; } + + orderedMimeTypes: IOrderedMimeType[]; + pickedMimeTypeIndex: number; +} + +export interface IGenericOutput { + outputKind: CellOutputKind; + pickedMimeType?: string; + pickedRenderer?: number; + transformedOutput?: { [key: string]: IDisplayOutput }; +} + +export type IOutput = ITransformedDisplayOutputDto | IStreamOutput | IErrorOutput; + +export interface ICell { + readonly uri: URI; + handle: number; + source: string[]; + language: string; + cellKind: CellKind; + outputs: IOutput[]; + metadata?: NotebookCellMetadata; + onDidChangeOutputs?: Event; + onDidChangeLanguage: Event; + onDidChangeMetadata: Event; + resolveTextBufferFactory(): PieceTreeTextBufferFactory; + // TODO@rebornix it should be later on replaced by moving textmodel resolution into CellTextModel + contentChange(): void; +} + +export interface LanguageInfo { + file_extension: string; +} + +export interface IMetadata { + language_info: LanguageInfo; +} + +export interface INotebookTextModel { + handle: number; + viewType: string; + // metadata: IMetadata; + readonly uri: URI; + languages: string[]; + cells: ICell[]; + renderers: Set; + onDidChangeCells?: Event; + onDidChangeContent: Event; + onWillDispose(listener: () => void): IDisposable; +} + +export interface IRenderOutput { + shadowContent?: string; + hasDynamicHeight: boolean; +} + +export type NotebookCellsSplice = [ + number /* start */, + number /* delete count */, + ICell[] +]; + +export type NotebookCellOutputsSplice = [ + number /* start */, + number /* delete count */, + IOutput[] +]; + +export interface IMainCellDto { + handle: number; + uri: UriComponents, + source: string[]; + language: string; + cellKind: CellKind; + outputs: IOutput[]; + metadata?: NotebookCellMetadata; +} + +export type NotebookCellsSplice2 = [ + number /* start */, + number /* delete count */, + IMainCellDto[] +]; + +export interface NotebookCellsChangedEvent { + readonly changes: NotebookCellsSplice2[]; + readonly versionId: number; +} + +export enum CellEditType { + Insert = 1, + Delete = 2 +} + +export interface ICellDto2 { + source: string[]; + language: string; + cellKind: CellKind; + outputs: IOutput[]; + metadata?: NotebookCellMetadata; +} + +export interface ICellInsertEdit { + editType: CellEditType.Insert; + index: number; + cells: ICellDto2[]; +} + +export interface ICellDeleteEdit { + editType: CellEditType.Delete; + index: number; + count: number; +} + +export type ICellEditOperation = ICellInsertEdit | ICellDeleteEdit; + +export interface INotebookEditData { + documentVersionId: number; + edits: ICellEditOperation[]; + renderers: number[]; +} + +export namespace CellUri { + + export const scheme = 'vscode-notebook'; + + export function generate(notebook: URI, handle: number): URI { + return notebook.with({ + path: `${notebook.path}#cell-${handle}`, + query: JSON.stringify({ cell: handle, notebook: notebook.toString() }), + scheme, + }); + } + + export function parse(cell: URI): { notebook: URI, handle: number } | undefined { + if (cell.scheme !== scheme) { + return undefined; + } + try { + const data = <{ cell: number, notebook: string }>JSON.parse(cell.query); + return { + handle: data.cell, + notebook: URI.parse(data.notebook) + }; + } catch { + return undefined; + } + } +} + +export function mimeTypeSupportedByCore(mimeType: string) { + if ([ + 'application/json', + 'application/javascript', + 'text/html', + 'image/svg+xml', + 'text/markdown', + 'image/png', + 'image/jpeg', + 'text/plain', + 'text/x-javascript' + ].indexOf(mimeType) > -1) { + return true; + } + + return false; +} + +// if (isWindows) { +// value = value.replace(/\//g, '\\'); +// } + +function matchGlobUniversal(pattern: string, path: string) { + if (isWindows) { + pattern = pattern.replace(/\//g, '\\'); + path = path.replace(/\//g, '\\'); + } + + return glob.match(pattern, path); +} + + +function getMimeTypeOrder(mimeType: string, userDisplayOrder: string[], documentDisplayOrder: string[], defaultOrder: string[]) { + let order = 0; + for (let i = 0; i < userDisplayOrder.length; i++) { + if (matchGlobUniversal(userDisplayOrder[i], mimeType)) { + return order; + } + order++; + } + + for (let i = 0; i < documentDisplayOrder.length; i++) { + if (matchGlobUniversal(documentDisplayOrder[i], mimeType)) { + return order; + } + + order++; + } + + for (let i = 0; i < defaultOrder.length; i++) { + if (matchGlobUniversal(defaultOrder[i], mimeType)) { + return order; + } + + order++; + } + + return order; +} + +export function sortMimeTypes(mimeTypes: string[], userDisplayOrder: string[], documentDisplayOrder: string[], defaultOrder: string[]) { + const sorted = mimeTypes.sort((a, b) => { + return getMimeTypeOrder(a, userDisplayOrder, documentDisplayOrder, defaultOrder) - getMimeTypeOrder(b, userDisplayOrder, documentDisplayOrder, defaultOrder); + }); + + return sorted; +} + +interface IMutableSplice extends ISplice { + deleteCount: number; +} + +export function diff(before: T[], after: T[], contains: (a: T) => boolean): ISplice[] { + const result: IMutableSplice[] = []; + + function pushSplice(start: number, deleteCount: number, toInsert: T[]): void { + if (deleteCount === 0 && toInsert.length === 0) { + return; + } + + const latest = result[result.length - 1]; + + if (latest && latest.start + latest.deleteCount === start) { + latest.deleteCount += deleteCount; + latest.toInsert.push(...toInsert); + } else { + result.push({ start, deleteCount, toInsert }); + } + } + + let beforeIdx = 0; + let afterIdx = 0; + + while (true) { + if (beforeIdx === before.length) { + pushSplice(beforeIdx, 0, after.slice(afterIdx)); + break; + } + + if (afterIdx === after.length) { + pushSplice(beforeIdx, before.length - beforeIdx, []); + break; + } + + const beforeElement = before[beforeIdx]; + const afterElement = after[afterIdx]; + + if (beforeElement === afterElement) { + // equal + beforeIdx += 1; + afterIdx += 1; + continue; + } + + if (contains(afterElement)) { + // `afterElement` exists before, which means some elements before `afterElement` are deleted + pushSplice(beforeIdx, 1, []); + beforeIdx += 1; + } else { + // `afterElement` added + pushSplice(beforeIdx, 0, [afterElement]); + afterIdx += 1; + } + } + + return result; +} + +export interface ICellEditorViewState { + selections: editorCommon.ICursorState[]; +} + +export const NOTEBOOK_EDITOR_CURSOR_BOUNDARY = new RawContextKey<'none' | 'top' | 'bottom' | 'both'>('notebookEditorCursorAtBoundary', 'none'); diff --git a/src/vs/workbench/contrib/notebook/common/notebookOutputRenderer.ts b/src/vs/workbench/contrib/notebook/common/notebookOutputRenderer.ts new file mode 100644 index 00000000000..796de2f51da --- /dev/null +++ b/src/vs/workbench/contrib/notebook/common/notebookOutputRenderer.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. + *--------------------------------------------------------------------------------------------*/ + +import * as glob from 'vs/base/common/glob'; + +export class NotebookOutputRendererInfo { + + readonly id: string; + readonly displayName: string; + readonly mimeTypes: readonly string[]; + readonly mimeTypeGlobs: glob.ParsedPattern[]; + + constructor(descriptor: { + readonly id: string; + readonly displayName: string; + readonly mimeTypes: readonly string[]; + }) { + this.id = descriptor.id; + this.displayName = descriptor.displayName; + this.mimeTypes = descriptor.mimeTypes; + this.mimeTypeGlobs = this.mimeTypes.map(pattern => glob.parse(pattern)); + } + + matches(mimeType: string) { + let matched = this.mimeTypeGlobs.find(pattern => pattern(mimeType)); + return matched; + } +} diff --git a/src/vs/workbench/contrib/notebook/common/notebookProvider.ts b/src/vs/workbench/contrib/notebook/common/notebookProvider.ts new file mode 100644 index 00000000000..f2d5a49b540 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/common/notebookProvider.ts @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as glob from 'vs/base/common/glob'; +import { URI } from 'vs/base/common/uri'; +import { basename } from 'vs/base/common/resources'; + +export interface NotebookSelector { + readonly filenamePattern?: string; + readonly excludeFileNamePattern?: string; +} + +export class NotebookProviderInfo { + + readonly id: string; + readonly displayName: string; + readonly selector: readonly NotebookSelector[]; + + constructor(descriptor: { + readonly id: string; + readonly displayName: string; + readonly selector: readonly NotebookSelector[]; + }) { + this.id = descriptor.id; + this.displayName = descriptor.displayName; + this.selector = descriptor.selector; + } + + matches(resource: URI): boolean { + return this.selector.some(selector => NotebookProviderInfo.selectorMatches(selector, resource)); + } + + static selectorMatches(selector: NotebookSelector, resource: URI): boolean { + if (selector.filenamePattern) { + if (glob.match(selector.filenamePattern.toLowerCase(), basename(resource).toLowerCase())) { + if (selector.excludeFileNamePattern) { + if (glob.match(selector.excludeFileNamePattern.toLowerCase(), basename(resource).toLowerCase())) { + // should exclude + + return false; + } + } + return true; + } + } + return false; + } +} diff --git a/src/vs/workbench/contrib/notebook/electron-browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/electron-browser/notebook.contribution.ts new file mode 100644 index 00000000000..189151fa9b7 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/electron-browser/notebook.contribution.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { isMacintosh } from 'vs/base/common/platform'; +import { registerAction2 } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import * as webviewCommands from 'vs/workbench/contrib/webview/electron-browser/webviewCommands'; +import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ElectronWebviewBasedWebview } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { getActiveNotebookEditor } from 'vs/workbench/contrib/notebook/browser/contrib/notebookActions'; + +function getActiveElectronBasedWebviewDelegate(accessor: ServicesAccessor): ElectronWebviewBasedWebview | undefined { + + const editorService = accessor.get(IEditorService); + const editor = getActiveNotebookEditor(editorService); + + const webview = editor?.getInnerWebview(); + + if (webview && webview instanceof ElectronWebviewBasedWebview) { + return webview; + } + + return; +} + +function registerNotebookCommands(editorId: string): void { + const contextKeyExpr = ContextKeyExpr.and(ContextKeyExpr.equals('activeEditor', editorId), ContextKeyExpr.not('editorFocus') /* https://github.com/Microsoft/vscode/issues/58668 */)!; + + // These commands are only needed on MacOS where we have to disable the menu bar commands + if (isMacintosh) { + registerAction2(class extends webviewCommands.CopyWebviewEditorCommand { constructor() { super(contextKeyExpr, getActiveElectronBasedWebviewDelegate); } }); + registerAction2(class extends webviewCommands.PasteWebviewEditorCommand { constructor() { super(contextKeyExpr, getActiveElectronBasedWebviewDelegate); } }); + registerAction2(class extends webviewCommands.CutWebviewEditorCommand { constructor() { super(contextKeyExpr, getActiveElectronBasedWebviewDelegate); } }); + registerAction2(class extends webviewCommands.UndoWebviewEditorCommand { constructor() { super(contextKeyExpr, getActiveElectronBasedWebviewDelegate); } }); + registerAction2(class extends webviewCommands.RedoWebviewEditorCommand { constructor() { super(contextKeyExpr, getActiveElectronBasedWebviewDelegate); } }); + } +} + +registerNotebookCommands(NotebookEditor.ID); diff --git a/src/vs/workbench/contrib/notebook/test/notebookCommon.test.ts b/src/vs/workbench/contrib/notebook/test/notebookCommon.test.ts new file mode 100644 index 00000000000..11f71061e37 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/test/notebookCommon.test.ts @@ -0,0 +1,341 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { NOTEBOOK_DISPLAY_ORDER, sortMimeTypes, CellKind, diff, CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { TestCell } from 'vs/workbench/contrib/notebook/test/testNotebookEditor'; +import { URI } from 'vs/base/common/uri'; + +suite('NotebookCommon', () => { + test('sortMimeTypes default orders', function () { + const defaultDisplayOrder = NOTEBOOK_DISPLAY_ORDER; + + assert.deepEqual(sortMimeTypes( + [ + 'application/json', + 'application/javascript', + 'text/html', + 'image/svg+xml', + 'text/markdown', + 'image/png', + 'image/jpeg', + 'text/plain' + ], [], [], defaultDisplayOrder), + [ + 'application/json', + 'application/javascript', + 'text/html', + 'image/svg+xml', + 'text/markdown', + 'image/png', + 'image/jpeg', + 'text/plain' + ] + ); + + assert.deepEqual(sortMimeTypes( + [ + 'application/json', + 'text/markdown', + 'application/javascript', + 'text/html', + 'text/plain', + 'image/png', + 'image/jpeg', + 'image/svg+xml' + ], [], [], defaultDisplayOrder), + [ + 'application/json', + 'application/javascript', + 'text/html', + 'image/svg+xml', + 'text/markdown', + 'image/png', + 'image/jpeg', + 'text/plain' + ] + ); + + assert.deepEqual(sortMimeTypes( + [ + 'text/markdown', + 'application/json', + 'text/plain', + 'image/jpeg', + 'application/javascript', + 'text/html', + 'image/png', + 'image/svg+xml' + ], [], [], defaultDisplayOrder), + [ + 'application/json', + 'application/javascript', + 'text/html', + 'image/svg+xml', + 'text/markdown', + 'image/png', + 'image/jpeg', + 'text/plain' + ] + ); + }); + + test('sortMimeTypes document orders', function () { + const defaultDisplayOrder = NOTEBOOK_DISPLAY_ORDER; + assert.deepEqual(sortMimeTypes( + [ + 'application/json', + 'application/javascript', + 'text/html', + 'image/svg+xml', + 'text/markdown', + 'image/png', + 'image/jpeg', + 'text/plain' + ], [], + [ + 'text/markdown', + 'text/html', + 'application/json' + ], defaultDisplayOrder), + [ + 'text/markdown', + 'text/html', + 'application/json', + 'application/javascript', + 'image/svg+xml', + 'image/png', + 'image/jpeg', + 'text/plain' + ] + ); + + assert.deepEqual(sortMimeTypes( + [ + 'text/markdown', + 'application/json', + 'text/plain', + 'application/javascript', + 'text/html', + 'image/svg+xml', + 'image/jpeg', + 'image/png' + ], [], + [ + 'text/html', + 'text/markdown', + 'application/json' + ], defaultDisplayOrder), + [ + 'text/html', + 'text/markdown', + 'application/json', + 'application/javascript', + 'image/svg+xml', + 'image/png', + 'image/jpeg', + 'text/plain' + ] + ); + }); + + test('sortMimeTypes user orders', function () { + const defaultDisplayOrder = NOTEBOOK_DISPLAY_ORDER; + assert.deepEqual(sortMimeTypes( + [ + 'application/json', + 'application/javascript', + 'text/html', + 'image/svg+xml', + 'text/markdown', + 'image/png', + 'image/jpeg', + 'text/plain' + ], + [ + 'image/png', + 'text/plain', + ], + [ + 'text/markdown', + 'text/html', + 'application/json' + ], defaultDisplayOrder), + [ + 'image/png', + 'text/plain', + 'text/markdown', + 'text/html', + 'application/json', + 'application/javascript', + 'image/svg+xml', + 'image/jpeg', + ] + ); + + assert.deepEqual(sortMimeTypes( + [ + 'text/markdown', + 'application/json', + 'text/plain', + 'application/javascript', + 'text/html', + 'image/svg+xml', + 'image/jpeg', + 'image/png' + ], + [ + 'application/json', + 'text/html', + ], + [ + 'text/html', + 'text/markdown', + 'application/json' + ], defaultDisplayOrder), + [ + 'application/json', + 'text/html', + 'text/markdown', + 'application/javascript', + 'image/svg+xml', + 'image/png', + 'image/jpeg', + 'text/plain' + ] + ); + }); + + test('sortMimeTypes glob', function () { + const defaultDisplayOrder = NOTEBOOK_DISPLAY_ORDER; + + // unknown mime types come last + assert.deepEqual(sortMimeTypes( + [ + 'application/json', + 'application/vnd-vega.json', + 'application/vnd-plot.json', + 'application/javascript', + 'text/html' + ], [], + [ + 'text/markdown', + 'text/html', + 'application/json' + ], defaultDisplayOrder), + [ + 'text/html', + 'application/json', + 'application/javascript', + 'application/vnd-vega.json', + 'application/vnd-plot.json' + ], + 'unknown mimetypes keep the ordering' + ); + + assert.deepEqual(sortMimeTypes( + [ + 'application/json', + 'application/javascript', + 'text/html', + 'application/vnd-plot.json', + 'application/vnd-vega.json' + ], [], + [ + 'application/vnd-vega*', + 'text/markdown', + 'text/html', + 'application/json' + ], defaultDisplayOrder), + [ + 'application/vnd-vega.json', + 'text/html', + 'application/json', + 'application/javascript', + 'application/vnd-plot.json' + ], + 'glob *' + ); + }); + + test('diff cells', function () { + const cells: TestCell[] = []; + + for (let i = 0; i < 5; i++) { + cells.push( + new TestCell('notebook', i, [`var a = ${i};`], 'javascript', CellKind.Code, []) + ); + } + + assert.deepEqual(diff(cells, [], (cell) => { + return cells.indexOf(cell) > -1; + }), [ + { + start: 0, + deleteCount: 5, + toInsert: [] + } + ] + ); + + assert.deepEqual(diff([], cells, (cell) => { + return false; + }), [ + { + start: 0, + deleteCount: 0, + toInsert: cells + } + ] + ); + + const cellA = new TestCell('notebook', 6, ['var a = 6;'], 'javascript', CellKind.Code, []); + const cellB = new TestCell('notebook', 7, ['var a = 7;'], 'javascript', CellKind.Code, []); + + const modifiedCells = [ + cells[0], + cells[1], + cellA, + cells[3], + cellB, + cells[4] + ]; + + const splices = diff(cells, modifiedCells, (cell) => { + return cells.indexOf(cell) > -1; + }); + + assert.deepEqual(splices, + [ + { + start: 2, + deleteCount: 1, + toInsert: [cellA] + }, + { + start: 4, + deleteCount: 0, + toInsert: [cellB] + } + ] + ); + }); +}); + + +suite('CellUri', function () { + + test('parse, generate', function () { + + const nb = URI.parse('foo:///bar/følder/file.nb'); + const id = 17; + + const data = CellUri.generate(nb, id); + const actual = CellUri.parse(data); + assert.ok(Boolean(actual)); + assert.equal(actual?.handle, id); + assert.equal(actual?.notebook.toString(), nb.toString()); + }); +}); diff --git a/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts b/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts new file mode 100644 index 00000000000..2d905ea506a --- /dev/null +++ b/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts @@ -0,0 +1,163 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { URI } from 'vs/base/common/uri'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; +import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; +import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { withTestNotebook, TestCell } from 'vs/workbench/contrib/notebook/test/testNotebookEditor'; +import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; +import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { NotebookEventDispatcher } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; + +suite('NotebookViewModel', () => { + const instantiationService = new TestInstantiationService(); + const blukEditService = instantiationService.get(IBulkEditService); + const undoRedoService = instantiationService.stub(IUndoRedoService, () => { }); + instantiationService.spy(IUndoRedoService, 'pushElement'); + + test('ctor', function () { + const notebook = new NotebookTextModel(0, 'notebook', URI.parse('test')); + const model = new NotebookEditorModel(notebook); + const eventDispatcher = new NotebookEventDispatcher(); + const viewModel = new NotebookViewModel('notebook', model, eventDispatcher, null, instantiationService, blukEditService, undoRedoService); + assert.equal(viewModel.viewType, 'notebook'); + }); + + test('insert/delete', function () { + withTestNotebook( + instantiationService, + blukEditService, + undoRedoService, + [ + [['var a = 1;'], 'javascript', CellKind.Code, [], { editable: true }], + [['var b = 2;'], 'javascript', CellKind.Code, [], { editable: false }] + ], + (editor, viewModel) => { + assert.equal(viewModel.viewCells[0].metadata?.editable, true); + assert.equal(viewModel.viewCells[1].metadata?.editable, false); + + const cell = viewModel.insertCell(1, new TestCell(viewModel.viewType, 0, ['var c = 3;'], 'javascript', CellKind.Code, []), true); + assert.equal(viewModel.viewCells.length, 3); + assert.equal(viewModel.notebookDocument.cells.length, 3); + assert.equal(viewModel.getViewCellIndex(cell), 1); + + viewModel.deleteCell(1, true); + assert.equal(viewModel.viewCells.length, 2); + assert.equal(viewModel.notebookDocument.cells.length, 2); + assert.equal(viewModel.getViewCellIndex(cell), -1); + } + ); + }); + + test('index', function () { + withTestNotebook( + instantiationService, + blukEditService, + undoRedoService, + [ + [['var a = 1;'], 'javascript', CellKind.Code, [], { editable: true }], + [['var b = 2;'], 'javascript', CellKind.Code, [], { editable: true }] + ], + (editor, viewModel) => { + const firstViewCell = viewModel.viewCells[0]; + const lastViewCell = viewModel.viewCells[viewModel.viewCells.length - 1]; + + const insertIndex = viewModel.getViewCellIndex(firstViewCell) + 1; + const cell = viewModel.insertCell(insertIndex, new TestCell(viewModel.viewType, 3, ['var c = 3;'], 'javascript', CellKind.Code, []), true); + + const addedCellIndex = viewModel.getViewCellIndex(cell); + viewModel.deleteCell(addedCellIndex, true); + + const secondInsertIndex = viewModel.getViewCellIndex(lastViewCell) + 1; + const cell2 = viewModel.insertCell(secondInsertIndex, new TestCell(viewModel.viewType, 4, ['var d = 4;'], 'javascript', CellKind.Code, []), true); + + assert.equal(viewModel.viewCells.length, 3); + assert.equal(viewModel.notebookDocument.cells.length, 3); + assert.equal(viewModel.getViewCellIndex(cell2), 2); + } + ); + }); + + test('metadata', function () { + withTestNotebook( + instantiationService, + blukEditService, + undoRedoService, + [ + [['var a = 1;'], 'javascript', CellKind.Code, [], {}], + [['var b = 2;'], 'javascript', CellKind.Code, [], { editable: true, runnable: true }], + [['var c = 3;'], 'javascript', CellKind.Code, [], { editable: true, runnable: false }], + [['var d = 4;'], 'javascript', CellKind.Code, [], { editable: false, runnable: true }], + [['var e = 5;'], 'javascript', CellKind.Code, [], { editable: false, runnable: false }], + ], + (editor, viewModel) => { + viewModel.notebookDocument.metadata = { editable: true, cellRunnable: true, cellEditable: true, hasExecutionOrder: true }; + + assert.deepEqual(viewModel.viewCells[0].getEvaluatedMetadata(viewModel.metadata), { + editable: true, + runnable: true + }); + + assert.deepEqual(viewModel.viewCells[1].getEvaluatedMetadata(viewModel.metadata), { + editable: true, + runnable: true + }); + + assert.deepEqual(viewModel.viewCells[2].getEvaluatedMetadata(viewModel.metadata), { + editable: true, + runnable: false + }); + + assert.deepEqual(viewModel.viewCells[3].getEvaluatedMetadata(viewModel.metadata), { + editable: false, + runnable: true + }); + + assert.deepEqual(viewModel.viewCells[4].getEvaluatedMetadata(viewModel.metadata), { + editable: false, + runnable: false + }); + + viewModel.notebookDocument.metadata = { editable: true, cellRunnable: false, cellEditable: true, hasExecutionOrder: true }; + + assert.deepEqual(viewModel.viewCells[0].getEvaluatedMetadata(viewModel.metadata), { + editable: true, + runnable: false + }); + + assert.deepEqual(viewModel.viewCells[1].getEvaluatedMetadata(viewModel.metadata), { + editable: true, + runnable: true + }); + + assert.deepEqual(viewModel.viewCells[2].getEvaluatedMetadata(viewModel.metadata), { + editable: true, + runnable: false + }); + + assert.deepEqual(viewModel.viewCells[3].getEvaluatedMetadata(viewModel.metadata), { + editable: false, + runnable: true + }); + + assert.deepEqual(viewModel.viewCells[4].getEvaluatedMetadata(viewModel.metadata), { + editable: false, + runnable: false + }); + + viewModel.notebookDocument.metadata = { editable: true, cellRunnable: false, cellEditable: false, hasExecutionOrder: true }; + + assert.deepEqual(viewModel.viewCells[0].getEvaluatedMetadata(viewModel.metadata), { + editable: false, + runnable: false + }); + } + ); + }); +}); diff --git a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts new file mode 100644 index 00000000000..a0f238ebf63 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts @@ -0,0 +1,235 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import { URI } from 'vs/base/common/uri'; +import { PieceTreeTextBufferFactory } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder'; +import { CellKind, ICell, IOutput, NotebookCellOutputsSplice, CellUri, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookViewModel, IModelDecorationsChangeAccessor, CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; +import { INotebookEditor, NotebookLayoutInfo, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; +import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; +import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; +import { Range } from 'vs/editor/common/core/range'; +import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; +import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; +import { NotebookEventDispatcher } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; +import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; + +export class TestCell implements ICell { + uri: URI; + private _onDidChangeContent = new Emitter(); + onDidChangeContent: Event = this._onDidChangeContent.event; + + private _onDidChangeOutputs = new Emitter(); + onDidChangeOutputs: Event = this._onDidChangeOutputs.event; + private _onDidChangeLanguage = new Emitter(); + onDidChangeLanguage: Event = this._onDidChangeLanguage.event; + private _onDidChangeMetadata = new Emitter(); + onDidChangeMetadata: Event = this._onDidChangeMetadata.event; + private _isDirty: boolean = false; + private _outputs: IOutput[]; + + get metadata(): NotebookCellMetadata { + return { editable: true }; + } + + get outputs(): IOutput[] { + return this._outputs; + } + + get isDirty() { + return this._isDirty; + } + + set isDirty(newState: boolean) { + this._isDirty = newState; + + } + + constructor( + public viewType: string, + public handle: number, + public source: string[], + public language: string, + public cellKind: CellKind, + outputs: IOutput[] + ) { + this._outputs = outputs; + this.uri = CellUri.generate(URI.parse('test:///fake/notebook'), handle); + } + contentChange(): void { + // throw new Error('Method not implemented.'); + } + + resolveTextBufferFactory(): PieceTreeTextBufferFactory { + throw new Error('Method not implemented.'); + } +} + +export class TestNotebookEditor implements INotebookEditor { + + get viewModel() { + return undefined; + } + + constructor( + ) { } + getInnerWebview(): Webview | undefined { + throw new Error('Method not implemented.'); + } + + cancelNotebookCellExecution(cell: ICellViewModel): void { + throw new Error('Method not implemented.'); + } + + executeNotebook(): Promise { + throw new Error('Method not implemented.'); + } + + cancelNotebookExecution(): void { + throw new Error('Method not implemented.'); + } + + executeNotebookCell(cell: ICellViewModel): Promise { + throw new Error('Method not implemented.'); + } + + isNotebookEditor = true; + + postMessage(message: any): void { + throw new Error('Method not implemented.'); + } + + setCellSelection(cell: CellViewModel, selection: Range): void { + throw new Error('Method not implemented.'); + } + + selectElement(cell: CellViewModel): void { + throw new Error('Method not implemented.'); + } + + moveCellDown(cell: CellViewModel): void { + throw new Error('Method not implemented.'); + } + + moveCellUp(cell: CellViewModel): void { + throw new Error('Method not implemented.'); + } + + setSelection(cell: CellViewModel, selection: Range): void { + throw new Error('Method not implemented.'); + } + revealRangeInView(cell: CellViewModel, range: Range): void { + throw new Error('Method not implemented.'); + } + revealRangeInCenter(cell: CellViewModel, range: Range): void { + throw new Error('Method not implemented.'); + } + revealRangeInCenterIfOutsideViewport(cell: CellViewModel, range: Range): void { + throw new Error('Method not implemented.'); + } + + revealLineInView(cell: CellViewModel, line: number): void { + throw new Error('Method not implemented.'); + } + getLayoutInfo(): NotebookLayoutInfo { + throw new Error('Method not implemented.'); + } + revealLineInCenterIfOutsideViewport(cell: CellViewModel, line: number): void { + throw new Error('Method not implemented.'); + } + revealLineInCenter(cell: CellViewModel, line: number): void { + throw new Error('Method not implemented.'); + } + focus(): void { + throw new Error('Method not implemented.'); + } + showFind(): void { + throw new Error('Method not implemented.'); + } + hideFind(): void { + throw new Error('Method not implemented.'); + } + revealInView(cell: CellViewModel): void { + throw new Error('Method not implemented.'); + } + revealInCenter(cell: CellViewModel): void { + throw new Error('Method not implemented.'); + } + revealInCenterIfOutsideViewport(cell: CellViewModel): void { + throw new Error('Method not implemented.'); + } + async insertNotebookCell(cell: CellViewModel, type: CellKind, direction: 'above' | 'below'): Promise { + // throw new Error('Method not implemented.'); + } + deleteNotebookCell(cell: CellViewModel): void { + // throw new Error('Method not implemented.'); + } + editNotebookCell(cell: CellViewModel): void { + // throw new Error('Method not implemented.'); + } + saveNotebookCell(cell: CellViewModel): void { + // throw new Error('Method not implemented.'); + } + focusNotebookCell(cell: CellViewModel, focusEditor: boolean): void { + // throw new Error('Method not implemented.'); + } + getActiveCell(): CellViewModel | undefined { + // throw new Error('Method not implemented.'); + return; + } + async layoutNotebookCell(cell: CellViewModel, height: number): Promise { + // throw new Error('Method not implemented.'); + return; + } + createInset(cell: CellViewModel, output: IOutput, shadowContent: string, offset: number): void { + // throw new Error('Method not implemented.'); + } + removeInset(output: IOutput): void { + // throw new Error('Method not implemented.'); + } + triggerScroll(event: IMouseWheelEvent): void { + // throw new Error('Method not implemented.'); + } + getFontInfo(): BareFontInfo | undefined { + return BareFontInfo.createFromRawSettings({ + fontFamily: 'Monaco', + }, 1, true); + } + getOutputRenderer(): OutputRenderer { + throw new Error('Method not implemented.'); + } + + changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => any): any { + throw new Error('Method not implemented.'); + } +} + +// export function createTestCellViewModel(instantiationService: IInstantiationService, viewType: string, notebookHandle: number, cellhandle: number, source: string[], language: string, cellKind: CellKind, outputs: IOutput[]) { +// const mockCell = new TestCell(viewType, cellhandle, source, language, cellKind, outputs); +// return createCellViewModel(instantiationService, viewType, notebookHandle, mockCell); +// } + +export function withTestNotebook(instantiationService: IInstantiationService, blukEditService: IBulkEditService, undoRedoService: IUndoRedoService, cells: [string[], string, CellKind, IOutput[], NotebookCellMetadata][], callback: (editor: TestNotebookEditor, viewModel: NotebookViewModel) => void) { + const viewType = 'notebook'; + const editor = new TestNotebookEditor(); + const notebook = new NotebookTextModel(0, viewType, URI.parse('test')); + notebook.cells = cells.map((cell, index) => { + return new NotebookCellTextModel(notebook.uri, index, cell[0], cell[1], cell[2], cell[3], cell[4]); + }); + const model = new NotebookEditorModel(notebook); + const eventDispatcher = new NotebookEventDispatcher(); + const viewModel = new NotebookViewModel(viewType, model, eventDispatcher, null, instantiationService, blukEditService, undoRedoService); + + callback(editor, viewModel); + + viewModel.dispose(); + return; +} diff --git a/src/vs/workbench/contrib/openInDesktop/browser/openInDesktop.web.contribution.ts b/src/vs/workbench/contrib/openInDesktop/browser/openInDesktop.web.contribution.ts deleted file mode 100644 index 9854d0f0628..00000000000 --- a/src/vs/workbench/contrib/openInDesktop/browser/openInDesktop.web.contribution.ts +++ /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. - *--------------------------------------------------------------------------------------------*/ - -import { localize } from 'vs/nls'; -import { STATUS_BAR_PROMINENT_ITEM_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_FOREGROUND } from 'vs/workbench/common/theme'; -import { themeColorFromId } from 'vs/platform/theme/common/themeService'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchContributionsExtensions } from 'vs/workbench/common/contributions'; -import { StatusbarAlignment, IStatusbarService, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; -import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { Action } from 'vs/base/common/actions'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IApplicationLink } from 'vs/workbench/workbench.web.api'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; - -export class OpenInDesktopIndicator extends Disposable implements IWorkbenchContribution { - - constructor( - @IStatusbarService private readonly statusbarService: IStatusbarService, - @IWorkspaceContextService workspaceService: IWorkspaceContextService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService - ) { - super(); - - const links = environmentService.options?.applicationLinks; - if (Array.isArray(links) && links?.length > 0) { - this.installOpenInDesktopIndicator(links); - } - } - - private installOpenInDesktopIndicator(links: readonly IApplicationLink[]): void { - - // Register action to trigger "Open In Desktop" - const registry = Registry.as(ActionExtensions.WorkbenchActions); - registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenInDesktopAction, OpenInDesktopAction.ID, OpenInDesktopAction.LABEL), 'Open Workspace in Desktop'); - - // Show in status bar - const properties: IStatusbarEntry = { - backgroundColor: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_BACKGROUND), - color: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_FOREGROUND), - text: links.length === 1 ? links[0].label : localize('openInDesktop', "Open in Desktop..."), - command: 'workbench.web.openWorkspaceInDesktop' - }; - - this.statusbarService.addEntry(properties, 'status.openInDesktop', properties.text, StatusbarAlignment.LEFT, Number.MAX_VALUE /* first entry */); - } -} - -const workbenchContributionsRegistry = Registry.as(WorkbenchContributionsExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution(OpenInDesktopIndicator, LifecyclePhase.Starting); - -export class OpenInDesktopAction extends Action { - static readonly ID = 'workbench.web.openWorkspaceInDesktop'; - static readonly LABEL = localize('openWorkspaceInDesktop', "Open Workspace in Desktop"); - - constructor( - id: string, - label: string, - @IQuickInputService private readonly quickInputService: IQuickInputService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IOpenerService private readonly openerService: IOpenerService - ) { - super(id, label); - } - - async run(): Promise { - const links = this.environmentService.options?.applicationLinks; - if (Array.isArray(links)) { - if (links.length === 1) { - return this.openApplicationLink(links[0]); - } - - return this.runWithPicker(links); - } - - return true; - } - - private async runWithPicker(links: readonly IApplicationLink[]): Promise { - - // Show a picker with choices - const quickPick = this.quickInputService.createQuickPick(); - quickPick.items = links; - quickPick.placeholder = OpenInDesktopAction.LABEL; - quickPick.canSelectMany = false; - quickPick.onDidAccept(() => { - const selectedItems = quickPick.selectedItems; - if (selectedItems.length === 1) { - this.openApplicationLink(selectedItems[0]); - } - quickPick.hide(); - }); - - quickPick.show(); - - return true; - } - - private async openApplicationLink(link: IApplicationLink): Promise { - this.openerService.open(link.uri, { openExternal: true }); - - return true; - } -} diff --git a/src/vs/workbench/contrib/outline/browser/outlinePane.css b/src/vs/workbench/contrib/outline/browser/outlinePane.css index 4f443d9f29e..bbdc8af1186 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePane.css +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.css @@ -44,14 +44,3 @@ .monaco-workbench .outline-pane.message .outline-tree { display: none; } - -.monaco-tree.focused .selected .outline-element-label, .monaco-tree.focused .selected .outline-element-decoration{ - /* make sure selection color wins when a label is being selected */ - color: inherit !important; -} - -.monaco-tree.focused .selected .outline-element-label .monaco-highlighted-label .highlight, -.monaco-tree.focused .selected .monaco-icon-label .monaco-highlighted-label .highlight{ - /* allows text color to use the default when selected */ - color: inherit !important; -} diff --git a/src/vs/workbench/contrib/outline/browser/outlinePane.ts b/src/vs/workbench/contrib/outline/browser/outlinePane.ts index 906dc9572d8..6083a6804a3 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePane.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.ts @@ -95,12 +95,12 @@ class RequestOracle { private _update(): void { - let widget = this._editorService.activeTextEditorWidget; + let control = this._editorService.activeTextEditorControl; let codeEditor: ICodeEditor | undefined = undefined; - if (isCodeEditor(widget)) { - codeEditor = widget; - } else if (isDiffEditor(widget)) { - codeEditor = widget.getModifiedEditor(); + if (isCodeEditor(control)) { + codeEditor = control; + } else if (isDiffEditor(control)) { + codeEditor = control.getModifiedEditor(); } if (!codeEditor || !codeEditor.hasModel()) { diff --git a/src/vs/workbench/contrib/output/browser/media/output.css b/src/vs/workbench/contrib/output/browser/media/output.css new file mode 100644 index 00000000000..78d3c1fd803 --- /dev/null +++ b/src/vs/workbench/contrib/output/browser/media/output.css @@ -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. + *--------------------------------------------------------------------------------------------*/ + +.monaco-workbench .part > .title > .title-actions .switch-output { + display: flex; + align-items: center; + font-size: 11px; + margin-right: 0.3em; + height: 20px; + flex-shrink: 1; + margin-top: 7px; +} + +.monaco-workbench.mac .part > .title > .title-actions .switch-output { + border-radius: 4px; +} + +.monaco-workbench .part > .title > .title-actions .switch-output > .monaco-select-box { + border: none !important; + display: block !important; + background-color: unset !important; +} + +.monaco-pane-view .pane > .pane-header .monaco-action-bar .switch-output.action-item.select-container { + border: none !important; +} + +.monaco-workbench .part > .title > .title-actions .switch-output > .monaco-select-box { + padding: 0 22px 0 6px; +} diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index 90a0493c1e3..b9961b11297 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -4,15 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; +import * as aria from 'vs/base/browser/ui/aria/aria'; +import 'vs/css!./media/output'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; -import { MenuId, MenuRegistry, SyncActionDescriptor, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; +import { MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { OutputService, LogContentProvider } from 'vs/workbench/contrib/output/browser/outputServices'; -import { ToggleOutputAction, ClearOutputAction, OpenLogOutputFile, ShowLogsOutputChannelAction, OpenOutputLogFileAction } from 'vs/workbench/contrib/output/browser/outputActions'; -import { OUTPUT_MODE_ID, OUTPUT_MIME, OUTPUT_VIEW_ID, IOutputService, CONTEXT_IN_OUTPUT, LOG_SCHEME, LOG_MODE_ID, LOG_MIME, CONTEXT_ACTIVE_LOG_OUTPUT } from 'vs/workbench/contrib/output/common/output'; +import { OUTPUT_MODE_ID, OUTPUT_MIME, OUTPUT_VIEW_ID, IOutputService, CONTEXT_IN_OUTPUT, LOG_SCHEME, LOG_MODE_ID, LOG_MIME, CONTEXT_ACTIVE_LOG_OUTPUT, CONTEXT_OUTPUT_SCROLL_LOCK } from 'vs/workbench/contrib/output/common/output'; import { OutputViewPane } from 'vs/workbench/contrib/output/browser/outputView'; import { IEditorRegistry, Extensions as EditorExtensions, EditorDescriptor } from 'vs/workbench/browser/editor'; import { LogViewer, LogViewerInput } from 'vs/workbench/contrib/output/browser/logViewer'; @@ -21,8 +21,17 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWo import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { ViewContainer, IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions, IViewsRegistry } from 'vs/workbench/common/views'; +import { ViewContainer, IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions, IViewsRegistry, IViewsService, IViewDescriptorService } from 'vs/workbench/common/views'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IOutputChannelDescriptor, IFileOutputChannelDescriptor } from 'vs/workbench/services/output/common/output'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { assertIsDefined } from 'vs/base/common/types'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { ContextKeyEqualsExpr, ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ToggleViewAction } from 'vs/workbench/browser/actions/layoutActions'; // Register Service registerSingleton(IOutputService, OutputService); @@ -42,23 +51,27 @@ ModesRegistry.registerLanguage({ }); // register output container +const toggleOutputAcitonId = 'workbench.action.output.toggleOutput'; +const toggleOutputActionKeybindings = { + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_U, + linux: { + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_H) // On Ubuntu Ctrl+Shift+U is taken by some global OS command + } +}; const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: OUTPUT_VIEW_ID, name: nls.localize('output', "Output"), + order: 1, ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [OUTPUT_VIEW_ID, OUTPUT_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), - focusCommand: { - id: ToggleOutputAction.ID, keybindings: { - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_U, - linux: { - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_H) // On Ubuntu Ctrl+Shift+U is taken by some global OS command - } - } - } + hideIfEmpty: true, + focusCommand: { id: toggleOutputAcitonId, keybindings: toggleOutputActionKeybindings } }, ViewContainerLocation.Panel); Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ id: OUTPUT_VIEW_ID, name: nls.localize('output', "Output"), + containerIcon: 'codicon-output', + canMoveView: true, canToggleVisibility: false, ctorDescriptor: new SyncDescriptor(OutputViewPane), }], VIEW_CONTAINER); @@ -85,63 +98,226 @@ class OutputContribution implements IWorkbenchContribution { Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(OutputContribution, LifecyclePhase.Restored); -// register toggle output action globally -const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleOutputAction, ToggleOutputAction.ID, ToggleOutputAction.LABEL, { - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_U, - linux: { - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_H) // On Ubuntu Ctrl+Shift+U is taken by some global OS command - } -}), 'View: Toggle Output', nls.localize('viewCategory', "View")); - -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ClearOutputAction, ClearOutputAction.ID, ClearOutputAction.LABEL), - 'View: Clear Output', nls.localize('viewCategory', "View")); - -const devCategory = nls.localize('developer', "Developer"); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ShowLogsOutputChannelAction, ShowLogsOutputChannelAction.ID, ShowLogsOutputChannelAction.LABEL), 'Developer: Show Logs...', devCategory); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(OpenOutputLogFileAction, OpenOutputLogFileAction.ID, OpenOutputLogFileAction.LABEL), 'Developer: Open Log File...', devCategory); - -// Define clear command, contribute to editor context menu registerAction2(class extends Action2 { constructor() { super({ - id: 'editor.action.clearoutput', - title: { value: nls.localize('clearOutput.label', "Clear Output"), original: 'Clear Output' }, + id: `workbench.output.action.switchBetweenOutputs`, + title: nls.localize('switchToOutput.label', "Switch to Output"), menu: { - id: MenuId.EditorContext, - when: CONTEXT_IN_OUTPUT + id: MenuId.ViewTitle, + when: ContextKeyEqualsExpr.create('view', OUTPUT_VIEW_ID), + group: 'navigation', + order: 1 }, }); } - run(accessor: ServicesAccessor) { - const activeChannel = accessor.get(IOutputService).getActiveChannel(); + async run(accessor: ServicesAccessor, channelId: string): Promise { + accessor.get(IOutputService).showChannel(channelId); + } +}); +registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.output.action.clearOutput`, + title: { value: nls.localize('clearOutput.label', "Clear Output"), original: 'Clear Output' }, + category: nls.localize('viewCategory', "View"), + menu: [{ + id: MenuId.ViewTitle, + when: ContextKeyEqualsExpr.create('view', OUTPUT_VIEW_ID), + group: 'navigation', + order: 2 + }, { + id: MenuId.CommandPalette + }, { + id: MenuId.EditorContext, + when: CONTEXT_IN_OUTPUT + }], + icon: { id: 'codicon/clear-all' } + }); + } + async run(accessor: ServicesAccessor): Promise { + const outputService = accessor.get(IOutputService); + const activeChannel = outputService.getActiveChannel(); if (activeChannel) { activeChannel.clear(); + aria.status(nls.localize('outputCleared', "Output was cleared")); + } + } +}); +registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.output.action.toggleAutoScroll`, + title: { value: nls.localize('toggleAutoScroll', "Toggle Auto Scrolling"), original: 'Toggle Auto Scrolling' }, + tooltip: { value: nls.localize('outputScrollOff', "Turn Auto Scrolling Off"), original: 'Turn Auto Scrolling Off' }, + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', OUTPUT_VIEW_ID)), + group: 'navigation', + order: 3, + }, + icon: { id: 'codicon/unlock' }, + toggled: { + condition: CONTEXT_OUTPUT_SCROLL_LOCK, + icon: { id: 'codicon/lock' }, + tooltip: { value: nls.localize('outputScrollOn', "Turn Auto Scrolling On"), original: 'Turn Auto Scrolling On' } + } + }); + } + async run(accessor: ServicesAccessor): Promise { + const outputView = accessor.get(IViewsService).getActiveViewWithId(OUTPUT_VIEW_ID)!; + outputView.scrollLock = !outputView.scrollLock; + } +}); +registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.action.openActiveLogOutputFile`, + title: { value: nls.localize('openActiveLogOutputFile', "Open Log Output File"), original: 'Open Log Output File' }, + menu: [{ + id: MenuId.ViewTitle, + when: ContextKeyEqualsExpr.create('view', OUTPUT_VIEW_ID), + group: 'navigation', + order: 4 + }, { + id: MenuId.CommandPalette, + when: CONTEXT_ACTIVE_LOG_OUTPUT, + }], + icon: { id: 'codicon/go-to-file' }, + precondition: CONTEXT_ACTIVE_LOG_OUTPUT + }); + } + async run(accessor: ServicesAccessor): Promise { + const outputService = accessor.get(IOutputService); + const editorService = accessor.get(IEditorService); + const instantiationService = accessor.get(IInstantiationService); + const logFileOutputChannelDescriptor = this.getLogFileOutputChannelDescriptor(outputService); + if (logFileOutputChannelDescriptor) { + await editorService.openEditor(instantiationService.createInstance(LogViewerInput, logFileOutputChannelDescriptor)); + } + } + private getLogFileOutputChannelDescriptor(outputService: IOutputService): IFileOutputChannelDescriptor | null { + const channel = outputService.getActiveChannel(); + if (channel) { + const descriptor = outputService.getChannelDescriptors().filter(c => c.id === channel.id)[0]; + if (descriptor && descriptor.file && descriptor.log) { + return descriptor; + } + } + return null; + } +}); + +// register toggle output action globally +registerAction2(class extends Action2 { + constructor() { + super({ + id: toggleOutputAcitonId, + title: { value: nls.localize('toggleOutput', "Toggle Output"), original: 'Toggle Output' }, + category: { value: nls.localize('viewCategory', "View"), original: 'View' }, + menu: { + id: MenuId.CommandPalette, + }, + keybinding: { + ...toggleOutputActionKeybindings, + ...{ + weight: KeybindingWeight.WorkbenchContrib, + when: undefined + } + }, + }); + } + async run(accessor: ServicesAccessor): Promise { + const viewsService = accessor.get(IViewsService); + const viewDescriptorService = accessor.get(IViewDescriptorService); + const contextKeyService = accessor.get(IContextKeyService); + const layoutService = accessor.get(IWorkbenchLayoutService); + return new class ToggleOutputAction extends ToggleViewAction { + constructor() { + super(toggleOutputAcitonId, 'Toggle Output', OUTPUT_VIEW_ID, viewsService, viewDescriptorService, contextKeyService, layoutService); + } + }().run(); + } +}); + +const devCategory = { value: nls.localize('developer', "Developer"), original: 'Developer' }; +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.showLogs', + title: { value: nls.localize('showLogs', "Show Logs..."), original: 'Show Logs...' }, + category: devCategory, + menu: { + id: MenuId.CommandPalette, + }, + }); + } + async run(accessor: ServicesAccessor): Promise { + const outputService = accessor.get(IOutputService); + const quickInputService = accessor.get(IQuickInputService); + const entries: { id: string, label: string }[] = outputService.getChannelDescriptors().filter(c => c.file && c.log) + .map(({ id, label }) => ({ id, label })); + + const entry = await quickInputService.pick(entries, { placeHolder: nls.localize('selectlog', "Select Log") }); + if (entry) { + return outputService.showChannel(entry.id); } } }); +interface IOutputChannelQuickPickItem extends IQuickPickItem { + channel: IOutputChannelDescriptor; +} + registerAction2(class extends Action2 { constructor() { super({ - id: 'workbench.action.openActiveLogOutputFile', - title: { value: nls.localize('openActiveLogOutputFile', "Open Active Log Output File"), original: 'Open Active Log Output File' }, + id: 'workbench.action.openLogFile', + title: { value: nls.localize('openLogFile', "Open Log File..."), original: 'Open Log File...' }, + category: devCategory, menu: { id: MenuId.CommandPalette, - when: CONTEXT_ACTIVE_LOG_OUTPUT }, }); } - run(accessor: ServicesAccessor) { - accessor.get(IInstantiationService).createInstance(OpenLogOutputFile).run(); + async run(accessor: ServicesAccessor): Promise { + const outputService = accessor.get(IOutputService); + const quickInputService = accessor.get(IQuickInputService); + const instantiationService = accessor.get(IInstantiationService); + const editorService = accessor.get(IEditorService); + + const entries: IOutputChannelQuickPickItem[] = outputService.getChannelDescriptors().filter(c => c.file && c.log) + .map(channel => ({ id: channel.id, label: channel.label, channel })); + + const entry = await quickInputService.pick(entries, { placeHolder: nls.localize('selectlogFile', "Select Log file") }); + if (entry) { + assertIsDefined(entry.channel.file); + await editorService.openEditor(instantiationService.createInstance(LogViewerInput, (entry.channel as IFileOutputChannelDescriptor))); + } } }); MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { group: '4_panels', command: { - id: ToggleOutputAction.ID, + id: toggleOutputAcitonId, title: nls.localize({ key: 'miToggleOutput', comment: ['&& denotes a mnemonic'] }, "&&Output") }, order: 1 }); + +Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ + id: 'output', + order: 30, + title: nls.localize('output', "Output"), + type: 'object', + properties: { + 'output.smartScroll.enabled': { + type: 'boolean', + description: nls.localize('output.smartScroll.enabled', "Enable/disable the ability of smart scrolling in the output view. Smart scrolling allows you to lock scrolling automatically when you click in the output view and unlocks when you click in the last line."), + default: true, + scope: ConfigurationScope.APPLICATION, + tags: ['output'] + } + } +}); diff --git a/src/vs/workbench/contrib/output/browser/outputActions.ts b/src/vs/workbench/contrib/output/browser/outputActions.ts deleted file mode 100644 index db610d418d1..00000000000 --- a/src/vs/workbench/contrib/output/browser/outputActions.ts +++ /dev/null @@ -1,269 +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 nls from 'vs/nls'; -import * as aria from 'vs/base/browser/ui/aria/aria'; -import { IAction, Action } from 'vs/base/common/actions'; -import { IOutputChannelRegistry, Extensions as OutputExt, IOutputChannelDescriptor, IFileOutputChannelDescriptor } from 'vs/workbench/services/output/common/output'; -import { IOutputService, OUTPUT_VIEW_ID } from 'vs/workbench/contrib/output/common/output'; -import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { TogglePanelAction } from 'vs/workbench/browser/panel'; -import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { groupBy } from 'vs/base/common/arrays'; -import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { LogViewerInput } from 'vs/workbench/contrib/output/browser/logViewer'; -import { ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; -import { assertIsDefined } from 'vs/base/common/types'; - -export class ToggleOutputAction extends TogglePanelAction { - - static readonly ID = 'workbench.action.output.toggleOutput'; - static readonly LABEL = nls.localize('toggleOutput', "Toggle Output"); - - constructor( - id: string, label: string, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @IPanelService panelService: IPanelService, - ) { - super(id, label, OUTPUT_VIEW_ID, panelService, layoutService); - } -} - -export class ClearOutputAction extends Action { - - static readonly ID = 'workbench.output.action.clearOutput'; - static readonly LABEL = nls.localize('clearOutput', "Clear Output"); - - constructor( - id: string, label: string, - @IOutputService private readonly outputService: IOutputService - ) { - super(id, label, 'output-action codicon-clear-all'); - } - - run(): Promise { - const activeChannel = this.outputService.getActiveChannel(); - if (activeChannel) { - activeChannel.clear(); - aria.status(nls.localize('outputCleared', "Output was cleared")); - } - return Promise.resolve(true); - } -} - -// this action can be triggered in two ways: -// 1. user clicks the action icon, In which case the action toggles the lock state -// 2. user clicks inside the output view, which sets the lock, Or unsets it if they click the last line. -export class ToggleOrSetOutputScrollLockAction extends Action { - - static readonly ID = 'workbench.output.action.toggleOutputScrollLock'; - static readonly LABEL = nls.localize({ key: 'toggleOutputScrollLock', comment: ['Turn on / off automatic output scrolling'] }, "Toggle Output Scroll Lock"); - - constructor(id: string, label: string, @IOutputService private readonly outputService: IOutputService) { - super(id, label, 'output-action codicon-unlock'); - this._register(this.outputService.onActiveOutputChannel(channel => { - const activeChannel = this.outputService.getActiveChannel(); - if (activeChannel) { - this.setClassAndLabel(activeChannel.scrollLock); - } - })); - } - - run(newLockState?: boolean): Promise { - - const activeChannel = this.outputService.getActiveChannel(); - if (activeChannel) { - if (typeof (newLockState) === 'boolean') { - activeChannel.scrollLock = newLockState; - } - else { - activeChannel.scrollLock = !activeChannel.scrollLock; - } - this.setClassAndLabel(activeChannel.scrollLock); - } - - return Promise.resolve(true); - } - - private setClassAndLabel(locked: boolean) { - if (locked) { - this.class = 'output-action codicon-lock'; - this.label = nls.localize('outputScrollOn', "Turn Auto Scrolling On"); - } else { - this.class = 'output-action codicon-unlock'; - this.label = nls.localize('outputScrollOff', "Turn Auto Scrolling Off"); - } - } -} - -export class SwitchOutputAction extends Action { - - static readonly ID = 'workbench.output.action.switchBetweenOutputs'; - - constructor(@IOutputService private readonly outputService: IOutputService) { - super(SwitchOutputAction.ID, nls.localize('switchToOutput.label', "Switch to Output")); - - this.class = 'output-action switch-to-output'; - } - - run(channelId: string): Promise { - return this.outputService.showChannel(channelId); - } -} - -export class SwitchOutputActionViewItem extends SelectActionViewItem { - - private static readonly SEPARATOR = '─────────'; - - private outputChannels: IOutputChannelDescriptor[] = []; - private logChannels: IOutputChannelDescriptor[] = []; - - constructor( - action: IAction, - @IOutputService private readonly outputService: IOutputService, - @IThemeService themeService: IThemeService, - @IContextViewService contextViewService: IContextViewService - ) { - super(null, action, [], 0, contextViewService, { ariaLabel: nls.localize('outputChannels', 'Output Channels.') }); - - let outputChannelRegistry = Registry.as(OutputExt.OutputChannels); - this._register(outputChannelRegistry.onDidRegisterChannel(() => this.updateOtions())); - this._register(outputChannelRegistry.onDidRemoveChannel(() => this.updateOtions())); - this._register(this.outputService.onActiveOutputChannel(() => this.updateOtions())); - this._register(attachSelectBoxStyler(this.selectBox, themeService)); - - this.updateOtions(); - } - - protected getActionContext(option: string, index: number): string { - const channel = index < this.outputChannels.length ? this.outputChannels[index] : this.logChannels[index - this.outputChannels.length - 1]; - return channel ? channel.id : option; - } - - private updateOtions(): void { - const groups = groupBy(this.outputService.getChannelDescriptors(), (c1: IOutputChannelDescriptor, c2: IOutputChannelDescriptor) => { - if (!c1.log && c2.log) { - return -1; - } - if (c1.log && !c2.log) { - return 1; - } - return 0; - }); - this.outputChannels = groups[0] || []; - this.logChannels = groups[1] || []; - const showSeparator = this.outputChannels.length && this.logChannels.length; - const separatorIndex = showSeparator ? this.outputChannels.length : -1; - const options: string[] = [...this.outputChannels.map(c => c.label), ...(showSeparator ? [SwitchOutputActionViewItem.SEPARATOR] : []), ...this.logChannels.map(c => nls.localize('logChannel', "Log ({0})", c.label))]; - let selected = 0; - const activeChannel = this.outputService.getActiveChannel(); - if (activeChannel) { - selected = this.outputChannels.map(c => c.id).indexOf(activeChannel.id); - if (selected === -1) { - const logChannelIndex = this.logChannels.map(c => c.id).indexOf(activeChannel.id); - selected = logChannelIndex !== -1 ? separatorIndex + 1 + logChannelIndex : 0; - } - } - this.setOptions(options.map((label, index) => { text: label, isDisabled: (index === separatorIndex ? true : false) }), Math.max(0, selected)); - } -} - -export class OpenLogOutputFile extends Action { - - static readonly ID = 'workbench.output.action.openLogOutputFile'; - static readonly LABEL = nls.localize('openInLogViewer', "Open Log File"); - - constructor( - @IOutputService private readonly outputService: IOutputService, - @IEditorService private readonly editorService: IEditorService, - @IInstantiationService private readonly instantiationService: IInstantiationService - ) { - super(OpenLogOutputFile.ID, OpenLogOutputFile.LABEL, 'output-action codicon-go-to-file'); - this._register(this.outputService.onActiveOutputChannel(this.update, this)); - this.update(); - } - - private update(): void { - this.enabled = !!this.getLogFileOutputChannelDescriptor(); - } - - async run(): Promise { - const logFileOutputChannelDescriptor = this.getLogFileOutputChannelDescriptor(); - if (logFileOutputChannelDescriptor) { - await this.editorService.openEditor(this.instantiationService.createInstance(LogViewerInput, logFileOutputChannelDescriptor)); - } - } - - private getLogFileOutputChannelDescriptor(): IFileOutputChannelDescriptor | null { - const channel = this.outputService.getActiveChannel(); - if (channel) { - const descriptor = this.outputService.getChannelDescriptors().filter(c => c.id === channel.id)[0]; - if (descriptor && descriptor.file && descriptor.log) { - return descriptor; - } - } - return null; - } -} - -export class ShowLogsOutputChannelAction extends Action { - - static readonly ID = 'workbench.action.showLogs'; - static readonly LABEL = nls.localize('showLogs', "Show Logs..."); - - constructor(id: string, label: string, - @IQuickInputService private readonly quickInputService: IQuickInputService, - @IOutputService private readonly outputService: IOutputService - ) { - super(id, label); - } - - async run(): Promise { - const entries: { id: string, label: string }[] = this.outputService.getChannelDescriptors().filter(c => c.file && c.log) - .map(({ id, label }) => ({ id, label })); - - const entry = await this.quickInputService.pick(entries, { placeHolder: nls.localize('selectlog', "Select Log") }); - if (entry) { - return this.outputService.showChannel(entry.id); - } - } -} - -interface IOutputChannelQuickPickItem extends IQuickPickItem { - channel: IOutputChannelDescriptor; -} - -export class OpenOutputLogFileAction extends Action { - - static readonly ID = 'workbench.action.openLogFile'; - static readonly LABEL = nls.localize('openLogFile', "Open Log File..."); - - constructor(id: string, label: string, - @IQuickInputService private readonly quickInputService: IQuickInputService, - @IOutputService private readonly outputService: IOutputService, - @IEditorService private readonly editorService: IEditorService, - @IInstantiationService private readonly instantiationService: IInstantiationService - ) { - super(id, label); - } - - async run(): Promise { - const entries: IOutputChannelQuickPickItem[] = this.outputService.getChannelDescriptors().filter(c => c.file && c.log) - .map(channel => ({ id: channel.id, label: channel.label, channel })); - - const entry = await this.quickInputService.pick(entries, { placeHolder: nls.localize('selectlogFile', "Select Log file") }); - if (entry) { - assertIsDefined(entry.channel.file); - await this.editorService.openEditor(this.instantiationService.createInstance(LogViewerInput, (entry.channel as IFileOutputChannelDescriptor))); - } - } -} diff --git a/src/vs/workbench/contrib/output/browser/outputView.ts b/src/vs/workbench/contrib/output/browser/outputView.ts index aebdb85e8b8..2af62cc74a2 100644 --- a/src/vs/workbench/contrib/output/browser/outputView.ts +++ b/src/vs/workbench/contrib/output/browser/outputView.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { IAction } from 'vs/base/common/actions'; -import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionViewItem, SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -13,12 +13,11 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { AbstractTextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; -import { OUTPUT_VIEW_ID, IOutputService, CONTEXT_IN_OUTPUT, IOutputChannel, CONTEXT_ACTIVE_LOG_OUTPUT } from 'vs/workbench/contrib/output/common/output'; -import { SwitchOutputAction, SwitchOutputActionViewItem, ClearOutputAction, ToggleOrSetOutputScrollLockAction, OpenLogOutputFile } from 'vs/workbench/contrib/output/browser/outputActions'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { OUTPUT_VIEW_ID, IOutputService, CONTEXT_IN_OUTPUT, IOutputChannel, CONTEXT_ACTIVE_LOG_OUTPUT, CONTEXT_OUTPUT_SCROLL_LOCK } from 'vs/workbench/contrib/output/common/output'; +import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -26,17 +25,28 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IOutputChannelDescriptor, IOutputChannelRegistry, Extensions } from 'vs/workbench/services/output/common/output'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { attachSelectBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; +import { ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; +import { groupBy } from 'vs/base/common/arrays'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { editorBackground, selectBorder } from 'vs/platform/theme/common/colorRegistry'; +import { addClass } from 'vs/base/browser/dom'; export class OutputViewPane extends ViewPane { private readonly editor: OutputEditor; private channelId: string | undefined; private editorPromise: Promise | null = null; - private actions: IAction[] | undefined; + + private readonly scrollLockContextKey: IContextKey; + get scrollLock(): boolean { return !!this.scrollLockContextKey.get(); } + set scrollLock(scrollLock: boolean) { this.scrollLockContextKey.set(scrollLock); } constructor( options: IViewPaneOptions, @@ -52,6 +62,7 @@ export class OutputViewPane extends ViewPane { @ITelemetryService telemetryService: ITelemetryService, ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + this.scrollLockContextKey = CONTEXT_OUTPUT_SCROLL_LOCK.bindTo(this.contextKeyService); this.editor = instantiationService.createInstance(OutputEditor); this._register(this.editor.onTitleAreaUpdate(() => { this.updateTitle(this.editor.getTitle()); @@ -78,10 +89,12 @@ export class OutputViewPane extends ViewPane { renderBody(container: HTMLElement): void { this.editor.create(container); + addClass(container, 'output-view'); const codeEditor = this.editor.getControl(); + codeEditor.setAriaOptions({ role: 'document', activeDescendant: undefined }); this._register(codeEditor.onDidChangeModelContent(() => { const activeChannel = this.outputService.getActiveChannel(); - if (activeChannel && !activeChannel.scrollLock) { + if (activeChannel && !this.scrollLock) { this.editor.revealLastLine(); } })); @@ -90,13 +103,15 @@ export class OutputViewPane extends ViewPane { return; } + if (!this.configurationService.getValue('output.smartScroll.enabled')) { + return; + } + const model = codeEditor.getModel(); - if (model && this.actions) { + if (model) { const newPositionLine = e.position.lineNumber; const lastLine = model.getLineCount(); - const newLockState = lastLine !== newPositionLine; - const lockAction = this.actions.filter((action) => action.id === ToggleOrSetOutputScrollLockAction.ID)[0]; - lockAction.run(newLockState); + this.scrollLock = lastLine !== newPositionLine; } })); } @@ -105,30 +120,13 @@ export class OutputViewPane extends ViewPane { this.editor.layout({ height, width }); } - getActions(): IAction[] { - if (!this.actions) { - this.actions = [ - this._register(this.instantiationService.createInstance(SwitchOutputAction)), - this._register(this.instantiationService.createInstance(ClearOutputAction, ClearOutputAction.ID, ClearOutputAction.LABEL)), - this._register(this.instantiationService.createInstance(ToggleOrSetOutputScrollLockAction, ToggleOrSetOutputScrollLockAction.ID, ToggleOrSetOutputScrollLockAction.LABEL)), - this._register(this.instantiationService.createInstance(OpenLogOutputFile)) - ]; - } - return [...super.getActions(), ...this.actions]; - } - - getSecondaryActions(): IAction[] { - return [...super.getSecondaryActions(), ...this.editor.getSecondaryActions()]; - } - getActionViewItem(action: IAction): IActionViewItem | undefined { - if (action.id === SwitchOutputAction.ID) { + if (action.id === 'workbench.output.action.switchBetweenOutputs') { return this.instantiationService.createInstance(SwitchOutputActionViewItem, action); } return super.getActionViewItem(action); } - private onDidChangeVisibility(visible: boolean): void { this.editor.setVisible(visible); let channel: IOutputChannel | undefined = undefined; @@ -144,8 +142,8 @@ export class OutputViewPane extends ViewPane { private setInput(channel: IOutputChannel): void { this.channelId = channel.id; - const descriptor = this.outputService.getChannelDescriptor(channel.id)!; - CONTEXT_ACTIVE_LOG_OUTPUT.bindTo(this.contextKeyService).set(!!descriptor.file && descriptor.log); + const descriptor = this.outputService.getChannelDescriptor(channel.id); + CONTEXT_ACTIVE_LOG_OUTPUT.bindTo(this.contextKeyService).set(!!descriptor?.file && descriptor?.log); this.editorPromise = this.editor.setInput(this.createInput(channel), EditorOptions.create({ preserveFocus: true }), CancellationToken.None) .then(() => this.editor); } @@ -255,6 +253,8 @@ export class OutputEditor extends AbstractTextResourceEditor { protected createEditor(parent: HTMLElement): void { + parent.setAttribute('role', 'document'); + // First create the scoped instantiation service and only then construct the editor using the scoped service const scopedContextKeyService = this._register(this.contextKeyService.createScoped(parent)); this.scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); @@ -264,3 +264,82 @@ export class OutputEditor extends AbstractTextResourceEditor { CONTEXT_IN_OUTPUT.bindTo(scopedContextKeyService).set(true); } } + +class SwitchOutputActionViewItem extends SelectActionViewItem { + + private static readonly SEPARATOR = '─────────'; + + private outputChannels: IOutputChannelDescriptor[] = []; + private logChannels: IOutputChannelDescriptor[] = []; + + constructor( + action: IAction, + @IOutputService private readonly outputService: IOutputService, + @IThemeService private readonly themeService: IThemeService, + @IContextViewService contextViewService: IContextViewService + ) { + super(null, action, [], 0, contextViewService, { ariaLabel: nls.localize('outputChannels', 'Output Channels.') }); + + let outputChannelRegistry = Registry.as(Extensions.OutputChannels); + this._register(outputChannelRegistry.onDidRegisterChannel(() => this.updateOtions())); + this._register(outputChannelRegistry.onDidRemoveChannel(() => this.updateOtions())); + this._register(this.outputService.onActiveOutputChannel(() => this.updateOtions())); + this._register(attachSelectBoxStyler(this.selectBox, themeService)); + + this.updateOtions(); + } + + render(container: HTMLElement): void { + super.render(container); + addClass(container, 'switch-output'); + this._register(attachStylerCallback(this.themeService, { selectBorder }, colors => { + container.style.border = colors.selectBorder ? `1px solid ${colors.selectBorder}` : ''; + })); + } + + protected getActionContext(option: string, index: number): string { + const channel = index < this.outputChannels.length ? this.outputChannels[index] : this.logChannels[index - this.outputChannels.length - 1]; + return channel ? channel.id : option; + } + + private updateOtions(): void { + const groups = groupBy(this.outputService.getChannelDescriptors(), (c1: IOutputChannelDescriptor, c2: IOutputChannelDescriptor) => { + if (!c1.log && c2.log) { + return -1; + } + if (c1.log && !c2.log) { + return 1; + } + return 0; + }); + this.outputChannels = groups[0] || []; + this.logChannels = groups[1] || []; + const showSeparator = this.outputChannels.length && this.logChannels.length; + const separatorIndex = showSeparator ? this.outputChannels.length : -1; + const options: string[] = [...this.outputChannels.map(c => c.label), ...(showSeparator ? [SwitchOutputActionViewItem.SEPARATOR] : []), ...this.logChannels.map(c => nls.localize('logChannel', "Log ({0})", c.label))]; + let selected = 0; + const activeChannel = this.outputService.getActiveChannel(); + if (activeChannel) { + selected = this.outputChannels.map(c => c.id).indexOf(activeChannel.id); + if (selected === -1) { + const logChannelIndex = this.logChannels.map(c => c.id).indexOf(activeChannel.id); + selected = logChannelIndex !== -1 ? separatorIndex + 1 + logChannelIndex : 0; + } + } + this.setOptions(options.map((label, index) => { text: label, isDisabled: (index === separatorIndex ? true : false) }), Math.max(0, selected)); + } +} + +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { + // Sidebar background for the output view + const sidebarBackground = theme.getColor(SIDE_BAR_BACKGROUND); + if (sidebarBackground && sidebarBackground !== theme.getColor(editorBackground)) { + collector.addRule(` + .monaco-workbench .part.sidebar .output-view .monaco-editor, + .monaco-workbench .part.sidebar .output-view .monaco-editor .margin, + .monaco-workbench .part.sidebar .output-view .monaco-editor .monaco-editor-background { + background-color: ${sidebarBackground}; + } + `); + } +}); diff --git a/src/vs/workbench/contrib/output/common/output.ts b/src/vs/workbench/contrib/output/common/output.ts index 238e2b7758f..483be2132b5 100644 --- a/src/vs/workbench/contrib/output/common/output.ts +++ b/src/vs/workbench/contrib/output/common/output.ts @@ -52,6 +52,8 @@ export const CONTEXT_IN_OUTPUT = new RawContextKey('inOutput', false); export const CONTEXT_ACTIVE_LOG_OUTPUT = new RawContextKey('activeLogOutput', false); +export const CONTEXT_OUTPUT_SCROLL_LOCK = new RawContextKey(`outputView.scrollLock`, false); + export const IOutputService = createDecorator(OUTPUT_SERVICE_ID); /** @@ -105,11 +107,6 @@ export interface IOutputChannel { */ label: string; - /** - * Returns the value indicating whether the channel has scroll locked. - */ - scrollLock: boolean; - /** * URI of the output channel. */ diff --git a/src/vs/workbench/contrib/output/test/browser/outputLinkProvider.test.ts b/src/vs/workbench/contrib/output/test/browser/outputLinkProvider.test.ts index ce131747b3c..a53b8c389e7 100644 --- a/src/vs/workbench/contrib/output/test/browser/outputLinkProvider.test.ts +++ b/src/vs/workbench/contrib/output/test/browser/outputLinkProvider.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { isMacintosh, isLinux, isWindows } from 'vs/base/common/platform'; import { OutputLinkComputer } from 'vs/workbench/contrib/output/common/outputLinkComputer'; -import { TestContextService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; function toOSPath(p: string): string { if (isMacintosh || isLinux) { diff --git a/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts b/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts index 789a38d7dbb..3d7cc299ee1 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts @@ -98,8 +98,8 @@ export class StartupTimings implements IWorkbenchContribution { if (!activeViewlet || activeViewlet.getId() !== files.VIEWLET_ID) { return false; } - const visibleControls = this._editorService.visibleControls; - if (visibleControls.length !== 1 || !isCodeEditor(visibleControls[0].getControl())) { + const visibleEditorPanes = this._editorService.visibleEditorPanes; + if (visibleEditorPanes.length !== 1 || !isCodeEditor(visibleEditorPanes[0].getControl())) { return false; } if (this._panelService.getActivePanel()) { diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index 8f1aef29d23..17bc4b85e9c 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -23,14 +23,14 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IKeybindingService, IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; import { DefineKeybindingWidget, KeybindingsSearchWidget, KeybindingsSearchOptions } from 'vs/workbench/contrib/preferences/browser/keybindingWidgets'; import { - IKeybindingsEditor, CONTEXT_KEYBINDING_FOCUS, CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_COPY, + IKeybindingsEditorPane, CONTEXT_KEYBINDING_FOCUS, CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN } from 'vs/workbench/contrib/preferences/common/preferences'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; import { IListVirtualDelegate, IListRenderer, IListContextMenuEvent, IListEvent } from 'vs/base/browser/ui/list/list'; -import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode, ResolvedKeybinding } from 'vs/base/common/keyCodes'; @@ -55,7 +55,7 @@ interface ColumnItem { width: number; } -export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor { +export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditorPane { static readonly ID: string = 'workbench.editor.keybindings'; @@ -463,7 +463,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor } })) as WorkbenchList; this._register(this.keybindingsList.onContextMenu(e => this.onContextMenu(e))); - this._register(this.keybindingsList.onFocusChange(e => this.onFocusChange(e))); + this._register(this.keybindingsList.onDidChangeFocus(e => this.onFocusChange(e))); this._register(this.keybindingsList.onDidFocus(() => { DOM.addClass(this.keybindingsList.getHTMLElement(), 'focused'); })); @@ -551,7 +551,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor this.unAssignedKeybindingItemToRevealAndFocus = null; } else if (currentSelectedIndex !== -1 && currentSelectedIndex < this.listEntries.length) { this.selectEntry(currentSelectedIndex, preserveFocus); - } else if (this.editorService.activeControl === this && !preserveFocus) { + } else if (this.editorService.activeEditorPane === this && !preserveFocus) { this.focus(); } } @@ -854,7 +854,7 @@ abstract class Column extends Disposable { abstract readonly element: HTMLElement; abstract render(keybindingItemEntry: IKeybindingItemEntry): void; - constructor(protected keybindingsEditor: IKeybindingsEditor) { + constructor(protected keybindingsEditor: IKeybindingsEditorPane) { super(); } @@ -867,7 +867,7 @@ class ActionsColumn extends Column { constructor( parent: HTMLElement, - keybindingsEditor: IKeybindingsEditor, + keybindingsEditor: IKeybindingsEditorPane, @IKeybindingService private keybindingsService: IKeybindingService ) { super(keybindingsEditor); @@ -921,7 +921,7 @@ class CommandColumn extends Column { constructor( parent: HTMLElement, - keybindingsEditor: IKeybindingsEditor, + keybindingsEditor: IKeybindingsEditorPane, ) { super(keybindingsEditor); this.element = this.commandColumn = DOM.append(parent, $('.column.command', { id: 'command_' + ++Column.COUNTER })); @@ -953,7 +953,7 @@ class CommandColumn extends Column { } private getAriaLabel(keybindingItemEntry: IKeybindingItemEntry): string { - return localize('commandAriaLabel', "Command is {0}.", keybindingItemEntry.keybindingItem.commandLabel ? keybindingItemEntry.keybindingItem.commandLabel : keybindingItemEntry.keybindingItem.command); + return keybindingItemEntry.keybindingItem.commandLabel ? keybindingItemEntry.keybindingItem.commandLabel : keybindingItemEntry.keybindingItem.command; } } @@ -964,7 +964,7 @@ class KeybindingColumn extends Column { constructor( parent: HTMLElement, - keybindingsEditor: IKeybindingsEditor, + keybindingsEditor: IKeybindingsEditorPane, ) { super(keybindingsEditor); @@ -992,7 +992,7 @@ class SourceColumn extends Column { constructor( parent: HTMLElement, - keybindingsEditor: IKeybindingsEditor, + keybindingsEditor: IKeybindingsEditorPane, ) { super(keybindingsEditor); this.element = this.sourceColumn = DOM.append(parent, $('.column.source', { id: 'source_' + ++Column.COUNTER })); @@ -1024,7 +1024,7 @@ class WhenColumn extends Column { constructor( parent: HTMLElement, - keybindingsEditor: IKeybindingsEditor, + keybindingsEditor: IKeybindingsEditorPane, @IContextViewService private readonly contextViewService: IContextViewService, @IThemeService private readonly themeService: IThemeService ) { @@ -1123,7 +1123,7 @@ class WhenColumn extends Column { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const listHighlightForegroundColor = theme.getColor(listHighlightForeground); if (listHighlightForegroundColor) { collector.addRule(`.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .column .highlight { color: ${listHighlightForegroundColor}; }`); diff --git a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts index 77136d27d2f..484f0201131 100644 --- a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts +++ b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts @@ -21,7 +21,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IFileService } from 'vs/platform/files/common/files'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { VSBuffer } from 'vs/base/common/buffer'; -import { IEditor } from 'vs/workbench/common/editor'; +import { IEditorPane } from 'vs/workbench/common/editor'; export class KeyboardLayoutPickerContribution extends Disposable implements IWorkbenchContribution { private readonly pickerElement = this._register(new MutableDisposable()); @@ -156,7 +156,7 @@ export class KeyboardLayoutPickerAction extends Action { await this.fileService.resolve(file).then(undefined, (error) => { return this.fileService.createFile(file, VSBuffer.fromString(KeyboardLayoutPickerAction.DEFAULT_CONTENT)); - }).then((stat): Promise | undefined => { + }).then((stat): Promise | undefined => { if (!stat) { return undefined; } diff --git a/src/vs/workbench/contrib/preferences/browser/media/preferences.css b/src/vs/workbench/contrib/preferences/browser/media/preferences.css index 73c3859d0f3..bf030348ca5 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/preferences.css +++ b/src/vs/workbench/contrib/preferences/browser/media/preferences.css @@ -142,16 +142,6 @@ -webkit-user-select: none; } -.vs .monaco-editor .settings-header-widget .title-container { - color: #6f6f6f; -} -.vs-dark .monaco-editor .settings-header-widget .title-container { - color: #bbbbbb; -} -.hc-black .monaco-editor .settings-header-widget .title-container { - color: white; -} - .monaco-editor .settings-header-widget .title-container .title { font-weight: bold; white-space: nowrap; @@ -175,22 +165,12 @@ display: flex; } -.vs .monaco-editor .settings-group-title-widget .title-container { - color: #6f6f6f; -} .monaco-editor .settings-group-title-widget .title-container .title { white-space: nowrap; overflow: hidden; } -.vs-dark .monaco-editor .settings-group-title-widget .title-container { - color: #bbbbbb; -} -.hc-black .monaco-editor .settings-group-title-widget .title-container { - color: white; -} - .monaco-editor.vs-dark .settings-group-title-widget .title-container.focused, .monaco-editor.vs .settings-group-title-widget .title-container.focused { outline: none !important; diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index ec040450cce..55af88d7092 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -64,6 +64,10 @@ margin-top: 10px; } +.settings-editor > .settings-header > .settings-header-controls .settings-target-container { + flex: auto; +} + .settings-editor > .settings-header > .settings-header-controls .settings-tabs-widget .action-label { opacity: 0.9; } @@ -314,8 +318,19 @@ } .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-title .setting-item-overrides { - opacity: 0.5; + opacity: 0.9; font-style: italic; + margin-right: 4px; +} + +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-title .setting-item-ignored { + opacity: 0.9; + font-style: italic; +} + +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-title .setting-item-ignored .codicon { + vertical-align: text-top; + padding-left: 1px; } .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-title .setting-item-overrides a.modified-scope { diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index 8e94c2c2236..06c56c69a34 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -9,8 +9,8 @@ import { URI } from 'vs/base/common/uri'; import 'vs/css!../browser/media/preferences'; import { Context as SuggestContext } from 'vs/editor/contrib/suggest/suggest'; import * as nls from 'vs/nls'; -import { Action2, MenuId, MenuRegistry, registerAction2, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; +import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -19,25 +19,26 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IsMacNativeContext, RemoteNameContext, WorkbenchStateContext } from 'vs/workbench/browser/contextkeys'; +import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { RemoteNameContext, WorkbenchStateContext } from 'vs/workbench/browser/contextkeys'; +import { IsMacNativeContext } from 'vs/platform/contextkey/common/contextkeys'; import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor'; -import { Extensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { EditorInput, Extensions as EditorInputExtensions, IEditorInputFactory, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; import { ResourceContextKey } from 'vs/workbench/common/resources'; import { ExplorerFolderContext, ExplorerRootContext } from 'vs/workbench/contrib/files/common/files'; import { KeybindingsEditor } from 'vs/workbench/contrib/preferences/browser/keybindingsEditor'; -import { ConfigureLanguageBasedSettingsAction, OpenDefaultKeybindingsFileAction, OpenFolderSettingsAction, OpenGlobalKeybindingsAction, OpenGlobalKeybindingsFileAction, OpenGlobalSettingsAction, OpenRawDefaultSettingsAction, OpenRemoteSettingsAction, OpenSettings2Action, OpenSettingsJsonAction, OpenWorkspaceSettingsAction, OPEN_FOLDER_SETTINGS_COMMAND, OPEN_FOLDER_SETTINGS_LABEL } from 'vs/workbench/contrib/preferences/browser/preferencesActions'; +import { ConfigureLanguageBasedSettingsAction } from 'vs/workbench/contrib/preferences/browser/preferencesActions'; import { PreferencesEditor } from 'vs/workbench/contrib/preferences/browser/preferencesEditor'; import { SettingsEditor2 } from 'vs/workbench/contrib/preferences/browser/settingsEditor2'; -import { CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, CONTEXT_KEYBINDING_FOCUS, CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_JSON_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, IKeybindingsEditor, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_SEARCH, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS, MODIFIED_SETTING_TAG, SETTINGS_COMMAND_OPEN_SETTINGS, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING, SETTINGS_EDITOR_COMMAND_FILTER_MODIFIED, SETTINGS_EDITOR_COMMAND_FILTER_ONLINE, SETTINGS_EDITOR_COMMAND_FOCUS_FILE, SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_LIST, SETTINGS_EDITOR_COMMAND_SEARCH, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU, SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON } from 'vs/workbench/contrib/preferences/common/preferences'; +import { CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, CONTEXT_KEYBINDING_FOCUS, CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_JSON_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_SEARCH, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS, MODIFIED_SETTING_TAG, SETTINGS_COMMAND_OPEN_SETTINGS, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING, SETTINGS_EDITOR_COMMAND_FILTER_MODIFIED, SETTINGS_EDITOR_COMMAND_FILTER_ONLINE, SETTINGS_EDITOR_COMMAND_FOCUS_FILE, SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_LIST, SETTINGS_EDITOR_COMMAND_SEARCH, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU, SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON, SETTINGS_EDITOR_COMMAND_FOCUS_TOC } from 'vs/workbench/contrib/preferences/common/preferences'; import { PreferencesContribution } from 'vs/workbench/contrib/preferences/common/preferencesContribution'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { DefaultPreferencesEditorInput, KeybindingsEditorInput, PreferencesEditorInput, SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; +import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; Registry.as(EditorExtensions.Editors).registerEditor( EditorDescriptor.create( @@ -213,235 +214,865 @@ Registry.as(EditorInputExtensions.EditorInputFactor Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(KeybindingsEditorInput.ID, KeybindingsEditorInputFactory); Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(SettingsEditor2Input.ID, SettingsEditor2InputFactory); -// Contribute Global Actions -const category = nls.localize('preferences', "Preferences"); -const registry = Registry.as(Extensions.WorkbenchActions); -registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenRawDefaultSettingsAction, OpenRawDefaultSettingsAction.ID, OpenRawDefaultSettingsAction.LABEL), 'Preferences: Open Default Settings (JSON)', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenSettingsJsonAction, OpenSettingsJsonAction.ID, OpenSettingsJsonAction.LABEL), 'Preferences: Open Settings (JSON)', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenSettings2Action, OpenSettings2Action.ID, OpenSettings2Action.LABEL), 'Preferences: Open Settings (UI)', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenGlobalSettingsAction, OpenGlobalSettingsAction.ID, OpenGlobalSettingsAction.LABEL), 'Preferences: Open User Settings', category); - -registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenGlobalKeybindingsAction, OpenGlobalKeybindingsAction.ID, OpenGlobalKeybindingsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_S) }), 'Preferences: Open Keyboard Shortcuts', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenDefaultKeybindingsFileAction, OpenDefaultKeybindingsFileAction.ID, OpenDefaultKeybindingsFileAction.LABEL), 'Preferences: Open Default Keyboard Shortcuts (JSON)', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenGlobalKeybindingsFileAction, OpenGlobalKeybindingsFileAction.ID, OpenGlobalKeybindingsFileAction.LABEL, { primary: 0 }), 'Preferences: Open Keyboard Shortcuts (JSON)', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(ConfigureLanguageBasedSettingsAction, ConfigureLanguageBasedSettingsAction.ID, ConfigureLanguageBasedSettingsAction.LABEL), 'Preferences: Configure Language Specific Settings...', category); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: SETTINGS_COMMAND_OPEN_SETTINGS, - weight: KeybindingWeight.WorkbenchContrib, - when: null, - primary: KeyMod.CtrlCmd | KeyCode.US_COMMA, - handler: (accessor, args: string | undefined) => { - const query = typeof args === 'string' ? args : undefined; - accessor.get(IPreferencesService).openSettings(query ? false : undefined, query); - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: KEYBINDINGS_EDITOR_COMMAND_DEFINE, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS), - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_K), - handler: (accessor, args: any) => { - const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control && control instanceof KeybindingsEditor) { - control.defineKeybinding(control.activeKeybindingEntry!); - } - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS), - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_E), - handler: (accessor, args: any) => { - const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control && control instanceof KeybindingsEditor && control.activeKeybindingEntry!.keybindingItem.keybinding) { - control.defineWhenExpression(control.activeKeybindingEntry!); - } - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: KEYBINDINGS_EDITOR_COMMAND_REMOVE, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS), - primary: KeyCode.Delete, - mac: { - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.Backspace) - }, - handler: (accessor, args: any) => { - const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control && control instanceof KeybindingsEditor) { - control.removeKeybinding(control.activeKeybindingEntry!); - } - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: KEYBINDINGS_EDITOR_COMMAND_RESET, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS), - primary: 0, - handler: (accessor, args: any) => { - const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control && control instanceof KeybindingsEditor) { - control.resetKeybinding(control.activeKeybindingEntry!); - } - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: KEYBINDINGS_EDITOR_COMMAND_SEARCH, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR), - primary: KeyMod.CtrlCmd | KeyCode.KEY_F, - handler: (accessor, args: any) => { - const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control && control instanceof KeybindingsEditor) { - control.focusSearch(); - } - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS), - primary: KeyMod.Alt | KeyCode.KEY_K, - mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_K }, - handler: (accessor, args: any) => { - const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control && control instanceof KeybindingsEditor) { - control.recordSearchKeys(); - } - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR), - primary: KeyMod.Alt | KeyCode.KEY_P, - mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_P }, - handler: (accessor, args: any) => { - const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control && control instanceof KeybindingsEditor) { - control.toggleSortByPrecedence(); - } - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS), - primary: 0, - handler: (accessor, args: any) => { - const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control) { - control.showSimilarKeybindings(control.activeKeybindingEntry!); - } - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: KEYBINDINGS_EDITOR_COMMAND_COPY, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS), - primary: KeyMod.CtrlCmd | KeyCode.KEY_C, - handler: async (accessor, args: any) => { - const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control) { - await control.copyKeybinding(control.activeKeybindingEntry!); - } - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS), - primary: 0, - handler: async (accessor, args: any) => { - const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control) { - await control.copyKeybindingCommand(control.activeKeybindingEntry!); - } - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS), - primary: KeyCode.DownArrow, - handler: (accessor, args: any) => { - const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control) { - control.focusKeybindings(); - } - } -}); +const OPEN_SETTINGS2_ACTION_TITLE = { value: nls.localize('openSettings2', "Open Settings (UI)"), original: 'Open Settings (UI)' }; class PreferencesActionsContribution extends Disposable implements IWorkbenchContribution { constructor( - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IPreferencesService private readonly preferencesService: IPreferencesService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @ILabelService labelService: ILabelService, - @IExtensionService extensionService: IExtensionService, + @ILabelService private readonly labelService: ILabelService, + @IExtensionService private readonly extensionService: IExtensionService, ) { super(); - MenuRegistry.appendMenuItem(MenuId.EditorTitle, { - command: { - id: OpenGlobalKeybindingsAction.ID, - title: OpenGlobalKeybindingsAction.LABEL, - icon: { id: 'codicon/go-to-file' } - }, - when: ResourceContextKey.Resource.isEqualTo(environmentService.keybindingsResource.toString()), - group: 'navigation', - order: 1 - }); - const commandId = '_workbench.openUserSettingsEditor'; - CommandsRegistry.registerCommand(commandId, () => this.preferencesService.openGlobalSettings(false)); - MenuRegistry.appendMenuItem(MenuId.EditorTitle, { - command: { - id: commandId, - title: OpenSettings2Action.LABEL, - icon: { id: 'codicon/go-to-file' } - }, - when: ResourceContextKey.Resource.isEqualTo(environmentService.settingsResource.toString()), - group: 'navigation', - order: 1 - }); + this.registerSettingsActions(); + this.registerKeybindingsActions(); this.updatePreferencesEditorMenuItem(); this._register(workspaceContextService.onDidChangeWorkbenchState(() => this.updatePreferencesEditorMenuItem())); this._register(workspaceContextService.onDidChangeWorkspaceFolders(() => this.updatePreferencesEditorMenuItemForWorkspaceFolders())); + } - extensionService.whenInstalledExtensionsRegistered() - .then(() => { - const remoteAuthority = environmentService.configuration.remoteAuthority; - const hostLabel = labelService.getHostLabel(REMOTE_HOST_SCHEME, remoteAuthority) || remoteAuthority; - const label = nls.localize('openRemoteSettings', "Open Remote Settings ({0})", hostLabel); - CommandsRegistry.registerCommand(OpenRemoteSettingsAction.ID, serviceAccessor => { - serviceAccessor.get(IInstantiationService).createInstance(OpenRemoteSettingsAction, OpenRemoteSettingsAction.ID, label).run(); - }); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: OpenRemoteSettingsAction.ID, - title: { value: label, original: `Open Remote Settings (${hostLabel})` }, - category: { value: nls.localize('preferencesCategory', "Preferences"), original: 'Preferences' } + private registerSettingsActions() { + const that = this; + const category = { value: nls.localize('preferences', "Preferences"), original: 'Preferences' }; + registerAction2(class extends Action2 { + constructor() { + super({ + id: SETTINGS_COMMAND_OPEN_SETTINGS, + title: nls.localize('settings', "Settings"), + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: null, + primary: KeyMod.CtrlCmd | KeyCode.US_COMMA, }, - when: RemoteNameContext.notEqualsTo('') + menu: { + id: MenuId.GlobalActivity, + group: '2_configuration', + order: 1 + } + }); + } + run(accessor: ServicesAccessor, args: string | undefined) { + const query = typeof args === 'string' ? args : undefined; + return accessor.get(IPreferencesService).openSettings(query ? false : undefined, query); + } + }); + MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + group: '1_settings', + command: { + id: SETTINGS_COMMAND_OPEN_SETTINGS, + title: nls.localize({ key: 'miOpenSettings', comment: ['&& denotes a mnemonic'] }, "&&Settings") + }, + order: 1 + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.openSettings2', + title: { value: nls.localize('openSettings2', "Open Settings (UI)"), original: 'Open Settings (UI)' }, + category, + menu: { id: MenuId.CommandPalette } + }); + } + run(accessor: ServicesAccessor) { + return accessor.get(IPreferencesService).openSettings(false, undefined); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.openSettingsJson', + title: { value: nls.localize('openSettingsJson', "Open Settings (JSON)"), original: 'Open Settings (JSON)' }, + category, + menu: { id: MenuId.CommandPalette } + }); + } + run(accessor: ServicesAccessor) { + return accessor.get(IPreferencesService).openSettings(true, undefined); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.openGlobalSettings', + title: { value: nls.localize('openGlobalSettings', "Open User Settings"), original: 'Open User Settings' }, + category, + menu: { id: MenuId.CommandPalette } + }); + } + run(accessor: ServicesAccessor) { + return accessor.get(IPreferencesService).openGlobalSettings(); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.openRawDefaultSettings', + title: { value: nls.localize('openRawDefaultSettings', "Open Default Settings (JSON)"), original: 'Open Default Settings (JSON)' }, + category, + menu: { id: MenuId.CommandPalette } + }); + } + run(accessor: ServicesAccessor) { + return accessor.get(IPreferencesService).openRawDefaultSettings(); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: '_workbench.openUserSettingsEditor', + title: OPEN_SETTINGS2_ACTION_TITLE, + icon: { id: 'codicon/go-to-file' }, + menu: [{ + id: MenuId.EditorTitle, + when: ResourceContextKey.Resource.isEqualTo(that.environmentService.settingsResource.toString()), + group: 'navigation', + order: 1 + }] + }); + } + run(accessor: ServicesAccessor) { + return accessor.get(IPreferencesService).openGlobalSettings(false); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON, + title: { value: nls.localize('openSettingsJson', "Open Settings (JSON)"), original: 'Open Settings (JSON)' }, + icon: { id: 'codicon/go-to-file' }, + menu: [{ + id: MenuId.EditorTitle, + when: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_JSON_EDITOR.toNegated()), + group: 'navigation', + order: 1 + }] + }); + } + run(accessor: ServicesAccessor) { + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof SettingsEditor2) { + return editorPane.switchToSettingsFile(); + } + return Promise.resolve(null); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: ConfigureLanguageBasedSettingsAction.ID, + title: ConfigureLanguageBasedSettingsAction.LABEL, + category, + menu: { id: MenuId.CommandPalette } + }); + } + run(accessor: ServicesAccessor) { + return accessor.get(IInstantiationService).createInstance(ConfigureLanguageBasedSettingsAction, ConfigureLanguageBasedSettingsAction.ID, ConfigureLanguageBasedSettingsAction.LABEL.value).run(); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.openWorkspaceSettings', + title: { value: nls.localize('openWorkspaceSettings', "Open Workspace Settings"), original: 'Open Workspace Settings' }, + category, + menu: { + id: MenuId.CommandPalette, + when: WorkbenchStateContext.notEqualsTo('empty') + } + }); + } + run(accessor: ServicesAccessor) { + return accessor.get(IPreferencesService).openWorkspaceSettings(); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.openWorkspaceSettingsFile', + title: { value: nls.localize('openWorkspaceSettingsFile', "Open Workspace Settings (JSON)"), original: 'Open Workspace Settings (JSON)' }, + category, + menu: { + id: MenuId.CommandPalette, + when: WorkbenchStateContext.notEqualsTo('empty') + } + }); + } + run(accessor: ServicesAccessor) { + return accessor.get(IPreferencesService).openWorkspaceSettings(true); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.openFolderSettings', + title: { value: nls.localize('openFolderSettings', "Open Folder Settings"), original: 'Open Folder Settings' }, + category, + menu: { + id: MenuId.CommandPalette, + when: WorkbenchStateContext.isEqualTo('workspace') + } + }); + } + async run(accessor: ServicesAccessor) { + const commandService = accessor.get(ICommandService); + const preferencesService = accessor.get(IPreferencesService); + const workspaceFolder = await commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID); + if (workspaceFolder) { + await preferencesService.openFolderSettings(workspaceFolder.uri); + } + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.openFolderSettingsFile', + title: { value: nls.localize('openFolderSettingsFile', "Open Folder Settings (JSON)"), original: 'Open Folder Settings (JSON)' }, + category, + menu: { + id: MenuId.CommandPalette, + when: WorkbenchStateContext.isEqualTo('workspace') + } + }); + } + async run(accessor: ServicesAccessor) { + const commandService = accessor.get(ICommandService); + const preferencesService = accessor.get(IPreferencesService); + const workspaceFolder = await commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID); + if (workspaceFolder) { + await preferencesService.openFolderSettings(workspaceFolder.uri, true); + } + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: '_workbench.action.openFolderSettings', + title: { value: nls.localize('openFolderSettings', "Open Folder Settings"), original: 'Open Folder Settings' }, + category, + menu: { + id: MenuId.ExplorerContext, + group: '2_workspace', + order: 20, + when: ContextKeyExpr.and(ExplorerRootContext, ExplorerFolderContext) + } + }); + } + run(accessor: ServicesAccessor, resource: URI) { + return accessor.get(IPreferencesService).openFolderSettings(resource); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: SETTINGS_EDITOR_COMMAND_FILTER_MODIFIED, + title: { value: nls.localize('filterModifiedLabel', "Show modified settings"), original: 'Show modified settings' }, + menu: { + id: MenuId.ExplorerContext, + group: '1_filter', + order: 1, + when: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_JSON_EDITOR.toNegated()) + } + }); + } + run(accessor: ServicesAccessor, resource: URI) { + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof SettingsEditor2) { + editorPane.focusSearch(`@${MODIFIED_SETTING_TAG}`); + } + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: SETTINGS_EDITOR_COMMAND_FILTER_ONLINE, + title: { value: nls.localize('filterOnlineServicesLabel', "Show settings for online services"), original: 'Show settings for online services' }, + menu: { + id: MenuId.ExplorerContext, + group: '1_filter', + order: 2, + when: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_JSON_EDITOR.toNegated()) + } + }); + } + run(accessor: ServicesAccessor) { + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof SettingsEditor2) { + editorPane.focusSearch(`@tag:usesOnlineServices`); + } else { + accessor.get(IPreferencesService).openSettings(false, '@tag:usesOnlineServices'); + } + } + }); + MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + group: '1_settings', + command: { + id: SETTINGS_EDITOR_COMMAND_FILTER_ONLINE, + title: nls.localize({ key: 'miOpenOnlineSettings', comment: ['&& denotes a mnemonic'] }, "&&Online Services Settings") + }, + order: 2 + }); + MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { + group: '2_configuration', + command: { + id: SETTINGS_EDITOR_COMMAND_FILTER_ONLINE, + title: nls.localize('onlineServices', "Online Services Settings") + }, + order: 2 + }); + + this.registerSettingsEditorActions(); + + this.extensionService.whenInstalledExtensionsRegistered() + .then(() => { + const remoteAuthority = this.environmentService.configuration.remoteAuthority; + const hostLabel = this.labelService.getHostLabel(REMOTE_HOST_SCHEME, remoteAuthority) || remoteAuthority; + const label = nls.localize('openRemoteSettings', "Open Remote Settings ({0})", hostLabel); + registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.openRemoteSettings', + title: { value: label, original: `Open Remote Settings (${hostLabel})` }, + category, + menu: { + id: MenuId.CommandPalette, + when: RemoteNameContext.notEqualsTo('') + } + }); + } + run(accessor: ServicesAccessor) { + return accessor.get(IPreferencesService).openRemoteSettings(); + } }); }); } + private registerSettingsEditorActions() { + function getPreferencesEditor(accessor: ServicesAccessor): PreferencesEditor | SettingsEditor2 | null { + const activeEditorPane = accessor.get(IEditorService).activeEditorPane; + if (activeEditorPane instanceof PreferencesEditor || activeEditorPane instanceof SettingsEditor2) { + return activeEditorPane; + } + return null; + } + registerAction2(class extends Action2 { + constructor() { + super({ + id: SETTINGS_EDITOR_COMMAND_SEARCH, + precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR), + keybinding: { + primary: KeyMod.CtrlCmd | KeyCode.KEY_F, + weight: KeybindingWeight.EditorContrib, + when: null + }, + title: nls.localize('settings.focusSearch', "Focus settings search") + }); + } + + run(accessor: ServicesAccessor) { + const preferencesEditor = getPreferencesEditor(accessor); + if (preferencesEditor) { + preferencesEditor.focusSearch(); + } + } + }); + + registerAction2(class extends Action2 { + constructor() { + super({ + id: SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, + precondition: CONTEXT_SETTINGS_SEARCH_FOCUS, + keybinding: { + primary: KeyCode.Escape, + weight: KeybindingWeight.EditorContrib, + when: null + }, + title: nls.localize('settings.clearResults', "Clear settings search results") + }); + } + + run(accessor: ServicesAccessor) { + const preferencesEditor = getPreferencesEditor(accessor); + if (preferencesEditor) { + preferencesEditor.clearSearchResults(); + } + } + }); + + registerAction2(class extends Action2 { + constructor() { + super({ + id: SETTINGS_EDITOR_COMMAND_FOCUS_FILE, + precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_SEARCH_FOCUS, SuggestContext.Visible.toNegated()), + keybinding: { + primary: KeyCode.DownArrow, + weight: KeybindingWeight.EditorContrib, + when: null + }, + title: nls.localize('settings.focusFile', "Focus settings file") + }); + } + + run(accessor: ServicesAccessor, args: any): void { + const preferencesEditor = getPreferencesEditor(accessor); + if (preferencesEditor instanceof PreferencesEditor) { + preferencesEditor.focusSettingsFileEditor(); + } else if (preferencesEditor) { + preferencesEditor.focusSettings(); + } + } + }); + + registerAction2(class extends Action2 { + constructor() { + super({ + id: SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH, + precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_SEARCH_FOCUS, SuggestContext.Visible.toNegated()), + keybinding: { + primary: KeyCode.DownArrow, + weight: KeybindingWeight.WorkbenchContrib, + when: null + }, + title: nls.localize('settings.focusFile', "Focus settings file") + }); + } + + run(accessor: ServicesAccessor, args: any): void { + const preferencesEditor = getPreferencesEditor(accessor); + if (preferencesEditor instanceof PreferencesEditor) { + preferencesEditor.focusSettingsFileEditor(); + } else if (preferencesEditor) { + preferencesEditor.focusSettings(); + } + } + }); + + registerAction2(class extends Action2 { + constructor() { + super({ + id: SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING, + precondition: CONTEXT_SETTINGS_SEARCH_FOCUS, + keybinding: { + primary: KeyCode.Enter, + weight: KeybindingWeight.EditorContrib, + when: null + }, + title: nls.localize('settings.focusNextSetting', "Focus next setting") + }); + } + + run(accessor: ServicesAccessor): void { + const preferencesEditor = getPreferencesEditor(accessor); + if (preferencesEditor instanceof PreferencesEditor) { + preferencesEditor.focusNextResult(); + } + } + }); + + registerAction2(class extends Action2 { + constructor() { + super({ + id: SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING, + precondition: CONTEXT_SETTINGS_SEARCH_FOCUS, + keybinding: { + primary: KeyMod.Shift | KeyCode.Enter, + weight: KeybindingWeight.EditorContrib, + when: null + }, + title: nls.localize('settings.focusPreviousSetting', "Focus previous setting") + }); + } + + run(accessor: ServicesAccessor): void { + const preferencesEditor = getPreferencesEditor(accessor); + if (preferencesEditor instanceof PreferencesEditor) { + preferencesEditor.focusPreviousResult(); + } + } + }); + + registerAction2(class extends Action2 { + constructor() { + super({ + id: SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING, + precondition: CONTEXT_SETTINGS_SEARCH_FOCUS, + keybinding: { + primary: KeyMod.CtrlCmd | KeyCode.US_DOT, + weight: KeybindingWeight.EditorContrib, + when: null + }, + title: nls.localize('settings.editFocusedSetting', "Edit focused setting") + }); + } + + run(accessor: ServicesAccessor): void { + const preferencesEditor = getPreferencesEditor(accessor); + if (preferencesEditor instanceof PreferencesEditor) { + preferencesEditor.editFocusedPreference(); + } + } + }); + + registerAction2(class extends Action2 { + constructor() { + super({ + id: SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_LIST, + precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR, CONTEXT_TOC_ROW_FOCUS), + keybinding: { + primary: KeyCode.Enter, + weight: KeybindingWeight.WorkbenchContrib, + when: null + }, + title: nls.localize('settings.focusSettingsList', "Focus settings list") + }); + } + + run(accessor: ServicesAccessor): void { + const preferencesEditor = getPreferencesEditor(accessor); + if (preferencesEditor instanceof SettingsEditor2) { + preferencesEditor.focusSettings(); + } + } + }); + + registerAction2(class extends Action2 { + constructor() { + super({ + id: SETTINGS_EDITOR_COMMAND_FOCUS_TOC, + precondition: CONTEXT_SETTINGS_EDITOR, + title: nls.localize('settings.focusSettingsTOC', "Focus settings TOC tree") + }); + } + + run(accessor: ServicesAccessor): void { + const preferencesEditor = getPreferencesEditor(accessor); + if (preferencesEditor instanceof SettingsEditor2) { + preferencesEditor.focusTOC(); + } + } + }); + + registerAction2(class extends Action2 { + constructor() { + super({ + id: SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU, + precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR), + keybinding: { + primary: KeyMod.Shift | KeyCode.F9, + weight: KeybindingWeight.WorkbenchContrib, + when: null + }, + title: nls.localize('settings.showContextMenu', "Show context menu") + }); + } + + run(accessor: ServicesAccessor): void { + const preferencesEditor = getPreferencesEditor(accessor); + if (preferencesEditor instanceof SettingsEditor2) { + preferencesEditor.showContextMenu(); + } + } + }); + } + + private registerKeybindingsActions() { + const that = this; + const category = { value: nls.localize('preferences', "Preferences"), original: 'Preferences' }; + registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.openGlobalKeybindings', + title: { value: nls.localize('openGlobalKeybindings', "Open Keyboard Shortcuts"), original: 'Open Keyboard Shortcuts' }, + category, + icon: { id: 'codicon/go-to-file' }, + keybinding: { + when: null, + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_S) + }, + menu: [ + { id: MenuId.CommandPalette }, + { + id: MenuId.EditorTitle, + when: ResourceContextKey.Resource.isEqualTo(that.environmentService.keybindingsResource.toString()), + group: 'navigation', + order: 1, + } + ] + }); + } + run(accessor: ServicesAccessor) { + return accessor.get(IPreferencesService).openGlobalKeybindingSettings(false); + } + }); + MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { + command: { + id: 'workbench.action.openGlobalKeybindings', + title: { value: nls.localize('Keyboard Shortcuts', "Keyboard Shortcuts"), original: 'Keyboard Shortcuts' } + }, + group: '2_keybindings', + order: 1 + }); + MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + command: { + id: 'workbench.action.openGlobalKeybindings', + title: { value: nls.localize('Keyboard Shortcuts', "Keyboard Shortcuts"), original: 'Keyboard Shortcuts' } + }, + group: '2_keybindings', + order: 1 + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.openDefaultKeybindingsFile', + title: { value: nls.localize('openDefaultKeybindingsFile', "Open Default Keyboard Shortcuts (JSON)"), original: 'Open Default Keyboard Shortcuts (JSON)' }, + category, + menu: { id: MenuId.CommandPalette } + }); + } + run(accessor: ServicesAccessor) { + return accessor.get(IPreferencesService).openDefaultKeybindingsFile(); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.openGlobalKeybindingsFile', + title: { value: nls.localize('openGlobalKeybindingsFile', "Open Keyboard Shortcuts (JSON)"), original: 'Open Keyboard Shortcuts (JSON)' }, + category, + icon: { id: 'codicon/go-to-file' }, + menu: [ + { id: MenuId.CommandPalette }, + { + id: MenuId.EditorTitle, + when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR), + group: 'navigation', + } + ] + }); + } + run(accessor: ServicesAccessor) { + return accessor.get(IPreferencesService).openGlobalKeybindingSettings(true); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS, + title: { value: nls.localize('showDefaultKeybindings', "Show Default Keybindings"), original: 'Show Default Keybindings' }, + menu: [ + { + id: MenuId.EditorTitle, + when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR), + group: '1_keyboard_preferences_actions' + } + ] + }); + } + run(accessor: ServicesAccessor) { + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + editorPane.search('@source:default'); + } + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS, + title: { value: nls.localize('showUserKeybindings', "Show User Keybindings"), original: 'Show User Keybindings' }, + menu: [ + { + id: MenuId.EditorTitle, + when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR), + group: '1_keyboard_preferences_actions' + } + ] + }); + } + run(accessor: ServicesAccessor) { + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + editorPane.search('@source:user'); + } + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, + title: nls.localize('clear', "Clear Search Results"), + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS), + primary: KeyCode.Escape, + } + }); + } + run(accessor: ServicesAccessor) { + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + editorPane.clearSearchResults(); + } + } + }); + + this.registerKeybindingEditorActions(); + } + + private registerKeybindingEditorActions(): void { + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: KEYBINDINGS_EDITOR_COMMAND_DEFINE, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS), + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_K), + handler: (accessor, args: any) => { + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + editorPane.defineKeybinding(editorPane.activeKeybindingEntry!); + } + } + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS), + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_E), + handler: (accessor, args: any) => { + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor && editorPane.activeKeybindingEntry!.keybindingItem.keybinding) { + editorPane.defineWhenExpression(editorPane.activeKeybindingEntry!); + } + } + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: KEYBINDINGS_EDITOR_COMMAND_REMOVE, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS), + primary: KeyCode.Delete, + mac: { + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.Backspace) + }, + handler: (accessor, args: any) => { + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + editorPane.removeKeybinding(editorPane.activeKeybindingEntry!); + } + } + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: KEYBINDINGS_EDITOR_COMMAND_RESET, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS), + primary: 0, + handler: (accessor, args: any) => { + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + editorPane.resetKeybinding(editorPane.activeKeybindingEntry!); + } + } + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: KEYBINDINGS_EDITOR_COMMAND_SEARCH, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR), + primary: KeyMod.CtrlCmd | KeyCode.KEY_F, + handler: (accessor, args: any) => { + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + editorPane.focusSearch(); + } + } + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS), + primary: KeyMod.Alt | KeyCode.KEY_K, + mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_K }, + handler: (accessor, args: any) => { + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + editorPane.recordSearchKeys(); + } + } + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR), + primary: KeyMod.Alt | KeyCode.KEY_P, + mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_P }, + handler: (accessor, args: any) => { + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + editorPane.toggleSortByPrecedence(); + } + } + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS), + primary: 0, + handler: (accessor, args: any) => { + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + editorPane.showSimilarKeybindings(editorPane.activeKeybindingEntry!); + } + } + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: KEYBINDINGS_EDITOR_COMMAND_COPY, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS), + primary: KeyMod.CtrlCmd | KeyCode.KEY_C, + handler: async (accessor, args: any) => { + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + await editorPane.copyKeybinding(editorPane.activeKeybindingEntry!); + } + } + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS), + primary: 0, + handler: async (accessor, args: any) => { + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + await editorPane.copyKeybindingCommand(editorPane.activeKeybindingEntry!); + } + } + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS), + primary: KeyCode.DownArrow, + handler: (accessor, args: any) => { + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + editorPane.focusKeybindings(); + } + } + }); + } + private updatePreferencesEditorMenuItem() { const commandId = '_workbench.openWorkspaceSettingsEditor'; if (this.workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE && !CommandsRegistry.getCommand(commandId)) { @@ -449,7 +1080,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: commandId, - title: OpenSettings2Action.LABEL, + title: OPEN_SETTINGS2_ACTION_TITLE, icon: { id: 'codicon/go-to-file' } }, when: ContextKeyExpr.and(ResourceContextKey.Resource.isEqualTo(this.preferencesService.workspaceSettingsResource!.toString()), WorkbenchStateContext.isEqualTo('workspace')), @@ -474,7 +1105,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: commandId, - title: OpenSettings2Action.LABEL, + title: OPEN_SETTINGS2_ACTION_TITLE, icon: { id: 'codicon/go-to-file' } }, when: ContextKeyExpr.and(ResourceContextKey.Resource.isEqualTo(this.preferencesService.getFolderSettingsResource(folder.uri)!.toString())), @@ -490,327 +1121,6 @@ const workbenchContributionsRegistry = Registry.as { - serviceAccessor.get(IInstantiationService).createInstance(OpenFolderSettingsAction, OpenFolderSettingsAction.ID, OpenFolderSettingsAction.LABEL).run(); -}); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: OpenFolderSettingsAction.ID, - title: { value: OpenFolderSettingsAction.LABEL, original: 'Open Folder Settings' }, - category: { value: nls.localize('preferencesCategory', "Preferences"), original: 'Preferences' } - }, - when: WorkbenchStateContext.isEqualTo('workspace') -}); - -CommandsRegistry.registerCommand(OpenWorkspaceSettingsAction.ID, serviceAccessor => { - serviceAccessor.get(IInstantiationService).createInstance(OpenWorkspaceSettingsAction, OpenWorkspaceSettingsAction.ID, OpenWorkspaceSettingsAction.LABEL).run(); -}); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: OpenWorkspaceSettingsAction.ID, - title: { value: OpenWorkspaceSettingsAction.LABEL, original: 'Open Workspace Settings' }, - category: { value: nls.localize('preferencesCategory', "Preferences"), original: 'Preferences' } - }, - when: WorkbenchStateContext.notEqualsTo('empty') -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS), - primary: KeyCode.Escape, - handler: (accessor, args: any) => { - const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control) { - control.clearSearchResults(); - } - } -}); - -CommandsRegistry.registerCommand(OpenGlobalKeybindingsFileAction.ID, serviceAccessor => { - serviceAccessor.get(IInstantiationService).createInstance(OpenGlobalKeybindingsFileAction, OpenGlobalKeybindingsFileAction.ID, OpenGlobalKeybindingsFileAction.LABEL).run(); -}); -MenuRegistry.appendMenuItem(MenuId.EditorTitle, { - command: { - id: OpenGlobalKeybindingsFileAction.ID, - title: OpenGlobalKeybindingsFileAction.LABEL, - icon: { id: 'codicon/go-to-file' } - }, - when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR), - group: 'navigation', -}); - -CommandsRegistry.registerCommand(KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS, serviceAccessor => { - const control = serviceAccessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control) { - control.search('@source:default'); - } -}); -MenuRegistry.appendMenuItem(MenuId.EditorTitle, { - command: { - id: KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS, - title: nls.localize('showDefaultKeybindings', "Show Default Keybindings") - }, - when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR), - group: '1_keyboard_preferences_actions' -}); - -CommandsRegistry.registerCommand(KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS, serviceAccessor => { - const control = serviceAccessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control) { - control.search('@source:user'); - } -}); -MenuRegistry.appendMenuItem(MenuId.EditorTitle, { - command: { - id: KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS, - title: nls.localize('showUserKeybindings', "Show User Keybindings") - }, - when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR), - group: '1_keyboard_preferences_actions' -}); - -function getPreferencesEditor(accessor: ServicesAccessor): PreferencesEditor | SettingsEditor2 | null { - const activeControl = accessor.get(IEditorService).activeControl; - if (activeControl instanceof PreferencesEditor || activeControl instanceof SettingsEditor2) { - return activeControl; - } - - return null; -} - -registerAction2(class extends Action2 { - constructor() { - super({ - id: SETTINGS_EDITOR_COMMAND_SEARCH, - precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR), - keybinding: { - primary: KeyMod.CtrlCmd | KeyCode.KEY_F, - weight: KeybindingWeight.EditorContrib, - when: null - }, - title: nls.localize('settings.focusSearch', "Focus settings search") - }); - } - - run(accessor: ServicesAccessor) { - const preferencesEditor = getPreferencesEditor(accessor); - if (preferencesEditor) { - preferencesEditor.focusSearch(); - } - } -}); - -registerAction2(class extends Action2 { - constructor() { - super({ - id: SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, - precondition: CONTEXT_SETTINGS_SEARCH_FOCUS, - keybinding: { - primary: KeyCode.Escape, - weight: KeybindingWeight.EditorContrib, - when: null - }, - title: nls.localize('settings.clearResults', "Clear settings search results") - }); - } - - run(accessor: ServicesAccessor) { - const preferencesEditor = getPreferencesEditor(accessor); - if (preferencesEditor) { - preferencesEditor.clearSearchResults(); - } - } -}); - -registerAction2(class extends Action2 { - constructor() { - super({ - id: SETTINGS_EDITOR_COMMAND_FOCUS_FILE, - precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_SEARCH_FOCUS, SuggestContext.Visible.toNegated()), - keybinding: { - primary: KeyCode.DownArrow, - weight: KeybindingWeight.EditorContrib, - when: null - }, - title: nls.localize('settings.focusFile', "Focus settings file") - }); - } - - run(accessor: ServicesAccessor, args: any): void { - const preferencesEditor = getPreferencesEditor(accessor); - if (preferencesEditor instanceof PreferencesEditor) { - preferencesEditor.focusSettingsFileEditor(); - } else if (preferencesEditor) { - preferencesEditor.focusSettings(); - } - } -}); - -registerAction2(class extends Action2 { - constructor() { - super({ - id: SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH, - precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_SEARCH_FOCUS, SuggestContext.Visible.toNegated()), - keybinding: { - primary: KeyCode.DownArrow, - weight: KeybindingWeight.WorkbenchContrib, - when: null - }, - title: nls.localize('settings.focusFile', "Focus settings file") - }); - } - - run(accessor: ServicesAccessor, args: any): void { - const preferencesEditor = getPreferencesEditor(accessor); - if (preferencesEditor instanceof PreferencesEditor) { - preferencesEditor.focusSettingsFileEditor(); - } else if (preferencesEditor) { - preferencesEditor.focusSettings(); - } - } -}); - -registerAction2(class extends Action2 { - constructor() { - super({ - id: SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING, - precondition: CONTEXT_SETTINGS_SEARCH_FOCUS, - keybinding: { - primary: KeyCode.Enter, - weight: KeybindingWeight.EditorContrib, - when: null - }, - title: nls.localize('settings.focusNextSetting', "Focus next setting") - }); - } - - run(accessor: ServicesAccessor): void { - const preferencesEditor = getPreferencesEditor(accessor); - if (preferencesEditor instanceof PreferencesEditor) { - preferencesEditor.focusNextResult(); - } - } -}); - -registerAction2(class extends Action2 { - constructor() { - super({ - id: SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING, - precondition: CONTEXT_SETTINGS_SEARCH_FOCUS, - keybinding: { - primary: KeyMod.Shift | KeyCode.Enter, - weight: KeybindingWeight.EditorContrib, - when: null - }, - title: nls.localize('settings.focusPreviousSetting', "Focus previous setting") - }); - } - - run(accessor: ServicesAccessor): void { - const preferencesEditor = getPreferencesEditor(accessor); - if (preferencesEditor instanceof PreferencesEditor) { - preferencesEditor.focusPreviousResult(); - } - } -}); - -registerAction2(class extends Action2 { - constructor() { - super({ - id: SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING, - precondition: CONTEXT_SETTINGS_SEARCH_FOCUS, - keybinding: { - primary: KeyMod.CtrlCmd | KeyCode.US_DOT, - weight: KeybindingWeight.EditorContrib, - when: null - }, - title: nls.localize('settings.editFocusedSetting', "Edit focused setting") - }); - } - - run(accessor: ServicesAccessor): void { - const preferencesEditor = getPreferencesEditor(accessor); - if (preferencesEditor instanceof PreferencesEditor) { - preferencesEditor.editFocusedPreference(); - } - } -}); - -registerAction2(class extends Action2 { - constructor() { - super({ - id: SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_LIST, - precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR, CONTEXT_TOC_ROW_FOCUS), - keybinding: { - primary: KeyCode.Enter, - weight: KeybindingWeight.WorkbenchContrib, - when: null - }, - title: nls.localize('settings.focusSettingsList', "Focus settings list") - }); - } - - run(accessor: ServicesAccessor): void { - const preferencesEditor = getPreferencesEditor(accessor); - if (preferencesEditor instanceof SettingsEditor2) { - preferencesEditor.focusSettings(); - } - } -}); - -registerAction2(class extends Action2 { - constructor() { - super({ - id: SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU, - precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR), - keybinding: { - primary: KeyMod.Shift | KeyCode.F9, - weight: KeybindingWeight.WorkbenchContrib, - when: null - }, - title: nls.localize('settings.showContextMenu', "Show context menu") - }); - } - - run(accessor: ServicesAccessor): void { - const preferencesEditor = getPreferencesEditor(accessor); - if (preferencesEditor instanceof SettingsEditor2) { - preferencesEditor.showContextMenu(); - } - } -}); - -CommandsRegistry.registerCommand(SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON, serviceAccessor => { - const control = serviceAccessor.get(IEditorService).activeControl as SettingsEditor2; - if (control instanceof SettingsEditor2) { - return control.switchToSettingsFile(); - } - - return Promise.resolve(null); -}); - -CommandsRegistry.registerCommand(SETTINGS_EDITOR_COMMAND_FILTER_MODIFIED, serviceAccessor => { - const control = serviceAccessor.get(IEditorService).activeControl as SettingsEditor2; - if (control instanceof SettingsEditor2) { - control.focusSearch(`@${MODIFIED_SETTING_TAG}`); - } -}); - -CommandsRegistry.registerCommand(SETTINGS_EDITOR_COMMAND_FILTER_ONLINE, serviceAccessor => { - const control = serviceAccessor.get(IEditorService).activeControl as SettingsEditor2; - if (control instanceof SettingsEditor2) { - control.focusSearch(`@tag:usesOnlineServices`); - } else { - serviceAccessor.get(IPreferencesService).openSettings(false, '@tag:usesOnlineServices'); - } -}); - // Preferences menu MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { @@ -820,109 +1130,3 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { order: 2, when: IsMacNativeContext.toNegated() // on macOS native the preferences menu is separate under the application menu }); - -MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { - group: '1_settings', - command: { - id: SETTINGS_COMMAND_OPEN_SETTINGS, - title: nls.localize({ key: 'miOpenSettings', comment: ['&& denotes a mnemonic'] }, "&&Settings") - }, - order: 1 -}); - -MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - group: '2_configuration', - command: { - id: SETTINGS_COMMAND_OPEN_SETTINGS, - title: nls.localize('settings', "Settings") - }, - order: 1 -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { - group: '1_settings', - command: { - id: SETTINGS_EDITOR_COMMAND_FILTER_ONLINE, - title: nls.localize({ key: 'miOpenOnlineSettings', comment: ['&& denotes a mnemonic'] }, "&&Online Services Settings") - }, - order: 2 -}); - -MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - group: '2_configuration', - command: { - id: SETTINGS_EDITOR_COMMAND_FILTER_ONLINE, - title: nls.localize('onlineServices', "Online Services Settings") - }, - order: 2 -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { - group: '2_keybindings', - command: { - id: OpenGlobalKeybindingsAction.ID, - title: nls.localize({ key: 'miOpenKeymap', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts") - }, - order: 1 -}); - -MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - group: '2_keybindings', - command: { - id: OpenGlobalKeybindingsAction.ID, - title: nls.localize('keyboardShortcuts', "Keyboard Shortcuts") - }, - order: 1 -}); - -// Editor tool items - -MenuRegistry.appendMenuItem(MenuId.EditorTitle, { - command: { - id: SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON, - title: nls.localize('openSettingsJson', "Open Settings (JSON)"), - icon: { id: 'codicon/go-to-file' } - }, - group: 'navigation', - order: 1, - when: ContextKeyExpr.and( - CONTEXT_SETTINGS_EDITOR, - CONTEXT_SETTINGS_JSON_EDITOR.toNegated() - ) -}); - -MenuRegistry.appendMenuItem(MenuId.EditorTitle, { - command: { - id: SETTINGS_EDITOR_COMMAND_FILTER_MODIFIED, - title: nls.localize('filterModifiedLabel', "Show modified settings") - }, - group: '1_filter', - order: 1, - when: ContextKeyExpr.and( - CONTEXT_SETTINGS_EDITOR, - CONTEXT_SETTINGS_JSON_EDITOR.toNegated() - ) -}); - -MenuRegistry.appendMenuItem(MenuId.EditorTitle, { - command: { - id: SETTINGS_EDITOR_COMMAND_FILTER_ONLINE, - title: nls.localize('filterOnlineServicesLabel', "Show settings for online services"), - }, - group: '1_filter', - order: 2, - when: ContextKeyExpr.and( - CONTEXT_SETTINGS_EDITOR, - CONTEXT_SETTINGS_JSON_EDITOR.toNegated() - ) -}); - -MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { - group: '2_workspace', - order: 20, - command: { - id: OPEN_FOLDER_SETTINGS_COMMAND, - title: OPEN_FOLDER_SETTINGS_LABEL - }, - when: ContextKeyExpr.and(ExplorerRootContext, ExplorerFolderContext) -}); diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts b/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts index b7742712238..8b095ecb557 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts @@ -4,233 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import { Action } from 'vs/base/common/actions'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import * as nls from 'vs/nls'; -import { ICommandService } from 'vs/platform/commands/common/commands'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; -export class OpenRawDefaultSettingsAction extends Action { - - static readonly ID = 'workbench.action.openRawDefaultSettings'; - static readonly LABEL = nls.localize('openRawDefaultSettings', "Open Default Settings (JSON)"); - - constructor( - id: string, - label: string, - @IPreferencesService private readonly preferencesService: IPreferencesService - ) { - super(id, label); - } - - run(event?: any): Promise { - return this.preferencesService.openRawDefaultSettings(); - } -} - -export class OpenSettings2Action extends Action { - - static readonly ID = 'workbench.action.openSettings2'; - static readonly LABEL = nls.localize('openSettings2', "Open Settings (UI)"); - - constructor( - id: string, - label: string, - @IPreferencesService private readonly preferencesService: IPreferencesService - ) { - super(id, label); - } - - run(event?: any): Promise { - return this.preferencesService.openSettings(false, undefined); - } -} - -export class OpenSettingsJsonAction extends Action { - - static readonly ID = 'workbench.action.openSettingsJson'; - static readonly LABEL = nls.localize('openSettingsJson', "Open Settings (JSON)"); - - constructor( - id: string, - label: string, - @IPreferencesService private readonly preferencesService: IPreferencesService - ) { - super(id, label); - } - - run(event?: any): Promise { - return this.preferencesService.openSettings(true, undefined); - } -} - -export class OpenGlobalSettingsAction extends Action { - - static readonly ID = 'workbench.action.openGlobalSettings'; - static readonly LABEL = nls.localize('openGlobalSettings', "Open User Settings"); - - constructor( - id: string, - label: string, - @IPreferencesService private readonly preferencesService: IPreferencesService, - ) { - super(id, label); - } - - run(event?: any): Promise { - return this.preferencesService.openGlobalSettings(); - } -} - -export class OpenRemoteSettingsAction extends Action { - - static readonly ID = 'workbench.action.openRemoteSettings'; - - constructor( - id: string, - label: string, - @IPreferencesService private readonly preferencesService: IPreferencesService, - ) { - super(id, label); - } - - run(event?: any): Promise { - return this.preferencesService.openRemoteSettings(); - } -} - -export class OpenGlobalKeybindingsAction extends Action { - - static readonly ID = 'workbench.action.openGlobalKeybindings'; - static readonly LABEL = nls.localize('openGlobalKeybindings', "Open Keyboard Shortcuts"); - - constructor( - id: string, - label: string, - @IPreferencesService private readonly preferencesService: IPreferencesService - ) { - super(id, label); - } - - run(event?: any): Promise { - return this.preferencesService.openGlobalKeybindingSettings(false); - } -} - -export class OpenGlobalKeybindingsFileAction extends Action { - - static readonly ID = 'workbench.action.openGlobalKeybindingsFile'; - static readonly LABEL = nls.localize('openGlobalKeybindingsFile', "Open Keyboard Shortcuts (JSON)"); - - constructor( - id: string, - label: string, - @IPreferencesService private readonly preferencesService: IPreferencesService - ) { - super(id, label); - } - - run(event?: any): Promise { - return this.preferencesService.openGlobalKeybindingSettings(true); - } -} - -export class OpenDefaultKeybindingsFileAction extends Action { - - static readonly ID = 'workbench.action.openDefaultKeybindingsFile'; - static readonly LABEL = nls.localize('openDefaultKeybindingsFile', "Open Default Keyboard Shortcuts (JSON)"); - - constructor( - id: string, - label: string, - @IPreferencesService private readonly preferencesService: IPreferencesService - ) { - super(id, label); - } - - run(event?: any): Promise { - return this.preferencesService.openDefaultKeybindingsFile(); - } -} - -export class OpenWorkspaceSettingsAction extends Action { - - static readonly ID = 'workbench.action.openWorkspaceSettings'; - static readonly LABEL = nls.localize('openWorkspaceSettings', "Open Workspace Settings"); - - private readonly disposables = new DisposableStore(); - - constructor( - id: string, - label: string, - @IPreferencesService private readonly preferencesService: IPreferencesService, - @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - ) { - super(id, label); - this.update(); - this.disposables.add(this.workspaceContextService.onDidChangeWorkbenchState(() => this.update(), this)); - } - - private update(): void { - this.enabled = this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY; - } - - run(event?: any): Promise { - return this.preferencesService.openWorkspaceSettings(); - } - - dispose(): void { - this.disposables.dispose(); - super.dispose(); - } -} - -export const OPEN_FOLDER_SETTINGS_COMMAND = '_workbench.action.openFolderSettings'; -export const OPEN_FOLDER_SETTINGS_LABEL = nls.localize('openFolderSettings', "Open Folder Settings"); -export class OpenFolderSettingsAction extends Action { - - static readonly ID = 'workbench.action.openFolderSettings'; - static readonly LABEL = OPEN_FOLDER_SETTINGS_LABEL; - - constructor( - id: string, - label: string, - @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @IPreferencesService private readonly preferencesService: IPreferencesService, - @ICommandService private readonly commandService: ICommandService, - ) { - super(id, label); - this.update(); - this._register(this.workspaceContextService.onDidChangeWorkbenchState(() => this.update(), this)); - this._register(this.workspaceContextService.onDidChangeWorkspaceFolders(() => this.update(), this)); - } - - private update(): void { - this.enabled = this.workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE && this.workspaceContextService.getWorkspace().folders.length > 0; - } - - run(): Promise { - return this.commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID) - .then(workspaceFolder => { - if (workspaceFolder) { - return this.preferencesService.openFolderSettings(workspaceFolder.uri); - } - - return undefined; - }); - } -} - export class ConfigureLanguageBasedSettingsAction extends Action { static readonly ID = 'workbench.action.configureLanguageBasedSettings'; - static readonly LABEL = nls.localize('configureLanguageBasedSettings', "Configure Language Specific Settings..."); + static readonly LABEL = { value: nls.localize('configureLanguageBasedSettings', "Configure Language Specific Settings..."), original: 'Configure Language Specific Settings...' }; constructor( id: string, @@ -270,7 +55,7 @@ export class ConfigureLanguageBasedSettingsAction extends Action { if (pick) { const modeId = this.modeService.getModeIdForLanguageName(pick.label.toLowerCase()); if (typeof modeId === 'string') { - return this.preferencesService.configureSettingsForLanguage(modeId); + return this.preferencesService.openGlobalSettings(true, { editSetting: `[${modeId}]` }); } } return undefined; diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts index 76786b81061..aa3fdb6652a 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts @@ -249,10 +249,10 @@ export class PreferencesEditor extends BaseEditor { private switchSettings(target: SettingsTarget): void { // Focus the editor if this editor is not active editor - if (this.editorService.activeControl !== this) { + if (this.editorService.activeEditorPane !== this) { this.focus(); } - const promise: Promise = this.input && this.input.isDirty() ? this.editorService.save({ editor: this.input, groupId: this.group!.id }) : Promise.resolve(true); + const promise = this.input && this.input.isDirty() ? this.editorService.save({ editor: this.input, groupId: this.group!.id }) : Promise.resolve(true); promise.then(() => { if (target === ConfigurationTarget.USER_LOCAL) { this.preferencesService.switchSettings(ConfigurationTarget.USER_LOCAL, this.preferencesService.userSettingsResource, true); diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts index 56318f1ecf1..5f286260679 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts @@ -28,7 +28,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { activeContrastBorder, badgeBackground, badgeForeground, contrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; -import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { PANEL_ACTIVE_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND } from 'vs/workbench/common/theme'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -642,7 +642,7 @@ export class SearchWidget extends Widget { this.countElement.style.borderStyle = border ? 'solid' : ''; this.countElement.style.borderColor = border; - const color = this.themeService.getTheme().getColor(badgeForeground); + const color = this.themeService.getColorTheme().getColor(badgeForeground); this.countElement.style.color = color ? color.toString() : ''; })); } @@ -803,7 +803,7 @@ export class EditPreferenceWidget extends Disposable { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { collector.addRule(` .settings-tabs-widget > .monaco-action-bar .action-item .action-label:focus, diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 30eb7a13953..ea9f21f3952 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -6,34 +6,41 @@ import * as DOM from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { Button } from 'vs/base/browser/ui/button/button'; import { ITreeElement } from 'vs/base/browser/ui/tree/tree'; import { Action } from 'vs/base/common/actions'; import * as arrays from 'vs/base/common/arrays'; -import { Delayer, ThrottledDelayer, timeout } from 'vs/base/common/async'; +import { Delayer, ThrottledDelayer, timeout, IntervalTimer } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import * as collections from 'vs/base/common/collections'; import { getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors'; import { Iterator } from 'vs/base/common/iterator'; import { KeyCode } from 'vs/base/common/keyCodes'; +import { Disposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; import { isArray, withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/settingsEditor2'; import { localize } from 'vs/nls'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { ConfigurationTarget, IConfigurationOverrides, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IEditorModel } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IProductService } from 'vs/platform/product/common/productService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { badgeBackground, badgeForeground, contrastBorder, editorForeground } from 'vs/platform/theme/common/colorRegistry'; import { attachStylerCallback } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; +import { getUserDataSyncStore, IUserDataSyncService, SyncStatus, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { IEditor, IEditorMemento } from 'vs/workbench/common/editor'; +import { IEditorMemento, IEditorPane } from 'vs/workbench/common/editor'; import { attachSuggestEnabledInputBoxStyler, SuggestEnabledInput } from 'vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput'; import { SettingsTarget, SettingsTargetsWidget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; import { commonlyUsedData, tocData } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; @@ -46,7 +53,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IPreferencesService, ISearchResult, ISettingsEditorModel, ISettingsEditorOptions, SettingsEditorOptions, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; -import { IEditorModel } from 'vs/platform/editor/common/editor'; +import { fromNow } from 'vs/base/common/date'; function createGroupIterator(group: SettingsTreeGroupElement): Iterator> { const groupsIt = Iterator.fromArray(group.children); @@ -67,6 +74,7 @@ interface IFocusEventFromScroll extends KeyboardEvent { fromScroll: true; } +const SETTINGS_AUTOSAVE_NOTIFIED_KEY = 'hasNotifiedOfSettingsAutosave'; const SETTINGS_EDITOR_STATE_KEY = 'settingsEditorState'; export class SettingsEditor2 extends BaseEditor { @@ -157,7 +165,9 @@ export class SettingsEditor2 extends BaseEditor { @IStorageService private readonly storageService: IStorageService, @INotificationService private readonly notificationService: INotificationService, @IEditorGroupsService protected editorGroupService: IEditorGroupsService, - @IKeybindingService private readonly keybindingService: IKeybindingService + @IKeybindingService private readonly keybindingService: IKeybindingService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, + @IProductService private readonly productService: IProductService, ) { super(SettingsEditor2.ID, telemetryService, themeService, storageService); this.delayedFilterLogging = new Delayer(1000); @@ -183,6 +193,8 @@ export class SettingsEditor2 extends BaseEditor { this.onConfigUpdate(e.affectedKeys); } })); + + storageKeysSyncRegistryService.registerStorageKey({ key: SETTINGS_AUTOSAVE_NOTIFIED_KEY, version: 1 }); } get minimumWidth(): number { return 375; } @@ -346,6 +358,10 @@ export class SettingsEditor2 extends BaseEditor { } } + focusTOC(): void { + this.tocTree.domFocus(); + } + showContextMenu(): void { const activeElement = this.getActiveElementInSettingsTree(); if (!activeElement) { @@ -445,6 +461,10 @@ export class SettingsEditor2 extends BaseEditor { this.settingsTargetsWidget.settingsTarget = ConfigurationTarget.USER_LOCAL; this.settingsTargetsWidget.onDidTargetChange(target => this.onDidSettingsTargetChange(target)); + if (syncAllowed(this.productService, this.configurationService)) { + this._register(this.instantiationService.createInstance(SyncControls, headerControlsContainer)); + } + this.controlsElement = DOM.append(searchContainer, DOM.$('.settings-clear-widget')); const actionBar = this._register(new ActionBar(this.controlsElement, { @@ -493,15 +513,14 @@ export class SettingsEditor2 extends BaseEditor { } } - switchToSettingsFile(): Promise { - const query = parseQuery(this.searchWidget.getValue()); - return this.openSettingsFile(query.query); + switchToSettingsFile(): Promise { + const query = parseQuery(this.searchWidget.getValue()).query; + return this.openSettingsFile({ query }); } - private async openSettingsFile(query?: string): Promise { + private async openSettingsFile(options?: ISettingsEditorOptions): Promise { const currentSettingsTarget = this.settingsTargetsWidget.settingsTarget; - const options: ISettingsEditorOptions = { query }; if (currentSettingsTarget === ConfigurationTarget.USER_LOCAL) { return this.preferencesService.openGlobalSettings(true, options); } else if (currentSettingsTarget === ConfigurationTarget.USER_REMOTE) { @@ -660,7 +679,7 @@ export class SettingsEditor2 extends BaseEditor { this.settingRenderers = this.instantiationService.createInstance(SettingTreeRenderers); this._register(this.settingRenderers.onDidChangeSetting(e => this.onDidChangeSetting(e.key, e.value, e.type))); this._register(this.settingRenderers.onDidOpenSettings(settingKey => { - this.openSettingsFile(settingKey); + this.openSettingsFile({ editSetting: settingKey }); })); this._register(this.settingRenderers.onDidClickSettingLink(settingName => this.onDidClickSetting(settingName))); this._register(this.settingRenderers.onDidFocusSetting(element => { @@ -701,8 +720,8 @@ export class SettingsEditor2 extends BaseEditor { } private notifyNoSaveNeeded() { - if (!this.storageService.getBoolean('hasNotifiedOfSettingsAutosave', StorageScope.GLOBAL, false)) { - this.storageService.store('hasNotifiedOfSettingsAutosave', true, StorageScope.GLOBAL); + if (!this.storageService.getBoolean(SETTINGS_AUTOSAVE_NOTIFIED_KEY, StorageScope.GLOBAL, false)) { + this.storageService.store(SETTINGS_AUTOSAVE_NOTIFIED_KEY, true, StorageScope.GLOBAL); this.notificationService.info(localize('settingsNoSaveNeeded', "Your changes are automatically saved as you edit.")); } } @@ -1363,7 +1382,80 @@ export class SettingsEditor2 extends BaseEditor { } } +class SyncControls extends Disposable { + private readonly lastSyncedLabel!: HTMLElement; + private readonly turnOnSyncButton!: Button; + + constructor( + container: HTMLElement, + @ICommandService private readonly commandService: ICommandService, + @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, + @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService + ) { + super(); + + const headerRightControlsContainer = DOM.append(container, $('.settings-right-controls')); + const turnOnSyncButtonContainer = DOM.append(headerRightControlsContainer, $('.turn-on-sync')); + this.turnOnSyncButton = this._register(new Button(turnOnSyncButtonContainer, { title: true })); + this.lastSyncedLabel = DOM.append(headerRightControlsContainer, $('.last-synced-label')); + DOM.hide(this.lastSyncedLabel); + + this.turnOnSyncButton.enabled = true; + this.turnOnSyncButton.label = localize('turnOnSyncButton', "Turn on Preferences Sync"); + DOM.hide(this.turnOnSyncButton.element); + + this._register(this.turnOnSyncButton.onDidClick(async () => { + await this.commandService.executeCommand('workbench.userData.actions.syncStart'); + })); + + this.updateLastSyncedTime(); + this._register(this.userDataSyncService.onDidChangeLastSyncTime(() => { + this.updateLastSyncedTime(); + })); + + const updateLastSyncedTimer = this._register(new IntervalTimer()); + updateLastSyncedTimer.cancelAndSet(() => this.updateLastSyncedTime(), 60 * 1000); + + this.update(); + this._register(this.userDataSyncService.onDidChangeStatus(() => { + this.update(); + })); + + this._register(this.userDataSyncEnablementService.onDidChangeEnablement(() => { + this.update(); + })); + } + + private updateLastSyncedTime(): void { + const last = this.userDataSyncService.lastSyncTime; + if (typeof last === 'number') { + const d = fromNow(last, true); + this.lastSyncedLabel.textContent = localize('lastSyncedLabel', "Last synced: {0}", d); + } else { + this.lastSyncedLabel.textContent = ''; + } + } + + private update(): void { + if (this.userDataSyncService.status === SyncStatus.Uninitialized) { + return; + } + + if (this.userDataSyncEnablementService.isEnabled()) { + DOM.show(this.lastSyncedLabel); + DOM.hide(this.turnOnSyncButton.element); + } else { + DOM.hide(this.lastSyncedLabel); + DOM.show(this.turnOnSyncButton.element); + } + } +} + interface ISettingsEditor2State { searchQuery: string; target: SettingsTarget; } + +function syncAllowed(productService: IProductService, configService: IConfigurationService): boolean { + return !!getUserDataSyncStore(productService, configService); +} diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index fb75f474a2a..4763193df8c 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -165,6 +165,11 @@ export const tocData: ITOCEntry = { label: localize('problems', "Problems"), settings: ['problems.*'] }, + { + id: 'features/output', + label: localize('output', "Output"), + settings: ['output.*'] + }, { id: 'features/comments', label: localize('comments', "Comments"), diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index bae1aee1cd5..3f52a562eba 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -41,7 +41,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { editorBackground, errorForeground, focusBorder, foreground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground } from 'vs/platform/theme/common/colorRegistry'; import { attachButtonStyler, attachInputBoxStyler, attachSelectBoxStyler, attachStyler } from 'vs/platform/theme/common/styler'; -import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; import { ITOCEntry } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; import { ISettingsEditorViewState, settingKeyToDisplayFormat, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeNewExtensionsElement, SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; @@ -50,6 +50,7 @@ import { SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ISetting, ISettingsGroup, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; import { IUserDataSyncEnablementService, getDefaultIgnoredSettings } from 'vs/platform/userDataSync/common/userDataSync'; +import { CodiconLabel } from 'vs/base/browser/ui/codiconLabel/codiconLabel'; const $ = DOM.$; @@ -204,6 +205,7 @@ interface ISettingItemTemplate extends IDisposableTemplate { controlElement: HTMLElement; deprecationWarningElement: HTMLElement; otherOverridesElement: HTMLElement; + syncIgnoredElement: HTMLElement; toolbar: ToolBar; elementDisposables: IDisposable[]; } @@ -300,6 +302,10 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre private readonly _onDidFocusSetting = this._register(new Emitter()); readonly onDidFocusSetting: Event = this._onDidFocusSetting.event; + private ignoredSettings: string[]; + private readonly _onDidChangeIgnoredSettings = this._register(new Emitter()); + readonly onDidChangeIgnoredSettings: Event = this._onDidChangeIgnoredSettings.event; + // Put common injections back here constructor( private readonly settingActions: IAction[], @@ -311,8 +317,17 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre @ICommandService protected readonly _commandService: ICommandService, @IContextMenuService protected readonly _contextMenuService: IContextMenuService, @IKeybindingService protected readonly _keybindingService: IKeybindingService, + @IConfigurationService protected readonly _configService: IConfigurationService, ) { super(); + + this.ignoredSettings = getIgnoredSettings(getDefaultIgnoredSettings(), this._configService); + this._register(this._configService.onDidChangeConfiguration(e => { + if (e.affectedKeys.includes('sync.ignoredSettings')) { + this.ignoredSettings = getIgnoredSettings(getDefaultIgnoredSettings(), this._configService); + this._onDidChangeIgnoredSettings.fire(); + } + })); } renderTemplate(container: HTMLElement): any { @@ -323,6 +338,14 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre throw new Error('to override'); } + protected createSyncIgnoredElement(container: HTMLElement): HTMLElement { + const syncIgnoredElement = DOM.append(container, $('span.setting-item-ignored')); + const syncIgnoredLabel = new CodiconLabel(syncIgnoredElement); + syncIgnoredLabel.text = `($(sync-ignored) ${localize('extensionSyncIgnoredLabel', 'Sync: Ignored')})`; + + return syncIgnoredElement; + } + protected renderCommonTemplate(tree: any, _container: HTMLElement, typeClass: string): ISettingItemTemplate { DOM.addClass(_container, 'setting-item'); DOM.addClass(_container, 'setting-item-' + typeClass); @@ -333,6 +356,8 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre const categoryElement = DOM.append(labelCategoryContainer, $('span.setting-item-category')); const labelElement = DOM.append(labelCategoryContainer, $('span.setting-item-label')); const otherOverridesElement = DOM.append(titleElement, $('span.setting-item-overrides')); + const syncIgnoredElement = this.createSyncIgnoredElement(titleElement); + const descriptionElement = DOM.append(container, $('.setting-item-description')); const modifiedIndicatorElement = DOM.append(container, $('.setting-item-modified-indicator')); modifiedIndicatorElement.title = localize('modified', "Modified"); @@ -358,6 +383,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre controlElement, deprecationWarningElement, otherOverridesElement, + syncIgnoredElement, toolbar }; @@ -447,8 +473,10 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre template.descriptionElement.id = baseId + '_setting_description'; template.otherOverridesElement.innerHTML = ''; - + template.otherOverridesElement.style.display = 'none'; if (element.overriddenScopeList.length) { + template.otherOverridesElement.style.display = 'inline'; + const otherOverridesLabel = element.isConfigured ? localize('alsoConfiguredIn', "Also modified in") : localize('configuredIn', "Modified in"); @@ -482,6 +510,14 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre DOM.toggleClass(template.containerElement, 'is-deprecated', !!deprecationText); this.renderValue(element, template, onChange); + + const update = () => { + template.syncIgnoredElement.style.display = this.ignoredSettings.includes(element.setting.key) ? 'inline' : 'none'; + }; + update(); + template.elementDisposables.push(this.onDidChangeIgnoredSettings(() => { + update(); + })); } private renderDescriptionMarkdown(element: SettingsTreeSettingElement, text: string, disposeables: DisposableStore): HTMLElement { @@ -1085,6 +1121,7 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre const categoryElement = DOM.append(titleElement, $('span.setting-item-category')); const labelElement = DOM.append(titleElement, $('span.setting-item-label')); const otherOverridesElement = DOM.append(titleElement, $('span.setting-item-overrides')); + const syncIgnoredElement = this.createSyncIgnoredElement(titleElement); const descriptionAndValueElement = DOM.append(container, $('.setting-item-value-description')); const controlElement = DOM.append(descriptionAndValueElement, $('.setting-item-bool-control')); @@ -1138,6 +1175,7 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre descriptionElement, deprecationWarningElement, otherOverridesElement, + syncIgnoredElement, toolbar }; @@ -1508,7 +1546,7 @@ export class SettingsTree extends ObjectTree { }); this.disposables.clear(); - this.disposables.add(registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { + this.disposables.add(registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const activeBorderColor = theme.getColor(focusBorder); if (activeBorderColor) { // TODO@rob - why isn't this applied when added to the stylesheet from tocTree.ts? Seems like a chromium glitch. diff --git a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts index 1d5a396dab6..921a26078af 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts @@ -18,7 +18,7 @@ import { localize } from 'vs/nls'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { foreground, inputBackground, inputBorder, inputForeground, listActiveSelectionBackground, listActiveSelectionForeground, listHoverBackground, listHoverForeground, listInactiveSelectionBackground, listInactiveSelectionForeground, registerColor, selectBackground, selectBorder, selectForeground, textLinkForeground, textPreformatForeground, editorWidgetBorder, textLinkActiveForeground, simpleCheckboxBackground, simpleCheckboxForeground, simpleCheckboxBorder } from 'vs/platform/theme/common/colorRegistry'; import { attachButtonStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler'; -import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { disposableTimeout } from 'vs/base/common/async'; import { isUndefinedOrNull } from 'vs/base/common/types'; @@ -51,7 +51,7 @@ export const settingsNumberInputBackground = registerColor('settings.numberInput export const settingsNumberInputForeground = registerColor('settings.numberInputForeground', { dark: inputForeground, light: inputForeground, hc: inputForeground }, localize('numberInputBoxForeground', "Settings editor number input box foreground.")); export const settingsNumberInputBorder = registerColor('settings.numberInputBorder', { dark: inputBorder, light: inputBorder, hc: inputBorder }, localize('numberInputBoxBorder', "Settings editor number input box border.")); -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const checkboxBackgroundColor = theme.getColor(settingsCheckboxBackground); if (checkboxBackgroundColor) { collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-bool .setting-value-checkbox { background-color: ${checkboxBackgroundColor} !important; }`); diff --git a/src/vs/workbench/contrib/preferences/common/preferences.ts b/src/vs/workbench/contrib/preferences/common/preferences.ts index c9901f5f4eb..df315b95efa 100644 --- a/src/vs/workbench/contrib/preferences/common/preferences.ts +++ b/src/vs/workbench/contrib/preferences/common/preferences.ts @@ -6,7 +6,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ISettingsEditorModel, ISearchResult } from 'vs/workbench/services/preferences/common/preferences'; -import { IEditor } from 'vs/workbench/common/editor'; +import { IEditorPane } from 'vs/workbench/common/editor'; import { IKeybindingItemEntry } from 'vs/workbench/services/preferences/common/keybindingsEditorModel'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; @@ -43,7 +43,7 @@ export interface ISearchProvider { searchModel(preferencesModel: ISettingsEditorModel, token?: CancellationToken): Promise; } -export interface IKeybindingsEditor extends IEditor { +export interface IKeybindingsEditorPane extends IEditorPane { readonly activeKeybindingEntry: IKeybindingItemEntry | null; readonly onDefineWhenExpression: Event; @@ -83,6 +83,7 @@ export const SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING = 'settings.action.edi export const SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH = 'settings.action.focusSettingsFromSearch'; export const SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_LIST = 'settings.action.focusSettingsList'; export const SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU = 'settings.action.showContextMenu'; +export const SETTINGS_EDITOR_COMMAND_FOCUS_TOC = 'settings.action.focusTOC'; export const SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON = 'settings.switchToJSON'; export const SETTINGS_EDITOR_COMMAND_FILTER_MODIFIED = 'settings.filterByModified'; diff --git a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts new file mode 100644 index 00000000000..f15b64530ef --- /dev/null +++ b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts @@ -0,0 +1,181 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { ICommandQuickPick, CommandsHistory } from 'vs/platform/quickinput/browser/commandsQuickAccess'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IMenuService, MenuId, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { timeout } from 'vs/base/common/async'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { DisposableStore, toDisposable, dispose } from 'vs/base/common/lifecycle'; +import { AbstractEditorCommandsQuickAccessProvider } from 'vs/editor/contrib/quickAccess/commandsQuickAccess'; +import { IEditor } from 'vs/editor/common/editorCommon'; +import { Language } from 'vs/base/common/platform'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { DefaultQuickAccessFilterValue } from 'vs/platform/quickinput/common/quickAccess'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWorkbenchQuickAccessConfiguration } from 'vs/workbench/browser/quickaccess'; +import { stripCodicons } from 'vs/base/common/codicons'; +import { Action } from 'vs/base/common/actions'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IStorageService } from 'vs/platform/storage/common/storage'; + +export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAccessProvider { + + // If extensions are not yet registered, we wait for a little moment to give them + // a chance to register so that the complete set of commands shows up as result + // We do not want to delay functionality beyond that time though to keep the commands + // functional. + private readonly extensionRegistrationRace = Promise.race([ + timeout(800), + this.extensionService.whenInstalledExtensionsRegistered() + ]); + + protected get activeTextEditorControl(): IEditor | undefined { return this.editorService.activeTextEditorControl; } + + get defaultFilterValue(): DefaultQuickAccessFilterValue | undefined { + if (this.configuration.preserveInput) { + return DefaultQuickAccessFilterValue.LAST; + } + + return undefined; + } + + constructor( + @IEditorService private readonly editorService: IEditorService, + @IMenuService private readonly menuService: IMenuService, + @IExtensionService private readonly extensionService: IExtensionService, + @IInstantiationService instantiationService: IInstantiationService, + @IKeybindingService keybindingService: IKeybindingService, + @ICommandService commandService: ICommandService, + @ITelemetryService telemetryService: ITelemetryService, + @INotificationService notificationService: INotificationService, + @IConfigurationService private readonly configurationService: IConfigurationService + ) { + super({ + showAlias: !Language.isDefaultVariant(), + noResultsPick: { + label: localize('noCommandResults', "No matching commands"), + commandId: '' + } + }, instantiationService, keybindingService, commandService, telemetryService, notificationService); + } + + private get configuration() { + const commandPaletteConfig = this.configurationService.getValue().workbench.commandPalette; + + return { + preserveInput: commandPaletteConfig.preserveInput + }; + } + + protected async getCommandPicks(disposables: DisposableStore, token: CancellationToken): Promise> { + + // wait for extensions registration or 800ms once + await this.extensionRegistrationRace; + + if (token.isCancellationRequested) { + return []; + } + + return [ + ...this.getCodeEditorCommandPicks(), + ...this.getGlobalCommandPicks(disposables) + ]; + } + + private getGlobalCommandPicks(disposables: DisposableStore): ICommandQuickPick[] { + const globalCommandPicks: ICommandQuickPick[] = []; + + const globalCommandsMenu = this.editorService.invokeWithinEditorContext(accessor => + this.menuService.createMenu(MenuId.CommandPalette, accessor.get(IContextKeyService)) + ); + + const globalCommandsMenuActions = globalCommandsMenu.getActions() + .reduce((r, [, actions]) => [...r, ...actions], >[]) + .filter(action => action instanceof MenuItemAction) as MenuItemAction[]; + + for (const action of globalCommandsMenuActions) { + + // Label + let label = (typeof action.item.title === 'string' ? action.item.title : action.item.title.value) || action.item.id; + + // Category + const category = typeof action.item.category === 'string' ? action.item.category : action.item.category?.value; + if (category) { + label = localize('commandWithCategory', "{0}: {1}", category, label); + } + + // Alias + const aliasLabel = typeof action.item.title !== 'string' ? action.item.title.original : undefined; + const aliasCategory = (category && action.item.category && typeof action.item.category !== 'string') ? action.item.category.original : undefined; + const commandAlias = (aliasLabel && category) ? + aliasCategory ? `${aliasCategory}: ${aliasLabel}` : `${category}: ${aliasLabel}` : + aliasLabel; + + globalCommandPicks.push({ + commandId: action.item.id, + commandAlias, + label: stripCodicons(label) + }); + } + + // Cleanup + globalCommandsMenu.dispose(); + disposables.add(toDisposable(() => dispose(globalCommandsMenuActions))); + + return globalCommandPicks; + } +} + +//#region Actions + +export class ShowAllCommandsAction extends Action { + + static readonly ID = 'workbench.action.showCommands'; + static readonly LABEL = localize('showTriggerActions', "Show All Commands"); + + constructor( + id: string, + label: string, + @IQuickInputService private readonly quickInputService: IQuickInputService + ) { + super(id, label); + } + + async run(): Promise { + this.quickInputService.quickAccess.show(CommandsQuickAccessProvider.PREFIX); + } +} + +export class ClearCommandHistoryAction extends Action { + + static readonly ID = 'workbench.action.clearCommandHistory'; + static readonly LABEL = localize('clearCommandHistory', "Clear Command History"); + + constructor( + id: string, + label: string, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IStorageService private readonly storageService: IStorageService + ) { + super(id, label); + } + + async run(): Promise { + const commandHistoryLength = CommandsHistory.getConfiguredCommandHistoryLength(this.configurationService); + if (commandHistoryLength > 0) { + CommandsHistory.clearHistory(this.configurationService, this.storageService); + } + } +} + +//#endregion diff --git a/src/vs/workbench/contrib/quickaccess/browser/quickAccess.contribution.ts b/src/vs/workbench/contrib/quickaccess/browser/quickAccess.contribution.ts new file mode 100644 index 00000000000..87363412499 --- /dev/null +++ b/src/vs/workbench/contrib/quickaccess/browser/quickAccess.contribution.ts @@ -0,0 +1,152 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { HelpQuickAccessProvider } from 'vs/platform/quickinput/browser/helpQuickAccess'; +import { ViewQuickAccessProvider, OpenViewPickerAction, QuickAccessViewPickerAction } from 'vs/workbench/contrib/quickaccess/browser/viewQuickAccess'; +import { CommandsQuickAccessProvider, ShowAllCommandsAction, ClearCommandHistoryAction } from 'vs/workbench/contrib/quickaccess/browser/commandsQuickAccess'; +import { MenuRegistry, MenuId, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; +import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { inQuickPickContext, getQuickNavigateHandler } from 'vs/workbench/browser/quickaccess'; +import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; + +//#region Quick Access Proviers + +const quickAccessRegistry = Registry.as(Extensions.Quickaccess); + +quickAccessRegistry.registerQuickAccessProvider({ + ctor: HelpQuickAccessProvider, + prefix: HelpQuickAccessProvider.PREFIX, + placeholder: localize('helpQuickAccessPlaceholder', "Type '{0}' to get help on the actions you can take from here.", HelpQuickAccessProvider.PREFIX), + helpEntries: [{ description: localize('helpQuickAccess', "Show all Quick Access Providers"), needsEditor: false }] +}); + +quickAccessRegistry.registerQuickAccessProvider({ + ctor: ViewQuickAccessProvider, + prefix: ViewQuickAccessProvider.PREFIX, + contextKey: 'inViewsPicker', + placeholder: localize('viewQuickAccessPlaceholder', "Type the name of a view, output channel or terminal to open."), + helpEntries: [{ description: localize('viewQuickAccess', "Open View"), needsEditor: false }] +}); + +quickAccessRegistry.registerQuickAccessProvider({ + ctor: CommandsQuickAccessProvider, + prefix: CommandsQuickAccessProvider.PREFIX, + contextKey: 'inCommandsPicker', + placeholder: localize('commandsQuickAccessPlaceholder', "Type the name of a command to run."), + helpEntries: [{ description: localize('commandsQuickAccess', "Show and Run Commands"), needsEditor: false }] +}); + +//#endregion + + +//#region Menu contributions + +MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '1_open', + command: { + id: ShowAllCommandsAction.ID, + title: localize({ key: 'miCommandPalette', comment: ['&& denotes a mnemonic'] }, "&&Command Palette...") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '1_open', + command: { + id: OpenViewPickerAction.ID, + title: localize({ key: 'miOpenView', comment: ['&& denotes a mnemonic'] }, "&&Open View...") + }, + order: 2 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { + group: '4_symbol_nav', + command: { + id: 'workbench.action.gotoSymbol', + title: localize({ key: 'miGotoSymbolInEditor', comment: ['&& denotes a mnemonic'] }, "Go to &&Symbol in Editor...") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { + group: '5_infile_nav', + command: { + id: 'workbench.action.gotoLine', + title: localize({ key: 'miGotoLine', comment: ['&& denotes a mnemonic'] }, "Go to &&Line/Column...") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { + group: '1_command', + command: { + id: ShowAllCommandsAction.ID, + title: localize('commandPalette', "Command Palette...") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.EditorContext, { + group: 'z_commands', + command: { + id: ShowAllCommandsAction.ID, + title: localize('commandPalette', "Command Palette..."), + precondition: EditorContextKeys.editorSimpleInput.toNegated() + }, + order: 1 +}); + +//#endregion + + +//#region Workbench actions and commands + +const registry = Registry.as(ActionExtensions.WorkbenchActions); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ClearCommandHistoryAction, ClearCommandHistoryAction.ID, ClearCommandHistoryAction.LABEL), 'Clear Command History'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowAllCommandsAction, ShowAllCommandsAction.ID, ShowAllCommandsAction.LABEL, { + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_P, + secondary: [KeyCode.F1] +}), 'Show All Commands'); + +const inViewsPickerContextKey = 'inViewsPicker'; +const inViewsPickerContext = ContextKeyExpr.and(inQuickPickContext, ContextKeyExpr.has(inViewsPickerContextKey)); + +const viewPickerKeybinding = { primary: KeyMod.CtrlCmd | KeyCode.KEY_Q, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_Q }, linux: { primary: 0 } }; + +const viewCategory = localize('view', "View"); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenViewPickerAction, OpenViewPickerAction.ID, OpenViewPickerAction.LABEL), 'View: Open View', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickAccessViewPickerAction, QuickAccessViewPickerAction.ID, QuickAccessViewPickerAction.LABEL, viewPickerKeybinding), 'View: Quick Open View', viewCategory); + +const quickAccessNavigateNextInViewPickerId = 'workbench.action.quickOpenNavigateNextInViewPicker'; +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: quickAccessNavigateNextInViewPickerId, + weight: KeybindingWeight.WorkbenchContrib + 50, + handler: getQuickNavigateHandler(quickAccessNavigateNextInViewPickerId, true), + when: inViewsPickerContext, + primary: viewPickerKeybinding.primary, + linux: viewPickerKeybinding.linux, + mac: viewPickerKeybinding.mac +}); + +const quickAccessNavigatePreviousInViewPickerId = 'workbench.action.quickOpenNavigatePreviousInViewPicker'; +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: quickAccessNavigatePreviousInViewPickerId, + weight: KeybindingWeight.WorkbenchContrib + 50, + handler: getQuickNavigateHandler(quickAccessNavigatePreviousInViewPickerId, false), + when: inViewsPickerContext, + primary: viewPickerKeybinding.primary | KeyMod.Shift, + linux: viewPickerKeybinding.linux, + mac: { + primary: viewPickerKeybinding.mac.primary | KeyMod.Shift + } +}); + +//#endregion diff --git a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts new file mode 100644 index 00000000000..b43c24b7232 --- /dev/null +++ b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts @@ -0,0 +1,228 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IQuickPickSeparator, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IPickerQuickAccessItem, PickerQuickAccessProvider } from 'vs/platform/quickinput/browser/pickerQuickAccess'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { IViewDescriptorService, IViewsService, ViewContainer, Extensions as ViewExtensions, IViewContainersRegistry } from 'vs/workbench/common/views'; +import { IOutputService } from 'vs/workbench/contrib/output/common/output'; +import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { IPanelService, IPanelIdentifier } from 'vs/workbench/services/panel/common/panelService'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; +import { matchesFuzzy } from 'vs/base/common/filters'; +import { fuzzyContains } from 'vs/base/common/strings'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { Action } from 'vs/base/common/actions'; + +interface IViewQuickPickItem extends IPickerQuickAccessItem { + containerLabel: string; +} + +export class ViewQuickAccessProvider extends PickerQuickAccessProvider { + + static PREFIX = 'view '; + + constructor( + @IViewletService private readonly viewletService: IViewletService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, + @IViewsService private readonly viewsService: IViewsService, + @IOutputService private readonly outputService: IOutputService, + @ITerminalService private readonly terminalService: ITerminalService, + @IPanelService private readonly panelService: IPanelService, + @IContextKeyService private readonly contextKeyService: IContextKeyService + ) { + super(ViewQuickAccessProvider.PREFIX, { + noResultsPick: { + label: localize('noViewResults', "No matching views"), + containerLabel: '' + } + }); + } + + protected getPicks(filter: string): Array { + const filteredViewEntries = this.doGetViewPickItems().filter(entry => { + if (!filter) { + return true; + } + + // Match fuzzy on label + entry.highlights = { label: withNullAsUndefined(matchesFuzzy(filter, entry.label, true)) }; + + // Return if we have a match on label or container + return entry.highlights.label || fuzzyContains(entry.containerLabel, filter); + }); + + // Map entries to container labels + const mapEntryToContainer = new Map(); + for (const entry of filteredViewEntries) { + if (!mapEntryToContainer.has(entry.label)) { + mapEntryToContainer.set(entry.label, entry.containerLabel); + } + } + + // Add separators for containers + const filteredViewEntriesWithSeparators: Array = []; + let lastContainer: string | undefined = undefined; + for (const entry of filteredViewEntries) { + if (lastContainer !== entry.containerLabel) { + lastContainer = entry.containerLabel; + + // When the entry container has a parent container, set container + // label as Parent / Child. For example, `Views / Explorer`. + let separatorLabel: string; + if (mapEntryToContainer.has(lastContainer)) { + separatorLabel = `${mapEntryToContainer.get(lastContainer)} / ${lastContainer}`; + } else { + separatorLabel = lastContainer; + } + + filteredViewEntriesWithSeparators.push({ type: 'separator', label: separatorLabel }); + + } + + filteredViewEntriesWithSeparators.push(entry); + } + + return filteredViewEntriesWithSeparators; + } + + private doGetViewPickItems(): Array { + const viewEntries: Array = []; + + const getViewEntriesForViewlet = (viewlet: ViewletDescriptor, viewContainer: ViewContainer): IViewQuickPickItem[] => { + const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); + const result: IViewQuickPickItem[] = []; + for (const view of viewDescriptors.allViewDescriptors) { + if (this.contextKeyService.contextMatchesRules(view.when)) { + result.push({ + label: view.name, + containerLabel: viewlet.name, + accept: () => this.viewsService.openView(view.id, true) + }); + } + } + + return result; + }; + + // Viewlets + const viewlets = this.viewletService.getViewlets(); + for (const viewlet of viewlets) { + if (this.includeViewContainer(viewlet)) { + viewEntries.push({ + label: viewlet.name, + containerLabel: localize('views', "Side Bar"), + accept: () => this.viewletService.openViewlet(viewlet.id, true) + }); + } + } + + // Panels + const panels = this.panelService.getPanels(); + for (const panel of panels) { + if (this.includeViewContainer(panel)) { + viewEntries.push({ + label: panel.name, + containerLabel: localize('panels', "Panel"), + accept: () => this.panelService.openPanel(panel.id, true) + }); + } + } + + // Viewlet Views + for (const viewlet of viewlets) { + const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).get(viewlet.id); + if (viewContainer) { + viewEntries.push(...getViewEntriesForViewlet(viewlet, viewContainer)); + } + } + + // Terminals + this.terminalService.terminalTabs.forEach((tab, tabIndex) => { + tab.terminalInstances.forEach((terminal, terminalIndex) => { + const label = localize('terminalTitle', "{0}: {1}", `${tabIndex + 1}.${terminalIndex + 1}`, terminal.title); + viewEntries.push({ + label, + containerLabel: localize('terminals', "Terminal"), + accept: async () => { + await this.terminalService.showPanel(true); + + this.terminalService.setActiveInstance(terminal); + } + }); + }); + }); + + // Output Channels + const channels = this.outputService.getChannelDescriptors(); + for (const channel of channels) { + const label = channel.log ? localize('logChannel', "Log ({0})", channel.label) : channel.label; + viewEntries.push({ + label, + containerLabel: localize('channels', "Output"), + accept: () => this.outputService.showChannel(channel.id) + }); + } + + return viewEntries; + } + + private includeViewContainer(container: ViewletDescriptor | IPanelIdentifier): boolean { + const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).get(container.id); + if (viewContainer?.hideIfEmpty) { + return this.viewDescriptorService.getViewDescriptors(viewContainer).activeViewDescriptors.length > 0; + } + + return true; + } +} + + +//#region Actions + +export class OpenViewPickerAction extends Action { + + static readonly ID = 'workbench.action.openView'; + static readonly LABEL = localize('openView', "Open View"); + + constructor( + id: string, + label: string, + @IQuickInputService private readonly quickInputService: IQuickInputService + ) { + super(id, label); + } + + async run(): Promise { + this.quickInputService.quickAccess.show(ViewQuickAccessProvider.PREFIX); + } +} + +export class QuickAccessViewPickerAction extends Action { + + static readonly ID = 'workbench.action.quickOpenView'; + static readonly LABEL = localize('quickOpenView', "Quick Open View"); + + constructor( + id: string, + label: string, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IKeybindingService private readonly keybindingService: IKeybindingService + ) { + super(id, label); + } + + async run(): Promise { + const keys = this.keybindingService.lookupKeybindings(this.id); + + this.quickInputService.quickAccess.show(ViewQuickAccessProvider.PREFIX, { quickNavigateConfiguration: { keybindings: keys } }); + } +} + +//#endregion diff --git a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts deleted file mode 100644 index 78786ce99dd..00000000000 --- a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts +++ /dev/null @@ -1,634 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { localize } from 'vs/nls'; -import { distinct } from 'vs/base/common/arrays'; -import { withNullAsUndefined, isFunction } from 'vs/base/common/types'; -import { Language } from 'vs/base/common/platform'; -import { Action, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; -import { Mode, IEntryRunContext, IAutoFocus, IModel, IQuickNavigateConfiguration } from 'vs/base/parts/quickopen/common/quickOpen'; -import { QuickOpenEntryGroup, IHighlight, QuickOpenModel, QuickOpenEntry } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { IMenuService, MenuId, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { QuickOpenHandler, IWorkbenchQuickOpenConfiguration } from 'vs/workbench/browser/quickopen'; -import { IEditorAction } from 'vs/editor/common/editorCommon'; -import { matchesWords, matchesPrefix, matchesContiguousSubString, or } from 'vs/base/common/filters'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; -import { registerEditorAction, EditorAction } from 'vs/editor/browser/editorExtensions'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { LRUCache } from 'vs/base/common/map'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { isPromiseCanceledError } from 'vs/base/common/errors'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { Disposable, DisposableStore, IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; -import { timeout } from 'vs/base/common/async'; -import { isFirefox } from 'vs/base/browser/browser'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; - -export const ALL_COMMANDS_PREFIX = '>'; - -interface ISerializedCommandHistory { - usesLRU?: boolean; - entries: { key: string; value: number }[]; -} - -class CommandsHistory extends Disposable { - - static readonly DEFAULT_COMMANDS_HISTORY_LENGTH = 50; - - private static readonly PREF_KEY_CACHE = 'commandPalette.mru.cache'; - private static readonly PREF_KEY_COUNTER = 'commandPalette.mru.counter'; - - private static cache: LRUCache | undefined; - private static counter = 1; - - private configuredCommandsHistoryLength = 0; - - constructor( - @IStorageService private readonly storageService: IStorageService, - @IConfigurationService private readonly configurationService: IConfigurationService - ) { - super(); - - this.updateConfiguration(); - this.load(); - - this.registerListeners(); - } - - private registerListeners(): void { - this._register(this.configurationService.onDidChangeConfiguration(e => this.updateConfiguration())); - } - - private updateConfiguration(): void { - this.configuredCommandsHistoryLength = CommandsHistory.getConfiguredCommandHistoryLength(this.configurationService); - - if (CommandsHistory.cache && CommandsHistory.cache.limit !== this.configuredCommandsHistoryLength) { - CommandsHistory.cache.limit = this.configuredCommandsHistoryLength; - - CommandsHistory.saveState(this.storageService); - } - } - - private load(): void { - const raw = this.storageService.get(CommandsHistory.PREF_KEY_CACHE, StorageScope.GLOBAL); - let serializedCache: ISerializedCommandHistory | undefined; - if (raw) { - try { - serializedCache = JSON.parse(raw); - } catch (error) { - // invalid data - } - } - - const cache = CommandsHistory.cache = new LRUCache(this.configuredCommandsHistoryLength, 1); - if (serializedCache) { - let entries: { key: string; value: number }[]; - if (serializedCache.usesLRU) { - entries = serializedCache.entries; - } else { - entries = serializedCache.entries.sort((a, b) => a.value - b.value); - } - entries.forEach(entry => cache.set(entry.key, entry.value)); - } - - CommandsHistory.counter = this.storageService.getNumber(CommandsHistory.PREF_KEY_COUNTER, StorageScope.GLOBAL, CommandsHistory.counter); - } - - push(commandId: string): void { - if (!CommandsHistory.cache) { - return; - } - - CommandsHistory.cache.set(commandId, CommandsHistory.counter++); // set counter to command - - CommandsHistory.saveState(this.storageService); - } - - peek(commandId: string): number | undefined { - return CommandsHistory.cache?.peek(commandId); - } - - static saveState(storageService: IStorageService): void { - if (!CommandsHistory.cache) { - return; - } - - const serializedCache: ISerializedCommandHistory = { usesLRU: true, entries: [] }; - CommandsHistory.cache.forEach((value, key) => serializedCache.entries.push({ key, value })); - - storageService.store(CommandsHistory.PREF_KEY_CACHE, JSON.stringify(serializedCache), StorageScope.GLOBAL); - storageService.store(CommandsHistory.PREF_KEY_COUNTER, CommandsHistory.counter, StorageScope.GLOBAL); - } - - static getConfiguredCommandHistoryLength(configurationService: IConfigurationService): number { - const config = configurationService.getValue(); - - const configuredCommandHistoryLength = config.workbench?.commandPalette?.history; - if (typeof configuredCommandHistoryLength === 'number') { - return configuredCommandHistoryLength; - } - - return CommandsHistory.DEFAULT_COMMANDS_HISTORY_LENGTH; - } - - static clearHistory(configurationService: IConfigurationService, storageService: IStorageService): void { - const commandHistoryLength = CommandsHistory.getConfiguredCommandHistoryLength(configurationService); - CommandsHistory.cache = new LRUCache(commandHistoryLength); - CommandsHistory.counter = 1; - - CommandsHistory.saveState(storageService); - } -} - -let lastCommandPaletteInput: string | undefined = undefined; - -export class ShowAllCommandsAction extends Action { - - static readonly ID = 'workbench.action.showCommands'; - static readonly LABEL = localize('showTriggerActions', "Show All Commands"); - - constructor( - id: string, - label: string, - @IQuickOpenService private readonly quickOpenService: IQuickOpenService, - @IConfigurationService private readonly configurationService: IConfigurationService - ) { - super(id, label); - } - - run(): Promise { - const config = this.configurationService.getValue(); - const restoreInput = config.workbench?.commandPalette?.preserveInput === true; - - // Show with last command palette input if any and configured - let value = ALL_COMMANDS_PREFIX; - if (restoreInput && lastCommandPaletteInput) { - value = `${value}${lastCommandPaletteInput}`; - } - - this.quickOpenService.show(value, { inputSelection: lastCommandPaletteInput ? { start: 1 /* after prefix */, end: value.length } : undefined }); - - return Promise.resolve(undefined); - } -} - -export class ClearCommandHistoryAction extends Action { - - static readonly ID = 'workbench.action.clearCommandHistory'; - static readonly LABEL = localize('clearCommandHistory', "Clear Command History"); - - constructor( - id: string, - label: string, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IStorageService private readonly storageService: IStorageService - ) { - super(id, label); - } - - run(): Promise { - const commandHistoryLength = CommandsHistory.getConfiguredCommandHistoryLength(this.configurationService); - if (commandHistoryLength > 0) { - CommandsHistory.clearHistory(this.configurationService, this.storageService); - } - - return Promise.resolve(undefined); - } -} - -class CommandPaletteEditorAction extends EditorAction { - - constructor() { - super({ - id: ShowAllCommandsAction.ID, - label: localize('showCommands.label', "Command Palette..."), - alias: 'Command Palette', - precondition: EditorContextKeys.editorSimpleInput.toNegated(), - contextMenuOpts: { - group: 'z_commands', - order: 1 - } - }); - } - - run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { - const quickOpenService = accessor.get(IQuickOpenService); - - // Show with prefix - quickOpenService.show(ALL_COMMANDS_PREFIX); - - return Promise.resolve(undefined); - } -} - -abstract class BaseCommandEntry extends QuickOpenEntryGroup { - private description: string | undefined; - private alias: string | undefined; - private labelLowercase: string; - private readonly keybindingAriaLabel?: string; - - constructor( - private commandId: string, - private keybinding: ResolvedKeybinding | undefined, - private label: string, - alias: string | undefined, - highlights: { label: IHighlight[] | null, alias: IHighlight[] | null }, - private onBeforeRun: (commandId: string) => void, - @INotificationService private readonly notificationService: INotificationService, - @ITelemetryService protected telemetryService: ITelemetryService - ) { - super(); - - this.labelLowercase = this.label.toLowerCase(); - this.keybindingAriaLabel = keybinding ? keybinding.getAriaLabel() || undefined : undefined; - - if (this.label !== alias) { - this.alias = alias; - } else { - highlights.alias = null; - } - - this.setHighlights(withNullAsUndefined(highlights.label), undefined, withNullAsUndefined(highlights.alias)); - } - - getCommandId(): string { - return this.commandId; - } - - getLabel(): string { - return this.label; - } - - getSortLabel(): string { - return this.labelLowercase; - } - - getDescription(): string | undefined { - return this.description; - } - - setDescription(description: string): void { - this.description = description; - } - - getKeybinding(): ResolvedKeybinding | undefined { - return this.keybinding; - } - - getDetail(): string | undefined { - return this.alias; - } - - getAriaLabel(): string { - if (this.keybindingAriaLabel) { - return localize('entryAriaLabelWithKey', "{0}, {1}, commands", this.getLabel(), this.keybindingAriaLabel); - } - - return localize('entryAriaLabel', "{0}, commands", this.getLabel()); - } - - run(mode: Mode, context: IEntryRunContext): boolean { - if (mode === Mode.OPEN) { - this.runAction(this.getAction()); - - return true; - } - - return false; - } - - protected abstract getAction(): Action | IEditorAction; - - protected runAction(action: Action | IEditorAction): void { - - // Indicate onBeforeRun - this.onBeforeRun(this.commandId); - - const commandRunner = (async () => { - if (action && (!(action instanceof Action) || action.enabled)) { - try { - this.telemetryService.publicLog2('workbenchActionExecuted', { id: action.id, from: 'quick open' }); - - const promise = action.run(); - if (promise) { - try { - await promise; - } finally { - if (action instanceof Action) { - action.dispose(); - } - } - } - } catch (error) { - this.onError(error); - } - } else { - this.notificationService.info(localize('actionNotEnabled', "Command '{0}' is not enabled in the current context.", this.getLabel())); - } - }); - - // Use a timeout to give the quick open widget a chance to close itself first - // Firefox: since the browser is quite picky for certain commands, we do not - // use a timeout (https://github.com/microsoft/vscode/issues/83288) - if (!isFirefox) { - setTimeout(() => commandRunner(), 50); - } else { - commandRunner(); - } - } - - private onError(error?: Error): void { - if (isPromiseCanceledError(error)) { - return; - } - - this.notificationService.error(error || localize('canNotRun', "Command '{0}' resulted in an error.", this.label)); - } -} - -class EditorActionCommandEntry extends BaseCommandEntry { - - constructor( - commandId: string, - keybinding: ResolvedKeybinding | undefined, - label: string, - meta: string | undefined, - highlights: { label: IHighlight[] | null, alias: IHighlight[] | null }, - private action: IEditorAction, - onBeforeRun: (commandId: string) => void, - @INotificationService notificationService: INotificationService, - @ITelemetryService telemetryService: ITelemetryService - ) { - super(commandId, keybinding, label, meta, highlights, onBeforeRun, notificationService, telemetryService); - } - - protected getAction(): Action | IEditorAction { - return this.action; - } -} - -class ActionCommandEntry extends BaseCommandEntry { - - constructor( - commandId: string, - keybinding: ResolvedKeybinding | undefined, - label: string, - alias: string | undefined, - highlights: { label: IHighlight[] | null, alias: IHighlight[] | null }, - private action: Action, - onBeforeRun: (commandId: string) => void, - @INotificationService notificationService: INotificationService, - @ITelemetryService telemetryService: ITelemetryService - ) { - super(commandId, keybinding, label, alias, highlights, onBeforeRun, notificationService, telemetryService); - } - - protected getAction(): Action | IEditorAction { - return this.action; - } -} - -const wordFilter = or(matchesPrefix, matchesWords, matchesContiguousSubString); - -export class CommandsHandler extends QuickOpenHandler implements IDisposable { - - static readonly ID = 'workbench.picker.commands'; - - private commandHistoryEnabled: boolean | undefined; - private readonly commandsHistory: CommandsHistory; - - private readonly disposables = new DisposableStore(); - private readonly disposeOnClose = new DisposableStore(); - - private waitedForExtensionsRegistered: boolean | undefined; - - constructor( - @IEditorService private readonly editorService: IEditorService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IKeybindingService private readonly keybindingService: IKeybindingService, - @IMenuService private readonly menuService: IMenuService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IExtensionService private readonly extensionService: IExtensionService - ) { - super(); - - this.commandsHistory = this.disposables.add(this.instantiationService.createInstance(CommandsHistory)); - - this.extensionService.whenInstalledExtensionsRegistered().then(() => this.waitedForExtensionsRegistered = true); - - this.configurationService.onDidChangeConfiguration(e => this.updateConfiguration()); - this.updateConfiguration(); - } - - private updateConfiguration(): void { - this.commandHistoryEnabled = CommandsHistory.getConfiguredCommandHistoryLength(this.configurationService) > 0; - } - - async getResults(searchValue: string, token: CancellationToken): Promise { - if (this.waitedForExtensionsRegistered) { - return this.doGetResults(searchValue, token); - } - - // If extensions are not yet registered, we wait for a little moment to give them - // a chance to register so that the complete set of commands shows up as result - // We do not want to delay functionality beyond that time though to keep the commands - // functional. - await Promise.race([timeout(800).then(), this.extensionService.whenInstalledExtensionsRegistered()]); - this.waitedForExtensionsRegistered = true; - - return this.doGetResults(searchValue, token); - } - - private doGetResults(searchValue: string, token: CancellationToken): Promise { - if (token.isCancellationRequested) { - return Promise.resolve(new QuickOpenModel([])); - } - - searchValue = searchValue.trim(); - - // Remember as last command palette input - lastCommandPaletteInput = searchValue; - - // Editor Actions - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - let editorActions: IEditorAction[] = []; - if (activeTextEditorWidget && isFunction(activeTextEditorWidget.getSupportedActions)) { - editorActions = activeTextEditorWidget.getSupportedActions(); - } - - const editorEntries = this.editorActionsToEntries(editorActions, searchValue); - - // Other Actions - const menu = this.editorService.invokeWithinEditorContext(accessor => this.menuService.createMenu(MenuId.CommandPalette, accessor.get(IContextKeyService))); - const menuActions = menu.getActions() - .reduce((r, [, actions]) => [...r, ...actions], >[]) - .filter(action => action instanceof MenuItemAction) as MenuItemAction[]; - const commandEntries = this.menuItemActionsToEntries(menuActions, searchValue); - menu.dispose(); - this.disposeOnClose.add(toDisposable(() => dispose(menuActions))); - - // Concat - let entries = [...editorEntries, ...commandEntries]; - - // Remove duplicates - entries = distinct(entries, entry => `${entry.getLabel()}${entry.getGroupLabel()}${entry.getCommandId()}`); - - // Handle label clashes - const commandLabels = new Set(); - entries.forEach(entry => { - const commandLabel = `${entry.getLabel()}${entry.getGroupLabel()}`; - if (commandLabels.has(commandLabel)) { - entry.setDescription(entry.getCommandId()); - } else { - commandLabels.add(commandLabel); - } - }); - - // Sort by MRU order and fallback to name otherwie - entries = entries.sort((elementA, elementB) => { - const counterA = this.commandsHistory.peek(elementA.getCommandId()); - const counterB = this.commandsHistory.peek(elementB.getCommandId()); - - if (counterA && counterB) { - return counterA > counterB ? -1 : 1; // use more recently used command before older - } - - if (counterA) { - return -1; // first command was used, so it wins over the non used one - } - - if (counterB) { - return 1; // other command was used so it wins over the command - } - - // both commands were never used, so we sort by name - return elementA.getSortLabel().localeCompare(elementB.getSortLabel()); - }); - - // Introduce group marker border between recently used and others - // only if we have recently used commands in the result set - const firstEntry = entries[0]; - if (firstEntry && this.commandsHistory.peek(firstEntry.getCommandId())) { - firstEntry.setGroupLabel(localize('recentlyUsed', "recently used")); - for (let i = 1; i < entries.length; i++) { - const entry = entries[i]; - if (!this.commandsHistory.peek(entry.getCommandId())) { - entry.setShowBorder(true); - entry.setGroupLabel(localize('morecCommands', "other commands")); - break; - } - } - } - - return Promise.resolve(new QuickOpenModel(entries)); - } - - private editorActionsToEntries(actions: IEditorAction[], searchValue: string): EditorActionCommandEntry[] { - const entries: EditorActionCommandEntry[] = []; - - for (const action of actions) { - if (action.id === ShowAllCommandsAction.ID) { - continue; // avoid duplicates - } - - const label = action.label; - if (label) { - - // Alias for non default languages - const alias = !Language.isDefaultVariant() ? action.alias : undefined; - const labelHighlights = wordFilter(searchValue, label); - const aliasHighlights = alias ? wordFilter(searchValue, alias) : null; - - if (labelHighlights || aliasHighlights) { - entries.push(this.instantiationService.createInstance(EditorActionCommandEntry, action.id, this.keybindingService.lookupKeybinding(action.id), label, alias, { label: labelHighlights, alias: aliasHighlights }, action, (id: string) => this.onBeforeRunCommand(id))); - } - } - } - - return entries; - } - - private onBeforeRunCommand(commandId: string): void { - - // Remember in commands history - this.commandsHistory.push(commandId); - } - - private menuItemActionsToEntries(actions: MenuItemAction[], searchValue: string): ActionCommandEntry[] { - const entries: ActionCommandEntry[] = []; - - for (let action of actions) { - const title = typeof action.item.title === 'string' ? action.item.title : action.item.title.value; - let category, label = title; - if (action.item.category) { - category = typeof action.item.category === 'string' ? action.item.category : action.item.category.value; - label = localize('cat.title', "{0}: {1}", category, title); - } - - if (label) { - const labelHighlights = wordFilter(searchValue, label); - - // Add an 'alias' in original language when running in different locale - const aliasTitle = (!Language.isDefaultVariant() && typeof action.item.title !== 'string') ? action.item.title.original : undefined; - const aliasCategory = (!Language.isDefaultVariant() && category && action.item.category && typeof action.item.category !== 'string') ? action.item.category.original : undefined; - let alias; - if (aliasTitle && category) { - alias = aliasCategory ? `${aliasCategory}: ${aliasTitle}` : `${category}: ${aliasTitle}`; - } else if (aliasTitle) { - alias = aliasTitle; - } - const aliasHighlights = alias ? wordFilter(searchValue, alias) : null; - - if (labelHighlights || aliasHighlights) { - entries.push(this.instantiationService.createInstance(ActionCommandEntry, action.id, this.keybindingService.lookupKeybinding(action.item.id), label, alias, { label: labelHighlights, alias: aliasHighlights }, action, (id: string) => this.onBeforeRunCommand(id))); - } - } - } - - return entries; - } - - getAutoFocus(searchValue: string, context: { model: IModel, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus { - let autoFocusPrefixMatch: string | undefined = searchValue.trim(); - - if (autoFocusPrefixMatch && this.commandHistoryEnabled) { - const firstEntry = context.model && context.model.entries[0]; - if (firstEntry instanceof BaseCommandEntry && this.commandsHistory.peek(firstEntry.getCommandId())) { - autoFocusPrefixMatch = undefined; // keep focus on MRU element if we have history elements - } - } - - return { - autoFocusFirstEntry: true, - autoFocusPrefixMatch - }; - } - - getEmptyLabel(searchString: string): string { - return localize('noCommandsMatching', "No commands matching"); - } - - onClose(canceled: boolean): void { - super.onClose(canceled); - - this.disposeOnClose.clear(); - } - - dispose() { - this.disposables.dispose(); - this.disposeOnClose.dispose(); - } -} - -registerEditorAction(CommandPaletteEditorAction); diff --git a/src/vs/workbench/contrib/quickopen/browser/gotoLineHandler.ts b/src/vs/workbench/contrib/quickopen/browser/gotoLineHandler.ts deleted file mode 100644 index 59ef23bc2e1..00000000000 --- a/src/vs/workbench/contrib/quickopen/browser/gotoLineHandler.ts +++ /dev/null @@ -1,345 +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 nls from 'vs/nls'; -import * as types from 'vs/base/common/types'; -import { IEntryRunContext, Mode, IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen'; -import { QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { QuickOpenHandler, EditorQuickOpenEntry, QuickOpenAction } from 'vs/workbench/browser/quickopen'; -import { IEditor, IEditorViewState, IDiffEditorModel, ScrollType } from 'vs/editor/common/editorCommon'; -import { OverviewRulerLane, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; -import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; -import { IEditorInput, GroupIdentifier } from 'vs/workbench/common/editor'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; -import { IRange } from 'vs/editor/common/core/range'; -import { overviewRulerRangeHighlight } from 'vs/editor/common/view/editorColorRegistry'; -import { themeColorFromId } from 'vs/platform/theme/common/themeService'; -import { IEditorOptions, RenderLineNumbersType, EditorOption } from 'vs/editor/common/config/editorOptions'; -import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; -import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { Event } from 'vs/base/common/event'; -import { CancellationToken } from 'vs/base/common/cancellation'; - -export const GOTO_LINE_PREFIX = ':'; - -export class GotoLineAction extends QuickOpenAction { - - static readonly ID = 'workbench.action.gotoLine'; - static readonly LABEL = nls.localize('gotoLine', "Go to Line..."); - - constructor(actionId: string, actionLabel: string, - @IQuickOpenService private readonly _quickOpenService: IQuickOpenService, - @IEditorService private readonly editorService: IEditorService - ) { - super(actionId, actionLabel, GOTO_LINE_PREFIX, _quickOpenService); - } - - run(): Promise { - - let activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (!activeTextEditorWidget) { - return Promise.resolve(); - } - - if (isDiffEditor(activeTextEditorWidget)) { - activeTextEditorWidget = activeTextEditorWidget.getModifiedEditor(); - } - let restoreOptions: IEditorOptions | null = null; - - if (isCodeEditor(activeTextEditorWidget)) { - const options = activeTextEditorWidget.getOptions(); - const lineNumbers = options.get(EditorOption.lineNumbers); - if (lineNumbers.renderType === RenderLineNumbersType.Relative) { - activeTextEditorWidget.updateOptions({ - lineNumbers: 'on' - }); - restoreOptions = { - lineNumbers: 'relative' - }; - } - } - - const result = super.run(); - - if (restoreOptions) { - Event.once(this._quickOpenService.onHide)(() => { - activeTextEditorWidget!.updateOptions(restoreOptions!); - }); - } - - return result; - } -} - -class GotoLineEntry extends EditorQuickOpenEntry { - private line!: number; - private column!: number; - private handler: GotoLineHandler; - - constructor(line: string, editorService: IEditorService, handler: GotoLineHandler) { - super(editorService); - - this.parseInput(line); - this.handler = handler; - } - - private parseInput(line: string) { - const numbers = line.split(/,|:|#/).map(part => parseInt(part, 10)).filter(part => !isNaN(part)); - const endLine = this.getMaxLineNumber() + 1; - - this.column = numbers[1]; - this.line = numbers[0] > 0 ? numbers[0] : endLine + numbers[0]; - } - - getLabel(): string { - - // Inform user about valid range if input is invalid - const maxLineNumber = this.getMaxLineNumber(); - - if (this.editorService.activeTextEditorWidget && this.invalidRange(maxLineNumber)) { - const position = this.editorService.activeTextEditorWidget.getPosition(); - if (position) { - - if (maxLineNumber > 0) { - return nls.localize('gotoLineLabelEmptyWithLimit', "Current Line: {0}, Column: {1}. Type a line number between 1 and {2} to navigate to.", position.lineNumber, position.column, maxLineNumber); - } - - return nls.localize('gotoLineLabelEmpty', "Current Line: {0}, Column: {1}. Type a line number to navigate to.", position.lineNumber, position.column); - } - } - - // Input valid, indicate action - return this.column ? nls.localize('gotoLineColumnLabel', "Go to line {0} and column {1}.", this.line, this.column) : nls.localize('gotoLineLabel', "Go to line {0}.", this.line); - } - - private invalidRange(maxLineNumber: number = this.getMaxLineNumber()): boolean { - return !this.line || !types.isNumber(this.line) || (maxLineNumber > 0 && types.isNumber(this.line) && this.line > maxLineNumber) || this.line < 0; - } - - private getMaxLineNumber(): number { - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (!activeTextEditorWidget) { - return -1; - } - - let model = activeTextEditorWidget.getModel(); - if (model && (model).modified && (model).original) { - model = (model).modified; // Support for diff editor models - } - - return model && types.isFunction((model).getLineCount) ? (model).getLineCount() : -1; - } - - run(mode: Mode, context: IEntryRunContext): boolean { - if (mode === Mode.OPEN) { - return this.runOpen(context); - } - - return this.runPreview(); - } - - getInput(): IEditorInput | undefined { - return this.editorService.activeEditor; - } - - getOptions(pinned?: boolean): ITextEditorOptions { - return { - selection: this.toSelection(), - pinned - }; - } - - runOpen(context: IEntryRunContext): boolean { - - // No-op if range is not valid - if (this.invalidRange()) { - return false; - } - - // Check for sideBySide use - const sideBySide = context.keymods.ctrlCmd; - if (sideBySide) { - this.editorService.openEditor(this.getInput()!, this.getOptions(context.keymods.alt), SIDE_GROUP); - } - - // Apply selection and focus - const range = this.toSelection(); - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (activeTextEditorWidget) { - activeTextEditorWidget.setSelection(range); - activeTextEditorWidget.revealRangeInCenter(range, ScrollType.Smooth); - } - - return true; - } - - runPreview(): boolean { - - // No-op if range is not valid - if (this.invalidRange()) { - this.handler.clearDecorations(); - - return false; - } - - // Select Line Position - const range = this.toSelection(); - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (activeTextEditorWidget) { - activeTextEditorWidget.revealRangeInCenter(range, ScrollType.Smooth); - - // Decorate if possible - if (this.editorService.activeControl && types.isFunction(activeTextEditorWidget.changeDecorations)) { - this.handler.decorateOutline(range, activeTextEditorWidget, this.editorService.activeControl.group); - } - } - - return false; - } - - private toSelection(): IRange { - return { - startLineNumber: this.line, - startColumn: this.column || 1, - endLineNumber: this.line, - endColumn: this.column || 1 - }; - } -} - -interface IEditorLineDecoration { - groupId: GroupIdentifier; - rangeHighlightId: string; - lineDecorationId: string; -} - -export class GotoLineHandler extends QuickOpenHandler { - - static readonly ID = 'workbench.picker.line'; - - private rangeHighlightDecorationId: IEditorLineDecoration | null = null; - private lastKnownEditorViewState: IEditorViewState | null = null; - - constructor(@IEditorService private readonly editorService: IEditorService) { - super(); - } - - getAriaLabel(): string { - if (this.editorService.activeTextEditorWidget) { - const position = this.editorService.activeTextEditorWidget.getPosition(); - if (position) { - return nls.localize('gotoLineLabelEmpty', "Current Line: {0}, Column: {1}. Type a line number to navigate to.", position.lineNumber, position.column); - } - } - - return nls.localize('cannotRunGotoLine', "Open a text file first to go to a line."); - } - - getResults(searchValue: string, token: CancellationToken): Promise { - searchValue = searchValue.trim(); - - // Remember view state to be able to restore on cancel - if (!this.lastKnownEditorViewState) { - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (activeTextEditorWidget) { - this.lastKnownEditorViewState = activeTextEditorWidget.saveViewState(); - } - } - - return Promise.resolve(new QuickOpenModel([new GotoLineEntry(searchValue, this.editorService, this)])); - } - - canRun(): boolean | string { - const canRun = !!this.editorService.activeTextEditorWidget; - - return canRun ? true : nls.localize('cannotRunGotoLine', "Open a text file first to go to a line."); - } - - decorateOutline(range: IRange, editor: IEditor, group: IEditorGroup): void { - editor.changeDecorations(changeAccessor => { - const deleteDecorations: string[] = []; - - if (this.rangeHighlightDecorationId) { - deleteDecorations.push(this.rangeHighlightDecorationId.lineDecorationId); - deleteDecorations.push(this.rangeHighlightDecorationId.rangeHighlightId); - this.rangeHighlightDecorationId = null; - } - - const newDecorations: IModelDeltaDecoration[] = [ - // rangeHighlight at index 0 - { - range: range, - options: { - className: 'rangeHighlight', - isWholeLine: true - } - }, - - // lineDecoration at index 1 - { - range: range, - options: { - overviewRuler: { - color: themeColorFromId(overviewRulerRangeHighlight), - position: OverviewRulerLane.Full - } - } - } - ]; - - const decorations = changeAccessor.deltaDecorations(deleteDecorations, newDecorations); - const rangeHighlightId = decorations[0]; - const lineDecorationId = decorations[1]; - - this.rangeHighlightDecorationId = { - groupId: group.id, - rangeHighlightId: rangeHighlightId, - lineDecorationId: lineDecorationId, - }; - }); - } - - clearDecorations(): void { - const rangeHighlightDecorationId = this.rangeHighlightDecorationId; - if (rangeHighlightDecorationId) { - this.editorService.visibleControls.forEach(editor => { - if (editor.group && editor.group.id === rangeHighlightDecorationId.groupId) { - const editorControl = editor.getControl(); - editorControl.changeDecorations(changeAccessor => { - changeAccessor.deltaDecorations([ - rangeHighlightDecorationId.lineDecorationId, - rangeHighlightDecorationId.rangeHighlightId - ], []); - }); - } - }); - - this.rangeHighlightDecorationId = null; - } - } - - onClose(canceled: boolean): void { - - // Clear Highlight Decorations if present - this.clearDecorations(); - - // Restore selection if canceled - if (canceled && this.lastKnownEditorViewState) { - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (activeTextEditorWidget) { - activeTextEditorWidget.restoreViewState(this.lastKnownEditorViewState); - } - } - - this.lastKnownEditorViewState = null; - } - - getAutoFocus(searchValue: string): IAutoFocus { - return { - autoFocusFirstEntry: searchValue.trim().length > 0 - }; - } -} diff --git a/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts b/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts deleted file mode 100644 index 5dfba2ef0c8..00000000000 --- a/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts +++ /dev/null @@ -1,559 +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 'vs/css!vs/editor/contrib/documentSymbols/media/symbol-icons'; -import * as nls from 'vs/nls'; -import * as types from 'vs/base/common/types'; -import * as strings from 'vs/base/common/strings'; -import { IEntryRunContext, Mode, IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen'; -import { QuickOpenModel, IHighlight } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { QuickOpenHandler, EditorQuickOpenEntryGroup, QuickOpenAction } from 'vs/workbench/browser/quickopen'; -import * as filters from 'vs/base/common/filters'; -import { IEditor, IDiffEditorModel, IEditorViewState, ScrollType } from 'vs/editor/common/editorCommon'; -import { IModelDecorationsChangeAccessor, OverviewRulerLane, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; -import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; -import { getDocumentSymbols } from 'vs/editor/contrib/quickOpen/quickOpen'; -import { DocumentSymbolProviderRegistry, DocumentSymbol, SymbolKinds, SymbolKind, SymbolTag } from 'vs/editor/common/modes'; -import { IRange, Range } from 'vs/editor/common/core/range'; -import { themeColorFromId } from 'vs/platform/theme/common/themeService'; -import { overviewRulerRangeHighlight } from 'vs/editor/common/view/editorColorRegistry'; -import { GroupIdentifier, IEditorInput } from 'vs/workbench/common/editor'; -import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; - -export const GOTO_SYMBOL_PREFIX = '@'; -export const SCOPE_PREFIX = ':'; - -const FALLBACK_NLS_SYMBOL_KIND = nls.localize('property', "properties ({0})"); -const NLS_SYMBOL_KIND_CACHE: { [type: number]: string } = { - [SymbolKind.Method]: nls.localize('method', "methods ({0})"), - [SymbolKind.Function]: nls.localize('function', "functions ({0})"), - [SymbolKind.Constructor]: nls.localize('_constructor', "constructors ({0})"), - [SymbolKind.Variable]: nls.localize('variable', "variables ({0})"), - [SymbolKind.Class]: nls.localize('class', "classes ({0})"), - [SymbolKind.Struct]: nls.localize('struct', "structs ({0})"), - [SymbolKind.Event]: nls.localize('event', "events ({0})"), - [SymbolKind.Operator]: nls.localize('operator', "operators ({0})"), - [SymbolKind.Interface]: nls.localize('interface', "interfaces ({0})"), - [SymbolKind.Namespace]: nls.localize('namespace', "namespaces ({0})"), - [SymbolKind.Package]: nls.localize('package', "packages ({0})"), - [SymbolKind.TypeParameter]: nls.localize('typeParameter', "type parameters ({0})"), - [SymbolKind.Module]: nls.localize('modules', "modules ({0})"), - [SymbolKind.Property]: nls.localize('property', "properties ({0})"), - [SymbolKind.Enum]: nls.localize('enum', "enumerations ({0})"), - [SymbolKind.EnumMember]: nls.localize('enumMember', "enumeration members ({0})"), - [SymbolKind.String]: nls.localize('string', "strings ({0})"), - [SymbolKind.File]: nls.localize('file', "files ({0})"), - [SymbolKind.Array]: nls.localize('array', "arrays ({0})"), - [SymbolKind.Number]: nls.localize('number', "numbers ({0})"), - [SymbolKind.Boolean]: nls.localize('boolean', "booleans ({0})"), - [SymbolKind.Object]: nls.localize('object', "objects ({0})"), - [SymbolKind.Key]: nls.localize('key', "keys ({0})"), - [SymbolKind.Field]: nls.localize('field', "fields ({0})"), - [SymbolKind.Constant]: nls.localize('constant', "constants ({0})") -}; - -export class GotoSymbolAction extends QuickOpenAction { - - static readonly ID = 'workbench.action.gotoSymbol'; - static readonly LABEL = nls.localize('gotoSymbol', "Go to Symbol in File..."); - - constructor(actionId: string, actionLabel: string, @IQuickOpenService quickOpenService: IQuickOpenService) { - super(actionId, actionLabel, GOTO_SYMBOL_PREFIX, quickOpenService); - } -} - -class OutlineModel extends QuickOpenModel { - - applyFilter(searchValue: string): void { - - // Normalize search - const searchValueLow = searchValue.toLowerCase(); - const searchValuePos = searchValue.indexOf(SCOPE_PREFIX) === 0 ? 1 : 0; - - // Check for match and update visibility and group label - (>this.entries).forEach(entry => { - - // Clear all state first - entry.setGroupLabel(undefined); - entry.setShowBorder(false); - entry.setScore(undefined); - entry.setHidden(false); - - // Filter by search - if (searchValue.length > searchValuePos) { - const score = filters.fuzzyScore( - searchValue, searchValueLow, searchValuePos, - entry.getLabel(), entry.getLabel().toLowerCase(), 0, - true - ); - entry.setScore(score); - entry.setHidden(!score); - } - }); - - // select comparator based on the presence of the colon-prefix - (>this.entries).sort(searchValuePos === 0 - ? SymbolEntry.compareByRank - : SymbolEntry.compareByKindAndRank - ); - - // Mark all type groups - const visibleResults = this.getEntries(true); - if (visibleResults.length > 0 && searchValue.indexOf(SCOPE_PREFIX) === 0) { - let currentType: SymbolKind | null = null; - let currentResult: SymbolEntry | null = null; - let typeCounter = 0; - - for (let i = 0; i < visibleResults.length; i++) { - const result = visibleResults[i]; - - // Found new type - if (currentType !== result.getKind()) { - - // Update previous result with count - if (currentResult) { - currentResult.setGroupLabel(typeof currentType === 'number' ? this.renderGroupLabel(currentType, typeCounter) : undefined); - } - - currentType = result.getKind(); - currentResult = result; - typeCounter = 1; - - result.setShowBorder(i > 0); - } - - // Existing type, keep counting - else { - typeCounter++; - } - } - - // Update previous result with count - if (currentResult) { - currentResult.setGroupLabel(typeof currentType === 'number' ? this.renderGroupLabel(currentType, typeCounter) : undefined); - } - } - - // Mark first entry as outline - else if (visibleResults.length > 0) { - visibleResults[0].setGroupLabel(nls.localize('symbols', "symbols ({0})", visibleResults.length)); - } - } - - private renderGroupLabel(type: SymbolKind, count: number): string { - let pattern = NLS_SYMBOL_KIND_CACHE[type]; - if (!pattern) { - pattern = FALLBACK_NLS_SYMBOL_KIND; - } - - return strings.format(pattern, count); - } -} - -class SymbolEntry extends EditorQuickOpenEntryGroup { - - private score?: filters.FuzzyScore; - - constructor( - private readonly index: number, - private readonly name: string, - private readonly kind: SymbolKind, - private readonly description: string, - private readonly icon: string, - private readonly deprecated: boolean, - private readonly range: IRange, - private readonly revealRange: IRange, - private readonly editorService: IEditorService, - private readonly handler: GotoSymbolHandler - ) { - super(); - } - - setScore(score: filters.FuzzyScore | undefined): void { - this.score = score; - } - - getLabel(): string { - return this.name; - } - - getAriaLabel(): string { - return nls.localize('entryAriaLabel', "{0}, symbols", this.getLabel()); - } - - getIcon(): string { - return this.icon; - } - - getLabelOptions(): IIconLabelValueOptions | undefined { - return this.deprecated ? { extraClasses: ['deprecated'] } : undefined; - } - - getHighlights(): [IHighlight[] | undefined, IHighlight[] | undefined, IHighlight[] | undefined] { - return [ - this.deprecated ? [] : filters.createMatches(this.score), - undefined, - undefined - ]; - } - - getDescription(): string { - return this.description; - } - - getKind(): SymbolKind { - return this.kind; - } - - getRange(): IRange { - return this.range; - } - - getInput(): IEditorInput | undefined { - return this.editorService.activeEditor; - } - - getOptions(pinned?: boolean): ITextEditorOptions { - return { - selection: Range.collapseToStart(this.revealRange), - pinned - }; - } - - run(mode: Mode, context: IEntryRunContext): boolean { - if (mode === Mode.OPEN) { - return this.runOpen(context); - } - - return this.runPreview(); - } - - private runOpen(context: IEntryRunContext): boolean { - - // Check for sideBySide use - const sideBySide = context.keymods.ctrlCmd; - if (sideBySide) { - this.editorService.openEditor(this.getInput()!, this.getOptions(context.keymods.alt), SIDE_GROUP); - } - - // Apply selection and focus - else { - const range = Range.collapseToStart(this.revealRange); - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (activeTextEditorWidget) { - activeTextEditorWidget.setSelection(range); - activeTextEditorWidget.revealRangeInCenter(range, ScrollType.Smooth); - } - } - - return true; - } - - private runPreview(): boolean { - - // Select Outline Position - const range = Range.collapseToStart(this.revealRange); - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (activeTextEditorWidget) { - activeTextEditorWidget.revealRangeInCenter(range, ScrollType.Smooth); - - // Decorate if possible - if (this.editorService.activeControl && types.isFunction(activeTextEditorWidget.changeDecorations)) { - this.handler.decorateOutline(this.range, range, activeTextEditorWidget, this.editorService.activeControl.group); - } - } - - return false; - } - - static compareByRank(a: SymbolEntry, b: SymbolEntry): number { - if (!a.score && b.score) { - return 1; - } else if (a.score && !b.score) { - return -1; - } - if (a.score && b.score) { - if (a.score[0] > b.score[0]) { - return -1; - } else if (a.score[0] < b.score[0]) { - return 1; - } - } - if (a.index < b.index) { - return -1; - } else if (a.index > b.index) { - return 1; - } - return 0; - } - - static compareByKindAndRank(a: SymbolEntry, b: SymbolEntry): number { - // Sort by type first if scoped search - const kindA = NLS_SYMBOL_KIND_CACHE[a.getKind()] || FALLBACK_NLS_SYMBOL_KIND; - const kindB = NLS_SYMBOL_KIND_CACHE[b.getKind()] || FALLBACK_NLS_SYMBOL_KIND; - let r = kindA.localeCompare(kindB); - if (r === 0) { - r = SymbolEntry.compareByRank(a, b); - } - return r; - } -} - -interface IEditorLineDecoration { - groupId: GroupIdentifier; - rangeHighlightId: string; - lineDecorationId: string; -} - -export class GotoSymbolHandler extends QuickOpenHandler { - - static readonly ID = 'workbench.picker.filesymbols'; - - private rangeHighlightDecorationId?: IEditorLineDecoration; - private lastKnownEditorViewState: IEditorViewState | null = null; - - private cachedOutlineRequest?: Promise; - private pendingOutlineRequest?: CancellationTokenSource; - - constructor( - @IEditorService private readonly editorService: IEditorService - ) { - super(); - - this.registerListeners(); - } - - private registerListeners(): void { - this.editorService.onDidActiveEditorChange(() => this.onDidActiveEditorChange()); - } - - private onDidActiveEditorChange(): void { - this.clearOutlineRequest(); - - this.lastKnownEditorViewState = null; - this.rangeHighlightDecorationId = undefined; - } - - async getResults(searchValue: string, token: CancellationToken): Promise { - searchValue = searchValue.trim(); - - // Support to cancel pending outline requests - if (!this.pendingOutlineRequest) { - this.pendingOutlineRequest = new CancellationTokenSource(); - } - - // Remember view state to be able to restore on cancel - if (!this.lastKnownEditorViewState) { - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (activeTextEditorWidget) { - this.lastKnownEditorViewState = activeTextEditorWidget.saveViewState(); - } - } - - // Resolve Outline Model - const outline = await this.getOutline(); - if (!outline) { - return outline; - } - - if (token.isCancellationRequested) { - return outline; - } - - // Filter by search - outline.applyFilter(searchValue); - - return outline; - } - - getEmptyLabel(searchString: string): string { - if (searchString.length > 0) { - return nls.localize('noSymbolsMatching', "No symbols matching"); - } - - return nls.localize('noSymbolsFound', "No symbols found"); - } - - getAriaLabel(): string { - return nls.localize('gotoSymbolHandlerAriaLabel', "Type to narrow down symbols of the currently active editor."); - } - - canRun(): boolean | string { - let canRun = false; - - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (activeTextEditorWidget) { - let model = activeTextEditorWidget.getModel(); - if (model && (model).modified && (model).original) { - model = (model).modified; // Support for diff editor models - } - - if (model && types.isFunction((model).getLanguageIdentifier)) { - canRun = DocumentSymbolProviderRegistry.has(model); - } - } - - return canRun ? true : activeTextEditorWidget !== null ? nls.localize('cannotRunGotoSymbolInFile', "No symbol information for the file") : nls.localize('cannotRunGotoSymbol', "Open a text file first to go to a symbol"); - } - - getAutoFocus(searchValue: string): IAutoFocus { - searchValue = searchValue.trim(); - - // Remove any type pattern (:) from search value as needed - if (searchValue.indexOf(SCOPE_PREFIX) === 0) { - searchValue = searchValue.substr(SCOPE_PREFIX.length); - } - - return { - autoFocusPrefixMatch: searchValue, - autoFocusFirstEntry: !!searchValue - }; - } - - private toQuickOpenEntries(symbols: DocumentSymbol[]): SymbolEntry[] { - const results: SymbolEntry[] = []; - - for (let i = 0; i < symbols.length; i++) { - const element = symbols[i]; - const label = strings.trim(element.name); - - // Show parent scope as description - const description = element.containerName || ''; - const icon = SymbolKinds.toCssClassName(element.kind); - - // Add - results.push(new SymbolEntry(i, - label, element.kind, description, `symbol-icon ${icon}`, element.tags && element.tags.indexOf(SymbolTag.Deprecated) >= 0, - element.range, element.selectionRange, this.editorService, this - )); - } - - return results; - } - - private getOutline(): Promise { - if (!this.cachedOutlineRequest) { - this.cachedOutlineRequest = this.doGetActiveOutline(); - } - - return this.cachedOutlineRequest; - } - - private async doGetActiveOutline(): Promise { - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (activeTextEditorWidget) { - let model = activeTextEditorWidget.getModel(); - if (model && (model).modified && (model).original) { - model = (model).modified; // Support for diff editor models - } - - if (model && types.isFunction((model).getLanguageIdentifier)) { - const entries = await getDocumentSymbols(model, true, this.pendingOutlineRequest!.token); - - return new OutlineModel(this.toQuickOpenEntries(entries)); - } - } - - return null; - } - - decorateOutline(fullRange: IRange, startRange: IRange, editor: IEditor, group: IEditorGroup): void { - editor.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => { - const deleteDecorations: string[] = []; - - if (this.rangeHighlightDecorationId) { - deleteDecorations.push(this.rangeHighlightDecorationId.lineDecorationId); - deleteDecorations.push(this.rangeHighlightDecorationId.rangeHighlightId); - this.rangeHighlightDecorationId = undefined; - } - - const newDecorations: IModelDeltaDecoration[] = [ - - // rangeHighlight at index 0 - { - range: fullRange, - options: { - className: 'rangeHighlight', - isWholeLine: true - } - }, - - // lineDecoration at index 1 - { - range: startRange, - options: { - overviewRuler: { - color: themeColorFromId(overviewRulerRangeHighlight), - position: OverviewRulerLane.Full - } - } - } - - ]; - - const decorations = changeAccessor.deltaDecorations(deleteDecorations, newDecorations); - const rangeHighlightId = decorations[0]; - const lineDecorationId = decorations[1]; - - this.rangeHighlightDecorationId = { - groupId: group.id, - rangeHighlightId: rangeHighlightId, - lineDecorationId: lineDecorationId, - }; - }); - } - - private clearDecorations(): void { - const rangeHighlightDecorationId = this.rangeHighlightDecorationId; - if (rangeHighlightDecorationId) { - this.editorService.visibleControls.forEach(editor => { - if (editor.group && editor.group.id === rangeHighlightDecorationId.groupId) { - const editorControl = editor.getControl(); - editorControl.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => { - changeAccessor.deltaDecorations([ - rangeHighlightDecorationId.lineDecorationId, - rangeHighlightDecorationId.rangeHighlightId - ], []); - }); - } - }); - - this.rangeHighlightDecorationId = undefined; - } - } - - onClose(canceled: boolean): void { - - // Cancel any pending/cached outline request now - this.clearOutlineRequest(); - - // Clear Highlight Decorations if present - this.clearDecorations(); - - // Restore selection if canceled - if (canceled && this.lastKnownEditorViewState) { - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (activeTextEditorWidget) { - activeTextEditorWidget.restoreViewState(this.lastKnownEditorViewState); - } - - this.lastKnownEditorViewState = null; - } - } - - private clearOutlineRequest(): void { - if (this.pendingOutlineRequest) { - this.pendingOutlineRequest.cancel(); - this.pendingOutlineRequest.dispose(); - this.pendingOutlineRequest = undefined; - } - - this.cachedOutlineRequest = undefined; - } -} diff --git a/src/vs/workbench/contrib/quickopen/browser/helpHandler.ts b/src/vs/workbench/contrib/quickopen/browser/helpHandler.ts deleted file mode 100644 index 8d8aaafcca2..00000000000 --- a/src/vs/workbench/contrib/quickopen/browser/helpHandler.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 nls from 'vs/nls'; -import * as types from 'vs/base/common/types'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { Mode, IEntryRunContext, IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen'; -import { QuickOpenModel, QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { IQuickOpenRegistry, Extensions, QuickOpenHandler, QuickOpenHandlerDescriptor, QuickOpenHandlerHelpEntry } from 'vs/workbench/browser/quickopen'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; -import { CancellationToken } from 'vs/base/common/cancellation'; - -export const HELP_PREFIX = '?'; - -class HelpEntry extends QuickOpenEntryGroup { - private prefixLabel: string; - private prefix: string; - private description: string | undefined; - private quickOpenService: IQuickOpenService; - private openOnPreview: boolean; - - constructor(prefix: string, description: string | undefined, openOnPreview: boolean, quickOpenService: IQuickOpenService) { - super(); - - if (!prefix) { - this.prefix = ''; - this.prefixLabel = '\u2026' /* ... */; - } else { - this.prefix = this.prefixLabel = prefix; - } - - this.description = description; - this.quickOpenService = quickOpenService; - this.openOnPreview = openOnPreview; - } - - getLabel(): string { - return this.prefixLabel; - } - - getAriaLabel(): string { - return nls.localize('entryAriaLabel', "{0}, picker help", this.getLabel()); - } - - getDescription(): string | undefined { - return this.description; - } - - run(mode: Mode, context: IEntryRunContext): boolean { - if (mode === Mode.OPEN || this.openOnPreview) { - this.quickOpenService.show(this.prefix); - } - - return false; - } -} - -export class HelpHandler extends QuickOpenHandler { - - static readonly ID = 'workbench.picker.help'; - - constructor(@IQuickOpenService private readonly quickOpenService: IQuickOpenService) { - super(); - } - - getResults(searchValue: string, token: CancellationToken): Promise { - searchValue = searchValue.trim(); - - const registry = (Registry.as(Extensions.Quickopen)); - const handlerDescriptors = registry.getQuickOpenHandlers(); - - const defaultHandler = registry.getDefaultQuickOpenHandler(); - if (defaultHandler) { - handlerDescriptors.push(defaultHandler); - } - - const workbenchScoped: HelpEntry[] = []; - const editorScoped: HelpEntry[] = []; - - const matchingHandlers: Array = []; - handlerDescriptors.sort((h1, h2) => h1.prefix.localeCompare(h2.prefix)).forEach(handlerDescriptor => { - if (handlerDescriptor.prefix !== HELP_PREFIX) { - - // Descriptor has multiple help entries - if (types.isArray(handlerDescriptor.helpEntries)) { - for (const helpEntry of handlerDescriptor.helpEntries) { - if (helpEntry.prefix.indexOf(searchValue) === 0) { - matchingHandlers.push(helpEntry); - } - } - } - - // Single Help entry for descriptor - else if (handlerDescriptor.prefix.indexOf(searchValue) === 0) { - matchingHandlers.push(handlerDescriptor); - } - } - }); - - matchingHandlers.forEach(handler => { - if (handler instanceof QuickOpenHandlerDescriptor) { - workbenchScoped.push(new HelpEntry(handler.prefix, handler.description, matchingHandlers.length === 1, this.quickOpenService)); - } else { - const entry = new HelpEntry(handler.prefix, handler.description, matchingHandlers.length === 1, this.quickOpenService); - if (handler.needsEditor) { - editorScoped.push(entry); - } else { - workbenchScoped.push(entry); - } - } - }); - - // Add separator for workbench scoped handlers - if (workbenchScoped.length > 0) { - workbenchScoped[0].setGroupLabel(nls.localize('globalCommands', "global commands")); - } - - // Add separator for editor scoped handlers - if (editorScoped.length > 0) { - editorScoped[0].setGroupLabel(nls.localize('editorCommands', "editor commands")); - if (workbenchScoped.length > 0) { - editorScoped[0].setShowBorder(true); - } - } - - return Promise.resolve(new QuickOpenModel([...workbenchScoped, ...editorScoped])); - } - - getAutoFocus(searchValue: string): IAutoFocus { - searchValue = searchValue.trim(); - return { - autoFocusFirstEntry: searchValue.length > 0, - autoFocusPrefixMatch: searchValue - }; - } -} diff --git a/src/vs/workbench/contrib/quickopen/browser/quickopen.contribution.ts b/src/vs/workbench/contrib/quickopen/browser/quickopen.contribution.ts deleted file mode 100644 index ecac0bf8cc0..00000000000 --- a/src/vs/workbench/contrib/quickopen/browser/quickopen.contribution.ts +++ /dev/null @@ -1,194 +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 env from 'vs/base/common/platform'; -import * as nls from 'vs/nls'; -import { QuickOpenHandlerDescriptor, IQuickOpenRegistry, Extensions as QuickOpenExtensions } from 'vs/workbench/browser/quickopen'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; -import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; -import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; -import { GotoSymbolAction, GOTO_SYMBOL_PREFIX, SCOPE_PREFIX, GotoSymbolHandler } from 'vs/workbench/contrib/quickopen/browser/gotoSymbolHandler'; -import { ShowAllCommandsAction, ALL_COMMANDS_PREFIX, ClearCommandHistoryAction, CommandsHandler } from 'vs/workbench/contrib/quickopen/browser/commandsHandler'; -import { GotoLineAction, GOTO_LINE_PREFIX, GotoLineHandler } from 'vs/workbench/contrib/quickopen/browser/gotoLineHandler'; -import { HELP_PREFIX, HelpHandler } from 'vs/workbench/contrib/quickopen/browser/helpHandler'; -import { VIEW_PICKER_PREFIX, OpenViewPickerAction, QuickOpenViewPickerAction, ViewPickerHandler } from 'vs/workbench/contrib/quickopen/browser/viewPickerHandler'; -import { inQuickOpenContext, getQuickNavigateHandler } from 'vs/workbench/browser/parts/quickopen/quickopen'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; - -// Register Actions -const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(SyncActionDescriptor.create(ClearCommandHistoryAction, ClearCommandHistoryAction.ID, ClearCommandHistoryAction.LABEL), 'Clear Command History'); -registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowAllCommandsAction, ShowAllCommandsAction.ID, ShowAllCommandsAction.LABEL, { - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_P, - secondary: [KeyCode.F1] -}), 'Show All Commands'); - -registry.registerWorkbenchAction(SyncActionDescriptor.create(GotoLineAction, GotoLineAction.ID, GotoLineAction.LABEL, { - primary: KeyMod.CtrlCmd | KeyCode.KEY_G, - mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_G } -}), 'Go to Line...'); - -registry.registerWorkbenchAction(SyncActionDescriptor.create(GotoSymbolAction, GotoSymbolAction.ID, GotoSymbolAction.LABEL, { - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_O -}), 'Go to Symbol in File...'); - -const inViewsPickerContextKey = 'inViewsPicker'; -const inViewsPickerContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyExpr.has(inViewsPickerContextKey)); - -const viewPickerKeybinding = { primary: KeyMod.CtrlCmd | KeyCode.KEY_Q, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_Q }, linux: { primary: 0 } }; - -const viewCategory = nls.localize('view', "View"); -registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenViewPickerAction, OpenViewPickerAction.ID, OpenViewPickerAction.LABEL), 'View: Open View', viewCategory); -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenViewPickerAction, QuickOpenViewPickerAction.ID, QuickOpenViewPickerAction.LABEL, viewPickerKeybinding), 'View: Quick Open View', viewCategory); - -const quickOpenNavigateNextInViewPickerId = 'workbench.action.quickOpenNavigateNextInViewPicker'; -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: quickOpenNavigateNextInViewPickerId, - weight: KeybindingWeight.WorkbenchContrib + 50, - handler: getQuickNavigateHandler(quickOpenNavigateNextInViewPickerId, true), - when: inViewsPickerContext, - primary: viewPickerKeybinding.primary, - linux: viewPickerKeybinding.linux, - mac: viewPickerKeybinding.mac -}); - -const quickOpenNavigatePreviousInViewPickerId = 'workbench.action.quickOpenNavigatePreviousInViewPicker'; -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: quickOpenNavigatePreviousInViewPickerId, - weight: KeybindingWeight.WorkbenchContrib + 50, - handler: getQuickNavigateHandler(quickOpenNavigatePreviousInViewPickerId, false), - when: inViewsPickerContext, - primary: viewPickerKeybinding.primary | KeyMod.Shift, - linux: viewPickerKeybinding.linux, - mac: { - primary: viewPickerKeybinding.mac.primary | KeyMod.Shift - } -}); - -// Register Quick Open Handler - -Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - QuickOpenHandlerDescriptor.create( - CommandsHandler, - CommandsHandler.ID, - ALL_COMMANDS_PREFIX, - 'inCommandsPicker', - nls.localize('commandsHandlerDescriptionDefault', "Show and Run Commands") - ) -); - -Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - QuickOpenHandlerDescriptor.create( - GotoLineHandler, - GotoLineHandler.ID, - GOTO_LINE_PREFIX, - undefined, - [ - { - prefix: GOTO_LINE_PREFIX, - needsEditor: true, - description: env.isMacintosh ? nls.localize('gotoLineDescriptionMac', "Go to Line") : nls.localize('gotoLineDescriptionWin', "Go to Line") - }, - ] - ) -); - -Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - QuickOpenHandlerDescriptor.create( - GotoSymbolHandler, - GotoSymbolHandler.ID, - GOTO_SYMBOL_PREFIX, - 'inFileSymbolsPicker', - [ - { - prefix: GOTO_SYMBOL_PREFIX, - needsEditor: true, - description: nls.localize('gotoSymbolDescription', "Go to Symbol in File") - }, - { - prefix: GOTO_SYMBOL_PREFIX + SCOPE_PREFIX, - needsEditor: true, - description: nls.localize('gotoSymbolDescriptionScoped', "Go to Symbol in File by Category") - } - ] - ) -); - -Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - QuickOpenHandlerDescriptor.create( - HelpHandler, - HelpHandler.ID, - HELP_PREFIX, - undefined, - nls.localize('helpDescription', "Show Help") - ) -); - -Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - QuickOpenHandlerDescriptor.create( - ViewPickerHandler, - ViewPickerHandler.ID, - VIEW_PICKER_PREFIX, - inViewsPickerContextKey, - [ - { - prefix: VIEW_PICKER_PREFIX, - needsEditor: false, - description: nls.localize('viewPickerDescription', "Open View") - } - ] - ) -); - -// View menu - -MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { - group: '1_open', - command: { - id: ShowAllCommandsAction.ID, - title: nls.localize({ key: 'miCommandPalette', comment: ['&& denotes a mnemonic'] }, "&&Command Palette...") - }, - order: 1 -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { - group: '1_open', - command: { - id: OpenViewPickerAction.ID, - title: nls.localize({ key: 'miOpenView', comment: ['&& denotes a mnemonic'] }, "&&Open View...") - }, - order: 2 -}); - -// Go to menu - -MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { - group: '4_symbol_nav', - command: { - id: 'workbench.action.gotoSymbol', - title: nls.localize({ key: 'miGotoSymbolInFile', comment: ['&& denotes a mnemonic'] }, "Go to &&Symbol in File...") - }, - order: 1 -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { - group: '5_infile_nav', - command: { - id: 'workbench.action.gotoLine', - title: nls.localize({ key: 'miGotoLine', comment: ['&& denotes a mnemonic'] }, "Go to &&Line/Column...") - }, - order: 1 -}); - -MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - group: '1_command', - command: { - id: ShowAllCommandsAction.ID, - title: nls.localize('commandPalette', "Command Palette...") - }, - order: 1 -}); diff --git a/src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts b/src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts deleted file mode 100644 index 88d9c55a925..00000000000 --- a/src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts +++ /dev/null @@ -1,249 +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 nls from 'vs/nls'; -import { Mode, IEntryRunContext, IAutoFocus, IQuickNavigateConfiguration, IModel } from 'vs/base/parts/quickopen/common/quickOpen'; -import { QuickOpenModel, QuickOpenEntryGroup, QuickOpenEntry } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { QuickOpenHandler, QuickOpenAction } from 'vs/workbench/browser/quickopen'; -import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { IOutputService } from 'vs/workbench/contrib/output/common/output'; -import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; -import { Action } from 'vs/base/common/actions'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { fuzzyContains, stripWildcards } from 'vs/base/common/strings'; -import { matchesFuzzy } from 'vs/base/common/filters'; -import { IViewsRegistry, ViewContainer, IViewDescriptorService, IViewContainersRegistry, Extensions as ViewExtensions, IViewsService } from 'vs/workbench/common/views'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { IStringDictionary } from 'vs/base/common/collections'; - -export const VIEW_PICKER_PREFIX = 'view '; - -export class ViewEntry extends QuickOpenEntryGroup { - - constructor( - private label: string, - private category: string, - private open: () => void - ) { - super(); - } - - getLabel(): string { - return this.label; - } - - getCategory(): string { - return this.category; - } - - getAriaLabel(): string { - return nls.localize('entryAriaLabel', "{0}, view picker", this.getLabel()); - } - - run(mode: Mode, context: IEntryRunContext): boolean { - if (mode === Mode.OPEN) { - return this.runOpen(context); - } - - return super.run(mode, context); - } - - private runOpen(context: IEntryRunContext): boolean { - setTimeout(() => { - this.open(); - }, 0); - - return true; - } -} - -export class ViewPickerHandler extends QuickOpenHandler { - - static readonly ID = 'workbench.picker.views'; - - constructor( - @IViewletService private readonly viewletService: IViewletService, - @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, - @IViewsService private readonly viewsService: IViewsService, - @IOutputService private readonly outputService: IOutputService, - @ITerminalService private readonly terminalService: ITerminalService, - @IPanelService private readonly panelService: IPanelService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - ) { - super(); - } - - getResults(searchValue: string, token: CancellationToken): Promise { - searchValue = searchValue.trim(); - const normalizedSearchValueLowercase = stripWildcards(searchValue).toLowerCase(); - - const viewEntries = this.getViewEntries(); - - const entries = viewEntries.filter(e => { - if (!searchValue) { - return true; - } - - const highlights = matchesFuzzy(normalizedSearchValueLowercase, e.getLabel(), true); - if (highlights) { - e.setHighlights(highlights); - } - - if (!highlights && !fuzzyContains(e.getCategory(), normalizedSearchValueLowercase)) { - return false; - } - - return true; - }); - - const entryToCategory: IStringDictionary = {}; - entries.forEach(e => { - if (!entryToCategory[e.getLabel()]) { - entryToCategory[e.getLabel()] = e.getCategory(); - } - }); - - let lastCategory: string; - entries.forEach((e, index) => { - if (lastCategory !== e.getCategory()) { - lastCategory = e.getCategory(); - - e.setShowBorder(index > 0); - e.setGroupLabel(lastCategory); - - // When the entry category has a parent category, set group label as Parent / Child. For example, Views / Explorer. - if (entryToCategory[lastCategory]) { - e.setGroupLabel(`${entryToCategory[lastCategory]} / ${lastCategory}`); - } - } else { - e.setShowBorder(false); - e.setGroupLabel(undefined); - } - }); - - return Promise.resolve(new QuickOpenModel(entries)); - } - - private getViewEntries(): ViewEntry[] { - const viewEntries: ViewEntry[] = []; - - const getViewEntriesForViewlet = (viewlet: ViewletDescriptor, viewContainer: ViewContainer): ViewEntry[] => { - const views = Registry.as(ViewExtensions.ViewsRegistry).getViews(viewContainer); - const result: ViewEntry[] = []; - if (views.length) { - for (const view of views) { - if (this.contextKeyService.contextMatchesRules(view.when)) { - result.push(new ViewEntry(view.name, viewlet.name, () => this.viewsService.openView(view.id, true))); - } - } - } - return result; - }; - - // Viewlets - const viewlets = this.viewletService.getViewlets(); - viewlets.forEach((viewlet, index) => { - if (this.hasToShowViewlet(viewlet)) { - viewEntries.push(new ViewEntry(viewlet.name, nls.localize('views', "Side Bar"), () => this.viewletService.openViewlet(viewlet.id, true))); - } - }); - - // Panels - const panels = this.panelService.getPanels(); - panels.forEach((panel, index) => viewEntries.push(new ViewEntry(panel.name, nls.localize('panels', "Panel"), () => this.panelService.openPanel(panel.id, true)))); - - // Viewlet Views - viewlets.forEach((viewlet, index) => { - const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).get(viewlet.id); - if (viewContainer) { - const viewEntriesForViewlet: ViewEntry[] = getViewEntriesForViewlet(viewlet, viewContainer); - viewEntries.push(...viewEntriesForViewlet); - } - }); - - // Terminals - const terminalsCategory = nls.localize('terminals', "Terminal"); - this.terminalService.terminalTabs.forEach((tab, tabIndex) => { - tab.terminalInstances.forEach((terminal, terminalIndex) => { - const index = `${tabIndex + 1}.${terminalIndex + 1}`; - const entry = new ViewEntry(nls.localize('terminalTitle', "{0}: {1}", index, terminal.title), terminalsCategory, () => { - this.terminalService.showPanel(true).then(() => { - this.terminalService.setActiveInstance(terminal); - }); - }); - - viewEntries.push(entry); - }); - }); - - // Output Channels - const channels = this.outputService.getChannelDescriptors(); - channels.forEach((channel, index) => { - const outputCategory = nls.localize('channels', "Output"); - const entry = new ViewEntry(channel.log ? nls.localize('logChannel', "Log ({0})", channel.label) : channel.label, outputCategory, () => this.outputService.showChannel(channel.id)); - - viewEntries.push(entry); - }); - - return viewEntries; - } - - private hasToShowViewlet(viewlet: ViewletDescriptor): boolean { - const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).get(viewlet.id); - if (viewContainer?.hideIfEmpty) { - const viewsCollection = this.viewDescriptorService.getViewDescriptors(viewContainer); - return viewsCollection.activeViewDescriptors.length > 0; - } - return true; - } - - getAutoFocus(searchValue: string, context: { model: IModel, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus { - return { - autoFocusFirstEntry: !!searchValue || !!context.quickNavigateConfiguration - }; - } -} - -export class OpenViewPickerAction extends QuickOpenAction { - - static readonly ID = 'workbench.action.openView'; - static readonly LABEL = nls.localize('openView', "Open View"); - - constructor( - id: string, - label: string, - @IQuickOpenService quickOpenService: IQuickOpenService - ) { - super(id, label, VIEW_PICKER_PREFIX, quickOpenService); - } -} - -export class QuickOpenViewPickerAction extends Action { - - static readonly ID = 'workbench.action.quickOpenView'; - static readonly LABEL = nls.localize('quickOpenView', "Quick Open View"); - - constructor( - id: string, - label: string, - @IQuickOpenService private readonly quickOpenService: IQuickOpenService, - @IKeybindingService private readonly keybindingService: IKeybindingService - ) { - super(id, label); - } - - run(): Promise { - const keys = this.keybindingService.lookupKeybindings(this.id); - - this.quickOpenService.show(VIEW_PICKER_PREFIX, { quickNavigateConfiguration: { keybindings: keys } }); - - return Promise.resolve(true); - } -} diff --git a/src/vs/workbench/contrib/remote/browser/remoteViewlet.css b/src/vs/workbench/contrib/remote/browser/media/remoteViewlet.css similarity index 100% rename from src/vs/workbench/contrib/remote/browser/remoteViewlet.css rename to src/vs/workbench/contrib/remote/browser/media/remoteViewlet.css diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index 47dd085867b..f4b3c823651 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./remoteViewlet'; +import 'vs/css!./media/remoteViewlet'; import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { URI } from 'vs/base/common/uri'; @@ -50,11 +50,13 @@ import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views' import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; -import { WorkbenchAsyncDataTree, TreeResourceNavigator } from 'vs/platform/list/browser/listService'; +import { WorkbenchAsyncDataTree, ResourceNavigator } from 'vs/platform/list/browser/listService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Event } from 'vs/base/common/event'; import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { RemoteWindowActiveIndicator } from 'vs/workbench/contrib/remote/browser/remoteIndicator'; +import { inQuickPickContextKeyValue } from 'vs/workbench/browser/quickaccess'; export interface HelpInformation { extensionDescription: IExtensionDescription; @@ -402,7 +404,7 @@ class HelpPanel extends ViewPane { this.tree.setInput(model); - const helpItemNavigator = this._register(new TreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false })); + const helpItemNavigator = this._register(ResourceNavigator.createTreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false })); this._register(Event.debounce(helpItemNavigator.onDidOpenResource, (last, event) => event, 75, true)(e => { e.element.handleClick(); @@ -584,26 +586,100 @@ Registry.as(WorkbenchActionExtensions.WorkbenchActions nls.localize('view', "View") ); +class VisibleProgress { -class ProgressReporter { - private _currentProgress: IProgress | null = null; - private lastReport: string | null = null; + private _isDisposed: boolean; + private _lastReport: string | null; + private _currentProgressPromiseResolve: (() => void) | null; + private _currentProgress: IProgress | null; + private _currentTimer: ReconnectionTimer2 | null; - constructor(currentProgress: IProgress | null) { - this._currentProgress = currentProgress; + public get lastReport(): string | null { + return this._lastReport; } - set currentProgress(progress: IProgress) { - this._currentProgress = progress; + constructor(progressService: IProgressService, location: ProgressLocation, initialReport: string | null, buttons: string[], onDidCancel: (choice: number | undefined, lastReport: string | null) => void) { + this._isDisposed = false; + this._lastReport = initialReport; + this._currentProgressPromiseResolve = null; + this._currentProgress = null; + this._currentTimer = null; + + const promise = new Promise((resolve) => this._currentProgressPromiseResolve = resolve); + + progressService.withProgress( + { location: location, buttons: buttons }, + (progress) => { if (!this._isDisposed) { this._currentProgress = progress; } return promise; }, + (choice) => onDidCancel(choice, this._lastReport) + ); + + if (this._lastReport) { + this.report(); + } } - report(message?: string) { + public dispose(): void { + this._isDisposed = true; + if (this._currentProgressPromiseResolve) { + this._currentProgressPromiseResolve(); + this._currentProgressPromiseResolve = null; + } + this._currentProgress = null; + if (this._currentTimer) { + this._currentTimer.dispose(); + this._currentTimer = null; + } + } + + public report(message?: string) { if (message) { - this.lastReport = message; + this._lastReport = message; } - if (this.lastReport && this._currentProgress) { - this._currentProgress.report({ message: this.lastReport }); + if (this._lastReport && this._currentProgress) { + this._currentProgress.report({ message: this._lastReport }); + } + } + + public startTimer(completionTime: number): void { + this.stopTimer(); + this._currentTimer = new ReconnectionTimer2(this, completionTime); + } + + public stopTimer(): void { + if (this._currentTimer) { + this._currentTimer.dispose(); + this._currentTimer = null; + } + } +} + +class ReconnectionTimer2 implements IDisposable { + private readonly _parent: VisibleProgress; + private readonly _completionTime: number; + private readonly _token: any; + + constructor(parent: VisibleProgress, completionTime: number) { + this._parent = parent; + this._completionTime = completionTime; + this._token = setInterval(() => this._render(), 1000); + this._render(); + } + + public dispose(): void { + clearInterval(this._token); + } + + private _render() { + const remainingTimeMs = this._completionTime - Date.now(); + if (remainingTimeMs < 0) { + return; + } + const remainingTime = Math.ceil(remainingTimeMs / 1000); + if (remainingTime === 1) { + this._parent.report(nls.localize('reconnectionWaitOne', "Attempting to reconnect in {0} second...", remainingTime)); + } else { + this._parent.report(nls.localize('reconnectionWaitMany', "Attempting to reconnect in {0} seconds...", remainingTime)); } } } @@ -618,58 +694,41 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { ) { const connection = remoteAgentService.getConnection(); if (connection) { - let currentProgressPromiseResolve: (() => void) | null = null; - let progressReporter: ProgressReporter | null = null; - let lastLocation: ProgressLocation | null = null; - let currentTimer: ReconnectionTimer | null = null; + let visibleProgress: VisibleProgress | null = null; + let lastLocation: ProgressLocation.Dialog | ProgressLocation.Notification | null = null; let reconnectWaitEvent: ReconnectionWaitEvent | null = null; let disposableListener: IDisposable | null = null; - function showProgress(location: ProgressLocation, buttons: { label: string, callback: () => void }[]) { - if (currentProgressPromiseResolve) { - currentProgressPromiseResolve(); + function showProgress(location: ProgressLocation.Dialog | ProgressLocation.Notification, buttons: { label: string, callback: () => void }[], initialReport: string | null = null): VisibleProgress { + if (visibleProgress) { + visibleProgress.dispose(); + visibleProgress = null; } - const promise = new Promise((resolve) => currentProgressPromiseResolve = resolve); lastLocation = location; - if (location === ProgressLocation.Dialog) { - // Show dialog - progressService!.withProgress( - { location: ProgressLocation.Dialog, buttons: buttons.map(button => button.label) }, - (progress) => { if (progressReporter) { progressReporter.currentProgress = progress; } return promise; }, - (choice?) => { - // Handle choice from dialog - if (typeof choice !== 'undefined' && buttons[choice]) { - buttons[choice].callback(); - } else { - showProgress(ProgressLocation.Notification, buttons); - } - - progressReporter!.report(); - }); - } else { - // Show notification - progressService!.withProgress( - { location: ProgressLocation.Notification, buttons: buttons.map(button => button.label) }, - (progress) => { if (progressReporter) { progressReporter.currentProgress = progress; } return promise; }, - (choice?) => { - // Handle choice from dialog - if (typeof choice !== 'undefined' && buttons[choice]) { - buttons[choice].callback(); + return new VisibleProgress( + progressService, location, initialReport, buttons.map(button => button.label), + (choice, lastReport) => { + // Handle choice from dialog + if (typeof choice !== 'undefined' && buttons[choice]) { + buttons[choice].callback(); + } else { + if (location === ProgressLocation.Dialog) { + visibleProgress = showProgress(ProgressLocation.Notification, buttons, lastReport); } else { hideProgress(); } - }); - } + } + } + ); } function hideProgress() { - if (currentProgressPromiseResolve) { - currentProgressPromiseResolve(); + if (visibleProgress) { + visibleProgress.dispose(); + visibleProgress = null; } - - currentProgressPromiseResolve = null; } const reconnectButton = { @@ -689,9 +748,8 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { }; connection.onDidStateChange((e) => { - if (currentTimer) { - currentTimer.dispose(); - currentTimer = null; + if (visibleProgress) { + visibleProgress.stopTimer(); } if (disposableListener) { @@ -700,33 +758,27 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { } switch (e.type) { case PersistentConnectionEventType.ConnectionLost: - if (!currentProgressPromiseResolve) { - progressReporter = new ProgressReporter(null); - showProgress(ProgressLocation.Dialog, [reconnectButton, reloadButton]); + if (!visibleProgress) { + visibleProgress = showProgress(ProgressLocation.Dialog, [reconnectButton, reloadButton]); } - - progressReporter!.report(nls.localize('connectionLost', "Connection Lost")); + visibleProgress.report(nls.localize('connectionLost', "Connection Lost")); break; case PersistentConnectionEventType.ReconnectionWait: - hideProgress(); reconnectWaitEvent = e; - showProgress(lastLocation || ProgressLocation.Notification, [reconnectButton, reloadButton]); - currentTimer = new ReconnectionTimer(progressReporter!, Date.now() + 1000 * e.durationSeconds); + visibleProgress = showProgress(lastLocation || ProgressLocation.Notification, [reconnectButton, reloadButton]); + visibleProgress.startTimer(Date.now() + 1000 * e.durationSeconds); break; case PersistentConnectionEventType.ReconnectionRunning: - hideProgress(); - showProgress(lastLocation || ProgressLocation.Notification, [reloadButton]); - progressReporter!.report(nls.localize('reconnectionRunning', "Attempting to reconnect...")); + visibleProgress = showProgress(lastLocation || ProgressLocation.Notification, [reloadButton]); + visibleProgress.report(nls.localize('reconnectionRunning', "Attempting to reconnect...")); // Register to listen for quick input is opened disposableListener = contextKeyService.onDidChangeContext((contextKeyChangeEvent) => { - const reconnectInteraction = new Set(['inQuickOpen']); + const reconnectInteraction = new Set([inQuickPickContextKeyValue]); if (contextKeyChangeEvent.affectsSome(reconnectInteraction)) { // Need to move from dialog if being shown and user needs to type in a prompt - if (lastLocation === ProgressLocation.Dialog && progressReporter !== null) { - hideProgress(); - showProgress(ProgressLocation.Notification, [reloadButton]); - progressReporter.report(); + if (lastLocation === ProgressLocation.Dialog && visibleProgress !== null) { + visibleProgress = showProgress(ProgressLocation.Notification, [reloadButton], visibleProgress.lastReport); } } }); @@ -734,7 +786,6 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { break; case PersistentConnectionEventType.ReconnectionPermanentFailure: hideProgress(); - progressReporter = null; dialogService.show(Severity.Error, nls.localize('reconnectionPermanentFailure', "Cannot reconnect. Please reload the window."), [nls.localize('reloadWindow', "Reload Window"), nls.localize('cancel', "Cancel")], { cancelId: 1 }).then(result => { // Reload the window @@ -745,7 +796,6 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { break; case PersistentConnectionEventType.ConnectionGain: hideProgress(); - progressReporter = null; break; } }); @@ -753,35 +803,7 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { } } -class ReconnectionTimer implements IDisposable { - private readonly _progressReporter: ProgressReporter; - private readonly _completionTime: number; - private readonly _token: any; - - constructor(progressReporter: ProgressReporter, completionTime: number) { - this._progressReporter = progressReporter; - this._completionTime = completionTime; - this._token = setInterval(() => this._render(), 1000); - this._render(); - } - - public dispose(): void { - clearInterval(this._token); - } - - private _render() { - const remainingTimeMs = this._completionTime - Date.now(); - if (remainingTimeMs < 0) { - return; - } - const remainingTime = Math.ceil(remainingTimeMs / 1000); - if (remainingTime === 1) { - this._progressReporter.report(nls.localize('reconnectionWaitOne', "Attempting to reconnect in {0} second...", remainingTime)); - } else { - this._progressReporter.report(nls.localize('reconnectionWaitMany', "Attempting to reconnect in {0} seconds...", remainingTime)); - } - } -} - const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentConnectionStatusListener, LifecyclePhase.Eventually); +workbenchContributionsRegistry.registerWorkbenchContribution(RemoteWindowActiveIndicator, LifecyclePhase.Starting); + diff --git a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts new file mode 100644 index 00000000000..011947d3dc2 --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts @@ -0,0 +1,229 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { STATUS_BAR_HOST_NAME_BACKGROUND, STATUS_BAR_HOST_NAME_FOREGROUND } from 'vs/workbench/common/theme'; +import { themeColorFromId } from 'vs/platform/theme/common/themeService'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { MenuId, IMenuService, MenuItemAction, IMenu, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection'; +import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { RemoteConnectionState, Deprecated_RemoteAuthorityContext } from 'vs/workbench/browser/contextkeys'; +import { isWeb } from 'vs/base/common/platform'; +import { once } from 'vs/base/common/functional'; + +const WINDOW_ACTIONS_COMMAND_ID = 'workbench.action.remote.showMenu'; +const CLOSE_REMOTE_COMMAND_ID = 'workbench.action.remote.close'; +const SHOW_CLOSE_REMOTE_COMMAND_ID = !isWeb; // web does not have a "Close Remote" command + +export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenchContribution { + + private windowIndicatorEntry: IStatusbarEntryAccessor | undefined; + private windowCommandMenu: IMenu; + private hasWindowActions: boolean = false; + private remoteAuthority: string | undefined; + private connectionState: 'initializing' | 'connected' | 'disconnected' | undefined = undefined; + + constructor( + @IStatusbarService private readonly statusbarService: IStatusbarService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @ILabelService private readonly labelService: ILabelService, + @IContextKeyService private contextKeyService: IContextKeyService, + @IMenuService private menuService: IMenuService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @ICommandService private readonly commandService: ICommandService, + @IExtensionService extensionService: IExtensionService, + @IRemoteAgentService remoteAgentService: IRemoteAgentService, + @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, + @IHostService hostService: IHostService + ) { + super(); + + this.windowCommandMenu = this.menuService.createMenu(MenuId.StatusBarWindowIndicatorMenu, this.contextKeyService); + this._register(this.windowCommandMenu); + + const category = nls.localize('remote.category', "Remote"); + const that = this; + registerAction2(class extends Action2 { + constructor() { + super({ + id: WINDOW_ACTIONS_COMMAND_ID, + category, + title: { value: nls.localize('remote.showMenu', "Show Remote Menu"), original: 'Show Remote Menu' }, + f1: true, + }); + } + run = () => that.showIndicatorActions(that.windowCommandMenu); + }); + + this.remoteAuthority = environmentService.configuration.remoteAuthority; + Deprecated_RemoteAuthorityContext.bindTo(this.contextKeyService).set(this.remoteAuthority || ''); + + if (this.remoteAuthority) { + + if (SHOW_CLOSE_REMOTE_COMMAND_ID) { + registerAction2(class extends Action2 { + constructor() { + super({ + id: CLOSE_REMOTE_COMMAND_ID, + category, + title: { value: nls.localize('remote.close', "Close Remote Connection"), original: 'Close Remote Connection' }, + f1: true + }); + } + run = () => that.remoteAuthority && hostService.openWindow({ forceReuseWindow: true }); + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '6_close', + command: { + id: CLOSE_REMOTE_COMMAND_ID, + title: nls.localize({ key: 'miCloseRemote', comment: ['&& denotes a mnemonic'] }, "Close Re&&mote Connection") + }, + order: 3.5 + }); + } + + // Pending entry until extensions are ready + this.renderWindowIndicator('$(sync~spin) ' + nls.localize('host.open', "Opening Remote..."), undefined, WINDOW_ACTIONS_COMMAND_ID); + this.connectionState = 'initializing'; + RemoteConnectionState.bindTo(this.contextKeyService).set(this.connectionState); + + const connection = remoteAgentService.getConnection(); + if (connection) { + this._register(connection.onDidStateChange((e) => { + switch (e.type) { + case PersistentConnectionEventType.ConnectionLost: + case PersistentConnectionEventType.ReconnectionPermanentFailure: + case PersistentConnectionEventType.ReconnectionRunning: + case PersistentConnectionEventType.ReconnectionWait: + this.setDisconnected(true); + break; + case PersistentConnectionEventType.ConnectionGain: + this.setDisconnected(false); + break; + } + })); + } + } + + extensionService.whenInstalledExtensionsRegistered().then(_ => { + if (this.remoteAuthority) { + this._register(this.labelService.onDidChangeFormatters(e => this.updateWindowIndicator())); + remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority).then(() => this.setDisconnected(false), () => this.setDisconnected(true)); + } + this._register(this.windowCommandMenu.onDidChange(e => this.updateWindowActions())); + this.updateWindowIndicator(); + }); + } + + private setDisconnected(isDisconnected: boolean): void { + const newState = isDisconnected ? 'disconnected' : 'connected'; + if (this.connectionState !== newState) { + this.connectionState = newState; + RemoteConnectionState.bindTo(this.contextKeyService).set(this.connectionState); + Deprecated_RemoteAuthorityContext.bindTo(this.contextKeyService).set(isDisconnected ? `disconnected/${this.remoteAuthority!}` : this.remoteAuthority!); + this.updateWindowIndicator(); + } + } + + private updateWindowIndicator(): void { + const windowActionCommand = (this.remoteAuthority || this.windowCommandMenu.getActions().length) ? WINDOW_ACTIONS_COMMAND_ID : undefined; + if (this.remoteAuthority) { + const hostLabel = this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.remoteAuthority) || this.remoteAuthority; + if (this.connectionState !== 'disconnected') { + this.renderWindowIndicator(`$(remote) ${hostLabel}`, nls.localize('host.tooltip', "Editing on {0}", hostLabel), windowActionCommand); + } else { + this.renderWindowIndicator(`$(alert) ${nls.localize('disconnectedFrom', "Disconnected from")} ${hostLabel}`, nls.localize('host.tooltipDisconnected', "Disconnected from {0}", hostLabel), windowActionCommand); + } + } else { + if (windowActionCommand) { + this.renderWindowIndicator(`$(remote)`, nls.localize('noHost.tooltip', "Open a remote window"), windowActionCommand); + } else if (this.windowIndicatorEntry) { + this.windowIndicatorEntry.dispose(); + this.windowIndicatorEntry = undefined; + } + } + } + + private updateWindowActions() { + const newHasWindowActions = this.windowCommandMenu.getActions().length > 0; + if (newHasWindowActions !== this.hasWindowActions) { + this.hasWindowActions = newHasWindowActions; + this.updateWindowIndicator(); + } + } + + private renderWindowIndicator(text: string, tooltip?: string, command?: string): void { + const properties: IStatusbarEntry = { + backgroundColor: themeColorFromId(STATUS_BAR_HOST_NAME_BACKGROUND), color: themeColorFromId(STATUS_BAR_HOST_NAME_FOREGROUND), text, tooltip, command + }; + if (this.windowIndicatorEntry) { + this.windowIndicatorEntry.update(properties); + } else { + this.windowIndicatorEntry = this.statusbarService.addEntry(properties, 'status.host', nls.localize('status.host', "Remote Host"), StatusbarAlignment.LEFT, Number.MAX_VALUE /* first entry */); + } + } + + private showIndicatorActions(menu: IMenu) { + + const actions = menu.getActions(); + + const items: (IQuickPickItem | IQuickPickSeparator)[] = []; + for (let actionGroup of actions) { + if (items.length) { + items.push({ type: 'separator' }); + } + for (let action of actionGroup[1]) { + if (action instanceof MenuItemAction) { + let label = typeof action.item.title === 'string' ? action.item.title : action.item.title.value; + if (action.item.category) { + const category = typeof action.item.category === 'string' ? action.item.category : action.item.category.value; + label = nls.localize('cat.title', "{0}: {1}", category, label); + } + items.push({ + type: 'item', + id: action.item.id, + label + }); + } + } + } + + if (SHOW_CLOSE_REMOTE_COMMAND_ID && this.remoteAuthority) { + if (items.length) { + items.push({ type: 'separator' }); + } + items.push({ + type: 'item', + id: CLOSE_REMOTE_COMMAND_ID, + label: nls.localize('closeRemote.title', 'Close Remote Connection') + }); + } + + const quickPick = this.quickInputService.createQuickPick(); + quickPick.items = items; + quickPick.canSelectMany = false; + once(quickPick.onDidAccept)((_ => { + const selectedItems = quickPick.selectedItems; + if (selectedItems.length === 1) { + this.commandService.executeCommand(selectedItems[0].id!); + } + quickPick.hide(); + })); + quickPick.show(); + } +} diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index f4768fd7a15..ae1bac984fd 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/tunnelView'; import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { IViewDescriptor, IEditableData, IViewsService, IViewDescriptorService } from 'vs/workbench/common/views'; -import { WorkbenchAsyncDataTree, TreeResourceNavigator } from 'vs/platform/list/browser/listService'; +import { WorkbenchAsyncDataTree, ResourceNavigator } from 'vs/platform/list/browser/listService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IContextKeyService, IContextKey, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -28,7 +28,7 @@ import { IMenuService, MenuId, IMenu, MenuRegistry, MenuItemAction } from 'vs/pl import { createAndFillInContextMenuActions, createAndFillInActionBarActions, ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IRemoteExplorerService, TunnelModel, MakeAddress, TunnelType, ITunnelItem, Tunnel } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { INotificationService } from 'vs/platform/notification/common/notification'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { once } from 'vs/base/common/functional'; @@ -159,7 +159,7 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { this._candidates.forEach(value => { const key = MakeAddress(value.host, value.port); if (!this.model.forwarded.has(key) && !this.model.detected.has(key)) { - candidates.push(new TunnelItem(TunnelType.Candidate, value.host, value.port, undefined, false, undefined, value.detail)); + candidates.push(new TunnelItem(TunnelType.Candidate, value.host, value.port, undefined, undefined, false, undefined, value.detail)); } }); return candidates; @@ -282,13 +282,13 @@ class TunnelTreeRenderer extends Disposable implements ITreeRenderer { - const content = editableData.validationMessage(value); - if (!content) { + const message = editableData.validationMessage(value); + if (!message || message.severity !== Severity.Error) { return null; } return { - content, + content: message.content, formatContent: true, type: MessageType.ERROR }; @@ -375,7 +375,7 @@ interface ITunnelGroup { class TunnelItem implements ITunnelItem { static createFromTunnel(tunnel: Tunnel, type: TunnelType = TunnelType.Forwarded, closeable?: boolean) { - return new TunnelItem(type, tunnel.remoteHost, tunnel.remotePort, tunnel.localAddress, closeable === undefined ? tunnel.closeable : closeable, tunnel.name, tunnel.description); + return new TunnelItem(type, tunnel.remoteHost, tunnel.remotePort, tunnel.localAddress, tunnel.localPort, closeable === undefined ? tunnel.closeable : closeable, tunnel.name, tunnel.description); } constructor( @@ -383,6 +383,7 @@ class TunnelItem implements ITunnelItem { public remoteHost: string, public remotePort: number, public localAddress?: string, + public localPort?: number, public closeable?: boolean, public name?: string, private _description?: string, @@ -415,11 +416,6 @@ class TunnelItem implements ITunnelItem { } } -function isHostAndPort(address: string | undefined): boolean { - const result = address ? address.match(/^(localhost|([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)|([0-9]+:[0-9]+:[0-9]+:[0-9]+:[0-9]+:[0-9]+:[0-9]+:[0-9]+)):[0-9]+$/) : []; - return (!!result && result.length > 0); -} - export const TunnelTypeContextKey = new RawContextKey('tunnelType', TunnelType.Add); export const TunnelCloseableContextKey = new RawContextKey('tunnelCloseable', false); const TunnelViewFocusContextKey = new RawContextKey('tunnelViewFocus', false); @@ -528,7 +524,7 @@ export class TunnelPanel extends ViewPane { this.tree.updateChildren(undefined, true); })); - const navigator = this._register(new TreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false })); + const navigator = this._register(ResourceNavigator.createTreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false })); this._register(Event.debounce(navigator.onDidOpenResource, (last, event) => event, 75, true)(e => { if (e.element && (e.element.tunnelType === TunnelType.Add)) { @@ -577,7 +573,7 @@ export class TunnelPanel extends ViewPane { this.tunnelViewSelectionContext.set(item); this.tunnelTypeContext.set(item.tunnelType); this.tunnelCloseableContext.set(!!item.closeable); - this.portChangableContextKey.set(isHostAndPort(item.localAddress)); + this.portChangableContextKey.set(!!item.localPort); } else { this.tunnelTypeContext.reset(); this.tunnelViewSelectionContext.reset(); @@ -600,7 +596,7 @@ export class TunnelPanel extends ViewPane { this.tree!.setFocus([node]); this.tunnelTypeContext.set(node.tunnelType); this.tunnelCloseableContext.set(!!node.closeable); - this.portChangableContextKey.set(isHostAndPort(node.localAddress)); + this.portChangableContextKey.set(!!node.localPort); } else { this.tunnelTypeContext.set(TunnelType.Add); this.tunnelCloseableContext.set(false); @@ -661,6 +657,17 @@ export class TunnelPanelDescriptor implements IViewDescriptor { } } +function validationMessage(validationString: string | null): { content: string, severity: Severity } | null { + if (!validationString) { + return null; + } + + return { + content: validationString, + severity: Severity.Error + }; +} + namespace LabelTunnelAction { export const ID = 'remote.tunnel.label'; export const LABEL = nls.localize('remote.tunnel.label', "Set Label"); @@ -737,7 +744,7 @@ namespace ForwardPortAction { } remoteExplorerService.setEditable(undefined, null); }, - validationMessage: validateInput, + validationMessage: (value) => validationMessage(validateInput(value)), placeholder: forwardPrompt }); } @@ -920,7 +927,7 @@ namespace ChangeLocalPortAction { } } }, - validationMessage: validateInput, + validationMessage: (value) => validationMessage(validateInput(value)), placeholder: nls.localize('remote.tunnelsView.changePort', "New local port") }); } @@ -1039,7 +1046,7 @@ MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ id: ChangeLocalPortAction.ID, title: ChangeLocalPortAction.LABEL, }, - when: TunnelTypeContextKey.isEqualTo(TunnelType.Forwarded) + when: ContextKeyExpr.and(TunnelTypeContextKey.isEqualTo(TunnelType.Forwarded), PortChangableContextKey) })); MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ group: '0_manage', diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index eb03c3f76d7..bed450069dc 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -5,23 +5,17 @@ import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; -import { STATUS_BAR_HOST_NAME_BACKGROUND, STATUS_BAR_HOST_NAME_FOREGROUND } from 'vs/workbench/common/theme'; -import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { Disposable } from 'vs/base/common/lifecycle'; import { isMacintosh } from 'vs/base/common/platform'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { MenuId, IMenuService, MenuItemAction, IMenu, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchContributionsExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; import { ILabelService } from 'vs/platform/label/common/label'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { ILogService } from 'vs/platform/log/common/log'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { DialogChannel } from 'vs/platform/dialogs/electron-browser/dialogIpc'; @@ -34,209 +28,10 @@ import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteA import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { RemoteConnectionState, Deprecated_RemoteAuthorityContext, RemoteFileDialogContext } from 'vs/workbench/browser/contextkeys'; +import { RemoteFileDialogContext } from 'vs/workbench/browser/contextkeys'; import { IDownloadService } from 'vs/platform/download/common/download'; import { OpenLocalFileFolderCommand, OpenLocalFileCommand, OpenLocalFolderCommand, SaveLocalFileCommand } from 'vs/workbench/services/dialogs/browser/simpleFileDialog'; - -const WINDOW_ACTIONS_COMMAND_ID = 'workbench.action.remote.showMenu'; -const CLOSE_REMOTE_COMMAND_ID = 'workbench.action.remote.close'; - -export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenchContribution { - - private windowIndicatorEntry: IStatusbarEntryAccessor | undefined; - private windowCommandMenu: IMenu; - private hasWindowActions: boolean = false; - private remoteAuthority: string | undefined; - private connectionState: 'initializing' | 'connected' | 'disconnected' | undefined = undefined; - - constructor( - @IStatusbarService private readonly statusbarService: IStatusbarService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - @ILabelService private readonly labelService: ILabelService, - @IContextKeyService private contextKeyService: IContextKeyService, - @IMenuService private menuService: IMenuService, - @IQuickInputService private readonly quickInputService: IQuickInputService, - @ICommandService private readonly commandService: ICommandService, - @IExtensionService extensionService: IExtensionService, - @IRemoteAgentService remoteAgentService: IRemoteAgentService, - @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, - @IHostService hostService: IHostService - ) { - super(); - - this.windowCommandMenu = this.menuService.createMenu(MenuId.StatusBarWindowIndicatorMenu, this.contextKeyService); - this._register(this.windowCommandMenu); - - const category = nls.localize('remote.category', "Remote"); - const that = this; - registerAction2(class extends Action2 { - constructor() { - super({ - id: WINDOW_ACTIONS_COMMAND_ID, - category, - title: { value: nls.localize('remote.showMenu', "Show Remote Menu"), original: 'Show Remote Menu' }, - f1: true, - }); - } - run = () => that.showIndicatorActions(that.windowCommandMenu); - }); - - this.remoteAuthority = environmentService.configuration.remoteAuthority; - Deprecated_RemoteAuthorityContext.bindTo(this.contextKeyService).set(this.remoteAuthority || ''); - - if (this.remoteAuthority) { - registerAction2(class extends Action2 { - constructor() { - super({ - id: CLOSE_REMOTE_COMMAND_ID, - category, - title: { value: nls.localize('remote.close', "Close Remote Connection"), original: 'Close Remote Connection' }, - f1: true - }); - } - run = () => that.remoteAuthority && hostService.openWindow({ forceReuseWindow: true }); - }); - - // Pending entry until extensions are ready - this.renderWindowIndicator('$(sync~spin) ' + nls.localize('host.open', "Opening Remote..."), undefined, WINDOW_ACTIONS_COMMAND_ID); - this.connectionState = 'initializing'; - RemoteConnectionState.bindTo(this.contextKeyService).set(this.connectionState); - - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '6_close', - command: { - id: CLOSE_REMOTE_COMMAND_ID, - title: nls.localize({ key: 'miCloseRemote', comment: ['&& denotes a mnemonic'] }, "Close Re&&mote Connection") - }, - order: 3.5 - }); - - const connection = remoteAgentService.getConnection(); - if (connection) { - this._register(connection.onDidStateChange((e) => { - switch (e.type) { - case PersistentConnectionEventType.ConnectionLost: - case PersistentConnectionEventType.ReconnectionPermanentFailure: - case PersistentConnectionEventType.ReconnectionRunning: - case PersistentConnectionEventType.ReconnectionWait: - this.setDisconnected(true); - break; - case PersistentConnectionEventType.ConnectionGain: - this.setDisconnected(false); - break; - } - })); - } - } - - extensionService.whenInstalledExtensionsRegistered().then(_ => { - if (this.remoteAuthority) { - this._register(this.labelService.onDidChangeFormatters(e => this.updateWindowIndicator())); - remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority).then(() => this.setDisconnected(false), () => this.setDisconnected(true)); - } - this._register(this.windowCommandMenu.onDidChange(e => this.updateWindowActions())); - this.updateWindowIndicator(); - }); - } - - private setDisconnected(isDisconnected: boolean): void { - const newState = isDisconnected ? 'disconnected' : 'connected'; - if (this.connectionState !== newState) { - this.connectionState = newState; - RemoteConnectionState.bindTo(this.contextKeyService).set(this.connectionState); - Deprecated_RemoteAuthorityContext.bindTo(this.contextKeyService).set(isDisconnected ? `disconnected/${this.remoteAuthority!}` : this.remoteAuthority!); - this.updateWindowIndicator(); - } - } - - private updateWindowIndicator(): void { - const windowActionCommand = (this.remoteAuthority || this.windowCommandMenu.getActions().length) ? WINDOW_ACTIONS_COMMAND_ID : undefined; - if (this.remoteAuthority) { - const hostLabel = this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.remoteAuthority) || this.remoteAuthority; - if (this.connectionState !== 'disconnected') { - this.renderWindowIndicator(`$(remote) ${hostLabel}`, nls.localize('host.tooltip', "Editing on {0}", hostLabel), windowActionCommand); - } else { - this.renderWindowIndicator(`$(alert) ${nls.localize('disconnectedFrom', "Disconnected from")} ${hostLabel}`, nls.localize('host.tooltipDisconnected', "Disconnected from {0}", hostLabel), windowActionCommand); - } - } else { - if (windowActionCommand) { - this.renderWindowIndicator(`$(remote)`, nls.localize('noHost.tooltip', "Open a remote window"), windowActionCommand); - } else if (this.windowIndicatorEntry) { - this.windowIndicatorEntry.dispose(); - this.windowIndicatorEntry = undefined; - } - } - } - - private updateWindowActions() { - const newHasWindowActions = this.windowCommandMenu.getActions().length > 0; - if (newHasWindowActions !== this.hasWindowActions) { - this.hasWindowActions = newHasWindowActions; - this.updateWindowIndicator(); - } - } - - private renderWindowIndicator(text: string, tooltip?: string, command?: string): void { - const properties: IStatusbarEntry = { - backgroundColor: themeColorFromId(STATUS_BAR_HOST_NAME_BACKGROUND), color: themeColorFromId(STATUS_BAR_HOST_NAME_FOREGROUND), text, tooltip, command - }; - if (this.windowIndicatorEntry) { - this.windowIndicatorEntry.update(properties); - } else { - this.windowIndicatorEntry = this.statusbarService.addEntry(properties, 'status.host', nls.localize('status.host', "Remote Host"), StatusbarAlignment.LEFT, Number.MAX_VALUE /* first entry */); - } - } - - private showIndicatorActions(menu: IMenu) { - - const actions = menu.getActions(); - - const items: (IQuickPickItem | IQuickPickSeparator)[] = []; - for (let actionGroup of actions) { - if (items.length) { - items.push({ type: 'separator' }); - } - for (let action of actionGroup[1]) { - if (action instanceof MenuItemAction) { - let label = typeof action.item.title === 'string' ? action.item.title : action.item.title.value; - if (action.item.category) { - const category = typeof action.item.category === 'string' ? action.item.category : action.item.category.value; - label = nls.localize('cat.title', "{0}: {1}", category, label); - } - items.push({ - type: 'item', - id: action.item.id, - label - }); - } - } - } - - if (this.remoteAuthority) { - if (items.length) { - items.push({ type: 'separator' }); - } - items.push({ - type: 'item', - id: CLOSE_REMOTE_COMMAND_ID, - label: nls.localize('closeRemote.title', 'Close Remote Connection') - }); - } - - const quickPick = this.quickInputService.createQuickPick(); - quickPick.items = items; - quickPick.canSelectMany = false; - quickPick.onDidAccept(_ => { - const selectedItems = quickPick.selectedItems; - if (selectedItems.length === 1) { - this.commandService.executeCommand(selectedItems[0].id!); - } - quickPick.hide(); - }); - quickPick.show(); - } -} +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; class RemoteChannelsContribution implements IWorkbenchContribution { @@ -331,7 +126,7 @@ class RemoteTelemetryEnablementUpdater extends Disposable implements IWorkbenchC class RemoteEmptyWorkbenchPresentation extends Disposable implements IWorkbenchContribution { constructor( - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, @IConfigurationService configurationService: IConfigurationService, @ICommandService commandService: ICommandService, @@ -365,7 +160,6 @@ const workbenchContributionsRegistry = Registry.as c instanceof CodeEditorWidget) @@ -1372,7 +1372,7 @@ export class DirtyDiffWorkbenchController extends Disposable implements ext.IWor registerEditorContribution(DirtyDiffController.ID, DirtyDiffController); -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const editorGutterModifiedBackgroundColor = theme.getColor(editorGutterModifiedBackground); if (editorGutterModifiedBackgroundColor) { collector.addRule(` diff --git a/src/vs/workbench/contrib/scm/browser/mainPane.ts b/src/vs/workbench/contrib/scm/browser/mainPane.ts index 60d2d14c614..34208e6a383 100644 --- a/src/vs/workbench/contrib/scm/browser/mainPane.ts +++ b/src/vs/workbench/contrib/scm/browser/mainPane.ts @@ -212,8 +212,8 @@ export class MainPane extends ViewPane { }); this._register(renderer.onDidRenderElement(e => this.list.updateWidth(this.viewModel.repositories.indexOf(e)), null)); - this._register(this.list.onSelectionChange(this.onListSelectionChange, this)); - this._register(this.list.onFocusChange(this.onListFocusChange, this)); + this._register(this.list.onDidChangeSelection(this.onListSelectionChange, this)); + this._register(this.list.onDidChangeFocus(this.onListFocusChange, this)); this._register(this.list.onContextMenu(this.onListContextMenu, this)); this._register(this.viewModel.onDidChangeVisibleRepositories(this.updateListSelection, this)); diff --git a/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css index 9c94a5c49eb..5ea3fd924b9 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css +++ b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css @@ -153,6 +153,10 @@ background-repeat: no-repeat; } +.scm-viewlet .monaco-list-row .resource > .name > .monaco-icon-label > .actions .action-label.codicon { + line-height: 22px; +} + .scm-viewlet .scm-editor { box-sizing: border-box; padding: 5px 12px 5px 16px; diff --git a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts index 9d926605116..0691d524552 100644 --- a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts +++ b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts @@ -24,7 +24,7 @@ import { IAction, IActionViewItem, ActionRunner, Action } from 'vs/base/common/a import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { SCMMenus } from './menus'; import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IThemeService, LIGHT, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IThemeService, LIGHT, registerThemingParticipant, IFileIconTheme } from 'vs/platform/theme/common/themeService'; import { isSCMResource, isSCMResourceGroup, connectPrimaryMenuToInlineActionBar } from './util'; import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; import { WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService'; @@ -45,7 +45,6 @@ import { IViewDescriptor, IViewDescriptorService } from 'vs/workbench/common/vie import { localize } from 'vs/nls'; import { flatten, find } from 'vs/base/common/arrays'; import { memoize } from 'vs/base/common/decorators'; -import { IWorkbenchThemeService, IFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; @@ -152,10 +151,7 @@ interface ResourceTemplate { class RepositoryPaneActionRunner extends ActionRunner { - constructor( - private getSelectedResources: () => (ISCMResource | IResourceNode)[], - private focus: () => void - ) { + constructor(private getSelectedResources: () => (ISCMResource | IResourceNode)[]) { super(); } @@ -169,7 +165,6 @@ class RepositoryPaneActionRunner extends ActionRunner { const actualContext = contextIsSelected ? selection : [context]; const args = flatten(actualContext.map(e => ResourceTree.isResourceNode(e) ? ResourceTree.collect(e) : [e])); await action.run(...args); - this.focus(); } } @@ -182,8 +177,7 @@ class ResourceRenderer implements ICompressibleTreeRenderer ViewModel, private labels: ResourceLabels, private actionViewItemProvider: IActionViewItemProvider, - private getSelectedResources: () => (ISCMResource | IResourceNode)[], - private focus: () => void, + private actionRunner: ActionRunner, private themeService: IThemeService, private menus: SCMMenus ) { } @@ -195,7 +189,7 @@ class ResourceRenderer implements ICompressibleTreeRenderer this.getSelectedResources()); + this._register(actionRunner); + this._register(actionRunner.onDidRun(() => { + if (this.repository.input.visible && this.inputEditor.hasWidgetFocus()) { + return; + } + + this.tree.domFocus(); + })); + const renderers = [ new ResourceGroupRenderer(actionViewItemProvider, this.themeService, this.menus), - new ResourceRenderer(() => this.viewModel, this.listLabels, actionViewItemProvider, () => this.getSelectedResources(), () => this.tree.domFocus(), this.themeService, this.menus) + new ResourceRenderer(() => this.viewModel, this.listLabels, actionViewItemProvider, actionRunner, this.themeService, this.menus) ]; const filter = new SCMTreeFilter(); @@ -1005,10 +1010,10 @@ export class RepositoryPane extends ViewPane { } private pin(): void { - const activeControl = this.editorService.activeControl; + const activeEditorPane = this.editorService.activeEditorPane; - if (activeControl) { - activeControl.group.pinEditor(activeControl.input); + if (activeEditorPane) { + activeEditorPane.group.pinEditor(activeEditorPane.input); } } @@ -1032,11 +1037,14 @@ export class RepositoryPane extends ViewPane { actions = this.menus.getResourceContextActions(element); } + const actionRunner = new RepositoryPaneActionRunner(() => this.getSelectedResources()); + actionRunner.onDidRun(() => this.tree.domFocus()); + this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => actions, getActionsContext: () => element, - actionRunner: new RepositoryPaneActionRunner(() => this.getSelectedResources(), () => this.tree.domFocus()) + actionRunner }); } diff --git a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts new file mode 100644 index 00000000000..8e265b47037 --- /dev/null +++ b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts @@ -0,0 +1,926 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./media/anythingQuickAccess'; +import { IQuickInputButton, IKeyMods, quickPickItemScorerAccessor, QuickPickItemScorerAccessor, IQuickPick, IQuickPickItemWithResource } from 'vs/platform/quickinput/common/quickInput'; +import { IPickerQuickAccessItem, PickerQuickAccessProvider, TriggerAction, FastAndSlowPicks, Picks, PicksWithActive } from 'vs/platform/quickinput/browser/pickerQuickAccess'; +import { prepareQuery, IPreparedQuery, compareItemsByFuzzyScore, scoreItemFuzzy, FuzzyScorerCache } from 'vs/base/common/fuzzyScorer'; +import { IFileQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { getOutOfWorkspaceEditorResources, extractRangeFromFilter, IWorkbenchSearchConfiguration } from 'vs/workbench/contrib/search/common/search'; +import { ISearchService, ISearchComplete } from 'vs/workbench/services/search/common/search'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { untildify } from 'vs/base/common/labels'; +import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; +import { URI } from 'vs/base/common/uri'; +import { toLocalResource, dirname, basenameOrAuthority, isEqual } from 'vs/base/common/resources'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IFileService } from 'vs/platform/files/common/files'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { DisposableStore, IDisposable, toDisposable, MutableDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { localize } from 'vs/nls'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWorkbenchEditorConfiguration, IEditorInput, EditorInput } from 'vs/workbench/common/editor'; +import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { Range, IRange } from 'vs/editor/common/core/range'; +import { ThrottledDelayer } from 'vs/base/common/async'; +import { top } from 'vs/base/common/arrays'; +import { FileQueryCacheState } from 'vs/workbench/contrib/search/common/cacheState'; +import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { IResourceEditorInput, ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { Schemas } from 'vs/base/common/network'; +import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { ResourceMap } from 'vs/base/common/map'; +import { SymbolsQuickAccessProvider } from 'vs/workbench/contrib/search/browser/symbolsQuickAccess'; +import { DefaultQuickAccessFilterValue } from 'vs/platform/quickinput/common/quickAccess'; +import { IWorkbenchQuickAccessConfiguration } from 'vs/workbench/browser/quickaccess'; +import { GotoSymbolQuickAccessProvider } from 'vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { ScrollType, IEditor, ICodeEditorViewState, IDiffEditorViewState } from 'vs/editor/common/editorCommon'; +import { once } from 'vs/base/common/functional'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { stripCodicons } from 'vs/base/common/codicons'; + +interface IAnythingQuickPickItem extends IPickerQuickAccessItem, IQuickPickItemWithResource { } + +interface IEditorSymbolAnythingQuickPickItem extends IAnythingQuickPickItem { + resource: URI; + range: { decoration: IRange, selection: IRange } +} + +function isEditorSymbolQuickPickItem(pick?: IAnythingQuickPickItem): pick is IEditorSymbolAnythingQuickPickItem { + const candidate = pick ? pick as IEditorSymbolAnythingQuickPickItem : undefined; + + return !!candidate && !!candidate.range && !!candidate.resource; +} + +export class AnythingQuickAccessProvider extends PickerQuickAccessProvider { + + static PREFIX = ''; + + private static readonly MAX_RESULTS = 512; + + private static readonly TYPING_SEARCH_DELAY = 200; // this delay accommodates for the user typing a word and then stops typing to start searching + + private readonly pickState = new class { + + picker: IQuickPick | undefined = undefined; + + editorViewState: { + editor: IEditorInput, + group: IEditorGroup, + state: ICodeEditorViewState | IDiffEditorViewState | undefined + } | undefined = undefined; + + scorerCache: FuzzyScorerCache = Object.create(null); + fileQueryCache: FileQueryCacheState | undefined = undefined; + + lastOriginalFilter: string | undefined = undefined; + lastFilter: string | undefined = undefined; + lastRange: IRange | undefined = undefined; + + lastGlobalPicks: PicksWithActive | undefined = undefined; + + isQuickNavigating: boolean | undefined = undefined; + + constructor(private readonly provider: AnythingQuickAccessProvider, private readonly editorService: IEditorService) { } + + set(picker: IQuickPick): void { + + // Picker for this run + this.picker = picker; + once(picker.onDispose)(() => { + if (picker === this.picker) { + this.picker = undefined; // clear the picker when disposed to not keep it in memory for too long + } + }); + + // Caches + const isQuickNavigating = !!picker.quickNavigate; + if (!isQuickNavigating) { + this.fileQueryCache = this.provider.createFileQueryCache(); + this.scorerCache = Object.create(null); + } + + // Other + this.isQuickNavigating = isQuickNavigating; + this.lastOriginalFilter = undefined; + this.lastFilter = undefined; + this.lastRange = undefined; + this.lastGlobalPicks = undefined; + this.editorViewState = undefined; + } + + rememberEditorViewState(): void { + if (this.editorViewState) { + return; // return early if already done + } + + const activeEditorPane = this.editorService.activeEditorPane; + if (activeEditorPane) { + this.editorViewState = { + group: activeEditorPane.group, + editor: activeEditorPane.input, + state: withNullAsUndefined(getCodeEditor(activeEditorPane.getControl())?.saveViewState()) + }; + } + } + }(this, this.editorService); + + get defaultFilterValue(): DefaultQuickAccessFilterValue | undefined { + if (this.configuration.preserveInput) { + return DefaultQuickAccessFilterValue.LAST; + } + + return undefined; + } + + constructor( + @IInstantiationService private readonly instantiationService: IInstantiationService, + @ISearchService private readonly searchService: ISearchService, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @IRemotePathService private readonly remotePathService: IRemotePathService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IFileService private readonly fileService: IFileService, + @ILabelService private readonly labelService: ILabelService, + @IModelService private readonly modelService: IModelService, + @IModeService private readonly modeService: IModeService, + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IEditorService private readonly editorService: IEditorService, + @IHistoryService private readonly historyService: IHistoryService, + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, + @ITextModelService private readonly textModelService: ITextModelService + ) { + super(AnythingQuickAccessProvider.PREFIX, { + canAcceptInBackground: true, + noResultsPick: { + label: localize('noAnythingResults', "No matching results") + } + }); + } + + private get configuration() { + const editorConfig = this.configurationService.getValue().workbench.editor; + const searchConfig = this.configurationService.getValue().search; + const quickAccessConfig = this.configurationService.getValue().workbench.quickOpen; + + return { + openEditorPinned: !editorConfig.enablePreviewFromQuickOpen, + openSideBySideDirection: editorConfig.openSideBySideDirection, + includeSymbols: searchConfig.quickOpen.includeSymbols, + includeHistory: searchConfig.quickOpen.includeHistory, + historyFilterSortOrder: searchConfig.quickOpen.history.filterSortOrder, + shortAutoSaveDelay: this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY, + preserveInput: quickAccessConfig.preserveInput + }; + } + + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // Update the pick state for this run + this.pickState.set(picker); + + // Add editor decorations for active editor symbol picks + const editorDecorationsDisposable = disposables.add(new MutableDisposable()); + disposables.add(picker.onDidChangeActive(() => { + + // Clear old decorations + editorDecorationsDisposable.value = undefined; + + // Add new decoration if editor symbol is active + const [item] = picker.activeItems; + if (isEditorSymbolQuickPickItem(item)) { + editorDecorationsDisposable.value = this.decorateAndRevealSymbolRange(item); + } + })); + + // Restore view state upon cancellation if we changed it + disposables.add(once(token.onCancellationRequested)(() => { + if (this.pickState.editorViewState) { + this.editorService.openEditor( + this.pickState.editorViewState.editor, + { viewState: this.pickState.editorViewState.state, preserveFocus: true /* import to not close the picker as a result */ }, + this.pickState.editorViewState.group + ); + } + })); + + // Start picker + disposables.add(super.provide(picker, token)); + + return disposables; + } + + private decorateAndRevealSymbolRange(pick: IEditorSymbolAnythingQuickPickItem): IDisposable { + const activeEditor = this.editorService.activeEditor; + if (!isEqual(pick.resource, activeEditor?.resource)) { + return Disposable.None; // active editor needs to be for resource + } + + const activeEditorControl = this.editorService.activeTextEditorControl; + if (!activeEditorControl) { + return Disposable.None; // we need a text editor control to decorate and reveal + } + + // we must remember our curret view state to be able to restore + this.pickState.rememberEditorViewState(); + + // Reveal + activeEditorControl.revealRangeInCenter(pick.range.selection, ScrollType.Smooth); + + // Decorate + this.addDecorations(activeEditorControl, pick.range.decoration); + + return toDisposable(() => this.clearDecorations(activeEditorControl)); + } + + protected getPicks(originalFilter: string, disposables: DisposableStore, token: CancellationToken): Picks | Promise> | FastAndSlowPicks | null { + + // Find a suitable range from the pattern looking for ":", "#" or "," + // unless we have the `@` editor symbol character inside the filter + const filterWithRange = extractRangeFromFilter(originalFilter, [GotoSymbolQuickAccessProvider.PREFIX]); + + // Update filter with normalized values + let filter: string; + if (filterWithRange) { + filter = filterWithRange.filter; + } else { + filter = originalFilter; + } + + // Remember as last range + this.pickState.lastRange = filterWithRange?.range; + + // If the original filter value has changed but the normalized + // one has not, we return early with a `null` result indicating + // that the results should preserve because the range information + // (::) does not need to trigger any re-sorting. + if (originalFilter !== this.pickState.lastOriginalFilter && filter === this.pickState.lastFilter) { + return null; + } + + // Remember as last filter + const lastWasFiltering = !!this.pickState.lastOriginalFilter; + this.pickState.lastOriginalFilter = originalFilter; + this.pickState.lastFilter = filter; + + // Remember our pick state before returning new picks + // unless an editor symbol is selected. We can use this + // state to return back to the global pick when the + // user is narrowing back out of editor symbols. + const picks = this.pickState.picker?.items; + const activePick = this.pickState.picker?.activeItems[0]; + if (picks && activePick) { + if (!isEditorSymbolQuickPickItem(activePick)) { + this.pickState.lastGlobalPicks = { + items: picks, + active: activePick + }; + } + } + + // `enableEditorSymbolSearch`: this will enable local editor symbol + // search if the filter value includes `@` character. We only want + // to enable this support though if the user was filtering in the + // picker because this feature depends on an active item in the result + // list to get symbols from. If we would simply trigger editor symbol + // search without prior filtering, you could not paste a file name + // including the `@` character to open it (e.g. /some/file@path) + // refs: https://github.com/microsoft/vscode/issues/93845 + return this.doGetPicks(filter, { enableEditorSymbolSearch: lastWasFiltering }, disposables, token); + } + + private doGetPicks(filter: string, options: { enableEditorSymbolSearch: boolean }, disposables: DisposableStore, token: CancellationToken): Picks | Promise> | FastAndSlowPicks { + const query = prepareQuery(filter); + + // Return early if we have editor symbol picks. We support this by: + // - having a previously active global pick (e.g. a file) + // - the user typing `@` to start the local symbol query + if (options.enableEditorSymbolSearch) { + const editorSymbolPicks = this.getEditorSymbolPicks(query, disposables, token); + if (editorSymbolPicks) { + return editorSymbolPicks; + } + } + + // If we have a known last active editor symbol pick, we try to restore + // the last global pick to support the case of narrowing out from a + // editor symbol search back into the global search + const activePick = this.pickState.picker?.activeItems[0]; + if (isEditorSymbolQuickPickItem(activePick) && this.pickState.lastGlobalPicks) { + return this.pickState.lastGlobalPicks; + } + + // Otherwise return normally with history and file/symbol results + const historyEditorPicks = this.getEditorHistoryPicks(query); + + return { + + // Fast picks: editor history + picks: + (this.pickState.isQuickNavigating || historyEditorPicks.length === 0) ? + historyEditorPicks : + [ + { type: 'separator', label: localize('recentlyOpenedSeparator', "recently opened") }, + ...historyEditorPicks + ], + + // Slow picks: files and symbols + additionalPicks: (async (): Promise> => { + + // Exclude any result that is already present in editor history + const additionalPicksExcludes = new ResourceMap(); + for (const historyEditorPick of historyEditorPicks) { + if (historyEditorPick.resource) { + additionalPicksExcludes.set(historyEditorPick.resource, true); + } + } + + const additionalPicks = await this.getAdditionalPicks(query, additionalPicksExcludes, token); + if (token.isCancellationRequested) { + return []; + } + + return additionalPicks.length > 0 ? [ + { type: 'separator', label: this.configuration.includeSymbols ? localize('fileAndSymbolResultsSeparator', "file and symbol results") : localize('fileResultsSeparator', "file results") }, + ...additionalPicks + ] : []; + })() + }; + } + + private async getAdditionalPicks(query: IPreparedQuery, excludes: ResourceMap, token: CancellationToken): Promise> { + + // Resolve file and symbol picks (if enabled) + const [filePicks, symbolPicks] = await Promise.all([ + this.getFilePicks(query, excludes, token), + this.getWorkspaceSymbolPicks(query, token) + ]); + + if (token.isCancellationRequested) { + return []; + } + + // Perform sorting (top results by score) + const sortedAnythingPicks = top( + [...filePicks, ...symbolPicks], + (anyPickA, anyPickB) => compareItemsByFuzzyScore(anyPickA, anyPickB, query, true, quickPickItemScorerAccessor, this.pickState.scorerCache), + AnythingQuickAccessProvider.MAX_RESULTS + ); + + // Perform filtering + const filteredAnythingPicks: IAnythingQuickPickItem[] = []; + for (const anythingPick of sortedAnythingPicks) { + + // Always preserve any existing highlights (e.g. from workspace symbols) + if (anythingPick.highlights) { + filteredAnythingPicks.push(anythingPick); + } + + // Otherwise, do the scoring and matching here + else { + const { score, labelMatch, descriptionMatch } = scoreItemFuzzy(anythingPick, query, true, quickPickItemScorerAccessor, this.pickState.scorerCache); + if (!score) { + continue; + } + + anythingPick.highlights = { + label: labelMatch, + description: descriptionMatch + }; + + filteredAnythingPicks.push(anythingPick); + } + } + + return filteredAnythingPicks; + } + + + //#region Editor History + + private readonly labelOnlyEditorHistoryPickAccessor = new QuickPickItemScorerAccessor({ skipDescription: true }); + + private getEditorHistoryPicks(query: IPreparedQuery): Array { + const configuration = this.configuration; + + // Just return all history entries if not searching + if (!query.normalized) { + return this.historyService.getHistory().map(editor => this.createAnythingPick(editor, configuration)); + } + + if (!this.configuration.includeHistory) { + return []; // disabled when searching + } + + // Perform filtering + const editorHistoryScorerAccessor = query.containsPathSeparator ? quickPickItemScorerAccessor : this.labelOnlyEditorHistoryPickAccessor; // Only match on label of the editor unless the search includes path separators + const editorHistoryPicks: Array = []; + for (const editor of this.historyService.getHistory()) { + const resource = editor.resource; + if (!resource || (!this.fileService.canHandleResource(resource) && resource.scheme !== Schemas.untitled)) { + continue; // exclude editors without file resource if we are searching by pattern + } + + const editorHistoryPick = this.createAnythingPick(editor, configuration); + + const { score, labelMatch, descriptionMatch } = scoreItemFuzzy(editorHistoryPick, query, false, editorHistoryScorerAccessor, this.pickState.scorerCache); + if (!score) { + continue; // exclude editors not matching query + } + + editorHistoryPick.highlights = { + label: labelMatch, + description: descriptionMatch + }; + + editorHistoryPicks.push(editorHistoryPick); + } + + // Return without sorting if settings tell to sort by recency + if (this.configuration.historyFilterSortOrder === 'recency') { + return editorHistoryPicks; + } + + // Perform sorting + return editorHistoryPicks.sort((editorA, editorB) => compareItemsByFuzzyScore(editorA, editorB, query, false, editorHistoryScorerAccessor, this.pickState.scorerCache)); + } + + //#endregion + + + //#region File Search + + private readonly fileQueryDelayer = this._register(new ThrottledDelayer(AnythingQuickAccessProvider.TYPING_SEARCH_DELAY)); + + private readonly fileQueryBuilder = this.instantiationService.createInstance(QueryBuilder); + + private createFileQueryCache(): FileQueryCacheState { + return new FileQueryCacheState( + cacheKey => this.fileQueryBuilder.file(this.contextService.getWorkspace().folders, this.getFileQueryOptions({ cacheKey })), + query => this.searchService.fileSearch(query), + cacheKey => this.searchService.clearCache(cacheKey), + this.pickState.fileQueryCache + ).load(); + } + + private async getFilePicks(query: IPreparedQuery, excludes: ResourceMap, token: CancellationToken): Promise> { + if (!query.normalized) { + return []; + } + + // Absolute path result + const absolutePathResult = await this.getAbsolutePathFileResult(query, token); + if (token.isCancellationRequested) { + return []; + } + + // Use absolute path result as only results if present + let fileMatches: Array; + if (absolutePathResult) { + fileMatches = [absolutePathResult]; + } + + // Otherwise run the file search (with a delayer if cache is not ready yet) + else { + if (this.pickState.fileQueryCache?.isLoaded) { + fileMatches = await this.doFileSearch(query, token); + } else { + fileMatches = await this.fileQueryDelayer.trigger(async () => { + if (token.isCancellationRequested) { + return []; + } + + return this.doFileSearch(query, token); + }); + } + } + + if (token.isCancellationRequested) { + return []; + } + + // Filter excludes & convert to picks + const configuration = this.configuration; + return fileMatches + .filter(resource => !excludes.has(resource)) + .map(resource => this.createAnythingPick(resource, configuration)); + } + + private async doFileSearch(query: IPreparedQuery, token: CancellationToken): Promise { + const [fileSearchResults, relativePathFileResults] = await Promise.all([ + + // File search: this is a search over all files of the workspace using the provided pattern + this.getFileSearchResults(query, token), + + // Relative path search: we also want to consider results that match files inside the workspace + // by looking for relative paths that the user typed as query. This allows to return even excluded + // results into the picker if found (e.g. helps for opening compilation results that are otherwise + // excluded) + this.getRelativePathFileResults(query, token) + ]); + + if (token.isCancellationRequested) { + return []; + } + + // Return quickly if no relative results are present + if (!relativePathFileResults) { + return fileSearchResults; + } + + // Otherwise, make sure to filter relative path results from + // the search results to prevent duplicates + const relativePathFileResultsMap = new ResourceMap(); + for (const relativePathFileResult of relativePathFileResults) { + relativePathFileResultsMap.set(relativePathFileResult, true); + } + + return [ + ...fileSearchResults.filter(result => !relativePathFileResultsMap.has(result)), + ...relativePathFileResults + ]; + } + + private async getFileSearchResults(query: IPreparedQuery, token: CancellationToken): Promise { + + // filePattern for search depends on the number of queries in input: + // - with multiple: only take the first one and let the filter later drop non-matching results + // - with single: just take the original in full + // + // This enables to e.g. search for "someFile someFolder" by only returning + // search results for "someFile" and not both that would normally not match. + // + let filePattern = ''; + if (query.values && query.values.length > 1) { + filePattern = query.values[0].original; + } else { + filePattern = query.original; + } + + const fileSearchResults = await this.doGetFileSearchResults(filePattern, token); + if (token.isCancellationRequested) { + return []; + } + + // If we detect that the search limit has been hit and we have a query + // that was composed of multiple inputs where we only took the first part + // we run another search with the full original query included to make + // sure we are including all possible results that could match. + if (fileSearchResults.limitHit && query.values && query.values.length > 1) { + const additionalFileSearchResults = await this.doGetFileSearchResults(query.original, token); + if (token.isCancellationRequested) { + return []; + } + + // Remember which result we already covered + const existingFileSearchResultsMap = new ResourceMap(); + for (const fileSearchResult of fileSearchResults.results) { + existingFileSearchResultsMap.set(fileSearchResult.resource, true); + } + + // Add all additional results to the original set for inclusion + for (const additionalFileSearchResult of additionalFileSearchResults.results) { + if (!existingFileSearchResultsMap.has(additionalFileSearchResult.resource)) { + fileSearchResults.results.push(additionalFileSearchResult); + } + } + } + + return fileSearchResults.results.map(result => result.resource); + } + + private doGetFileSearchResults(filePattern: string, token: CancellationToken): Promise { + return this.searchService.fileSearch( + this.fileQueryBuilder.file( + this.contextService.getWorkspace().folders, + this.getFileQueryOptions({ + filePattern, + cacheKey: this.pickState.fileQueryCache?.cacheKey, + maxResults: AnythingQuickAccessProvider.MAX_RESULTS + }) + ), token); + } + + private getFileQueryOptions(input: { filePattern?: string, cacheKey?: string, maxResults?: number }): IFileQueryBuilderOptions { + return { + _reason: 'openFileHandler', // used for telemetry - do not change + extraFileResources: this.instantiationService.invokeFunction(getOutOfWorkspaceEditorResources), + filePattern: input.filePattern || '', + cacheKey: input.cacheKey, + maxResults: input.maxResults || 0, + sortByScore: true + }; + } + + private async getAbsolutePathFileResult(query: IPreparedQuery, token: CancellationToken): Promise { + if (!query.containsPathSeparator) { + return; + } + + const detildifiedQuery = untildify(query.original, (await this.remotePathService.userHome).path); + if (token.isCancellationRequested) { + return; + } + + const isAbsolutePathQuery = (await this.remotePathService.path).isAbsolute(detildifiedQuery); + if (token.isCancellationRequested) { + return; + } + + if (isAbsolutePathQuery) { + const resource = toLocalResource( + await this.remotePathService.fileURI(detildifiedQuery), + this.environmentService.configuration.remoteAuthority + ); + + if (token.isCancellationRequested) { + return; + } + + try { + if ((await this.fileService.resolve(resource)).isFile) { + return resource; + } + } catch (error) { + // ignore if file does not exist + } + } + + return; + } + + private async getRelativePathFileResults(query: IPreparedQuery, token: CancellationToken): Promise { + if (!query.containsPathSeparator) { + return; + } + + // Convert relative paths to absolute paths over all folders of the workspace + // and return them as results if the absolute paths exist + const isAbsolutePathQuery = (await this.remotePathService.path).isAbsolute(query.original); + if (!isAbsolutePathQuery) { + const resources: URI[] = []; + for (const folder of this.contextService.getWorkspace().folders) { + if (token.isCancellationRequested) { + break; + } + + const resource = toLocalResource( + folder.toResource(query.original), + this.environmentService.configuration.remoteAuthority + ); + + try { + if ((await this.fileService.resolve(resource)).isFile) { + resources.push(resource); + } + } catch (error) { + // ignore if file does not exist + } + } + + return resources; + } + + return; + } + + //#endregion + + + //#region Workspace Symbols (if enabled) + + private workspaceSymbolsQuickAccess = this._register(this.instantiationService.createInstance(SymbolsQuickAccessProvider)); + + private async getWorkspaceSymbolPicks(query: IPreparedQuery, token: CancellationToken): Promise> { + const configuration = this.configuration; + if ( + !query.normalized || // we need a value for search for + !configuration.includeSymbols || // we need to enable symbols in search + this.pickState.lastRange // a range is an indicator for just searching for files + ) { + return []; + } + + // Delegate to the existing symbols quick access + // but skip local results and also do not score + return this.workspaceSymbolsQuickAccess.getSymbolPicks(query.original, { + skipLocal: true, + skipSorting: true, + delay: AnythingQuickAccessProvider.TYPING_SEARCH_DELAY + }, token); + } + + //#endregion + + + //#region Editor Symbols (if narrowing down into a global pick via `@`) + + private readonly editorSymbolsQuickAccess = this.instantiationService.createInstance(GotoSymbolQuickAccessProvider); + + private getEditorSymbolPicks(query: IPreparedQuery, disposables: DisposableStore, token: CancellationToken): Promise> | null { + const filterSegments = query.original.split(GotoSymbolQuickAccessProvider.PREFIX); + const filter = filterSegments.length > 1 ? filterSegments[filterSegments.length - 1].trim() : undefined; + if (typeof filter !== 'string') { + return null; // we need to be searched for editor symbols via `@` + } + + const activeGlobalPick = this.pickState.lastGlobalPicks?.active; + if (!activeGlobalPick) { + return null; // we need an active global pick to find symbols for + } + + const activeGlobalResource = activeGlobalPick.resource; + if (!activeGlobalResource || (!this.fileService.canHandleResource(activeGlobalResource) && activeGlobalResource.scheme !== Schemas.untitled)) { + return null; // we need a resource that we can resolve + } + + if (activeGlobalPick.label.includes(GotoSymbolQuickAccessProvider.PREFIX) || activeGlobalPick.description?.includes(GotoSymbolQuickAccessProvider.PREFIX)) { + if (filterSegments.length < 3) { + return null; // require at least 2 `@` if our active pick contains `@` in label or description + } + } + + return this.doGetEditorSymbolPicks(activeGlobalPick, activeGlobalResource, filter, disposables, token); + } + + private async doGetEditorSymbolPicks(activeGlobalPick: IAnythingQuickPickItem, activeGlobalResource: URI, filter: string, disposables: DisposableStore, token: CancellationToken): Promise> { + + // Bring the editor to front to review symbols to go to + try { + + // we must remember our curret view state to be able to restore + this.pickState.rememberEditorViewState(); + + // open it + await this.editorService.openEditor({ + resource: activeGlobalResource, + options: { preserveFocus: true, revealIfOpened: true, ignoreError: true } + }); + } catch (error) { + return []; // return if resource cannot be opened + } + + if (token.isCancellationRequested) { + return []; + } + + // Obtain model from resource + let model = this.modelService.getModel(activeGlobalResource); + if (!model) { + try { + const modelReference = disposables.add(await this.textModelService.createModelReference(activeGlobalResource)); + if (token.isCancellationRequested) { + return []; + } + + model = modelReference.object.textEditorModel; + } catch (error) { + return []; // return if model cannot be resolved + } + } + + // Ask provider for editor symbols + const editorSymbolPicks = (await this.editorSymbolsQuickAccess.getSymbolPicks(model, filter, { extraContainerLabel: stripCodicons(activeGlobalPick.label) }, disposables, token)); + if (token.isCancellationRequested) { + return []; + } + + return editorSymbolPicks.map(editorSymbolPick => { + + // Preserve separators + if (editorSymbolPick.type === 'separator') { + return editorSymbolPick; + } + + // Convert editor symbols to anything pick + return { + ...editorSymbolPick, + resource: activeGlobalResource, + description: editorSymbolPick.description, + trigger: (buttonIndex, keyMods) => { + this.openAnything(activeGlobalResource, { keyMods, range: editorSymbolPick.range?.selection, forceOpenSideBySide: true }); + + return TriggerAction.CLOSE_PICKER; + }, + accept: (keyMods, event) => this.openAnything(activeGlobalResource, { keyMods, range: editorSymbolPick.range?.selection, preserveFocus: event.inBackground }) + }; + }); + } + + addDecorations(editor: IEditor, range: IRange): void { + this.editorSymbolsQuickAccess.addDecorations(editor, range); + } + + clearDecorations(editor: IEditor): void { + this.editorSymbolsQuickAccess.clearDecorations(editor); + } + + //#endregion + + + //#region Helpers + + private createAnythingPick(resourceOrEditor: URI | IEditorInput | IResourceEditorInput, configuration: { shortAutoSaveDelay: boolean, openSideBySideDirection: 'right' | 'down' | undefined }): IAnythingQuickPickItem { + const isEditorHistoryEntry = !URI.isUri(resourceOrEditor); + + let resource: URI | undefined; + let label: string; + let description: string | undefined = undefined; + let isDirty: boolean | undefined = undefined; + + if (resourceOrEditor instanceof EditorInput) { + resource = resourceOrEditor.resource; + label = resourceOrEditor.getName(); + description = resourceOrEditor.getDescription(); + isDirty = resourceOrEditor.isDirty() && !resourceOrEditor.isSaving(); + } else { + resource = URI.isUri(resourceOrEditor) ? resourceOrEditor : (resourceOrEditor as IResourceEditorInput).resource; + label = basenameOrAuthority(resource); + description = this.labelService.getUriLabel(dirname(resource), { relative: true }); + isDirty = this.workingCopyService.isDirty(resource) && !configuration.shortAutoSaveDelay; + } + + return { + resource, + label, + ariaLabel: isDirty ? localize('filePickAriaLabelDirty', "{0}, dirty", label) : label, + description, + iconClasses: getIconClasses(this.modelService, this.modeService, resource), + buttons: (() => { + const openSideBySideDirection = configuration.openSideBySideDirection; + const buttons: IQuickInputButton[] = []; + + // Open to side / below + buttons.push({ + iconClass: openSideBySideDirection === 'right' ? 'codicon-split-horizontal' : 'codicon-split-vertical', + tooltip: openSideBySideDirection === 'right' ? localize('openToSide', "Open to the Side") : localize('openToBottom', "Open to the Bottom") + }); + + // Remove from History (unless quick navigating) + if (!this.pickState.isQuickNavigating && isEditorHistoryEntry) { + buttons.push({ + iconClass: isDirty ? 'dirty-anything codicon-circle-filled' : 'codicon-close', + tooltip: localize('closeEditor', "Remove from Recently Opened"), + alwaysVisible: isDirty + }); + } + + return buttons; + })(), + trigger: (buttonIndex, keyMods) => { + switch (buttonIndex) { + + // Open to side / below + case 0: + this.openAnything(resourceOrEditor, { keyMods, range: this.pickState.lastRange, forceOpenSideBySide: true }); + + return TriggerAction.CLOSE_PICKER; + + // Remove from History + case 1: + if (!URI.isUri(resourceOrEditor)) { + this.historyService.remove(resourceOrEditor); + + return TriggerAction.REMOVE_ITEM; + } + } + + return TriggerAction.NO_ACTION; + }, + accept: (keyMods, event) => this.openAnything(resourceOrEditor, { keyMods, range: this.pickState.lastRange, preserveFocus: event.inBackground }) + }; + } + + private async openAnything(resourceOrEditor: URI | IEditorInput | IResourceEditorInput, options: { keyMods?: IKeyMods, preserveFocus?: boolean, range?: IRange, forceOpenSideBySide?: boolean }): Promise { + const editorOptions: ITextEditorOptions = { + preserveFocus: options.preserveFocus, + pinned: options.keyMods?.alt || this.configuration.openEditorPinned, + selection: options.range ? Range.collapseToStart(options.range) : undefined + }; + + const targetGroup = options.keyMods?.ctrlCmd || options.forceOpenSideBySide ? SIDE_GROUP : ACTIVE_GROUP; + + if (resourceOrEditor instanceof EditorInput) { + await this.editorService.openEditor(resourceOrEditor, editorOptions); + } else { + await this.editorService.openEditor({ + resource: URI.isUri(resourceOrEditor) ? resourceOrEditor : resourceOrEditor.resource, + options: editorOptions + }, targetGroup); + } + } + + //#endregion +} diff --git a/src/vs/workbench/contrib/search/browser/media/anythingQuickAccess.css b/src/vs/workbench/contrib/search/browser/media/anythingQuickAccess.css new file mode 100644 index 00000000000..55fe37ea1e7 --- /dev/null +++ b/src/vs/workbench/contrib/search/browser/media/anythingQuickAccess.css @@ -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. + *--------------------------------------------------------------------------------------------*/ + +.quick-input-list .quick-input-list-entry.has-actions:hover .quick-input-list-entry-action-bar .action-label.dirty-anything::before { + content: "\ea76"; /* Close icon flips between black dot and "X" for dirty open editors */ +} diff --git a/src/vs/workbench/contrib/search/browser/media/searchview.css b/src/vs/workbench/contrib/search/browser/media/searchview.css index c8f01bd98a3..69bfdab0284 100644 --- a/src/vs/workbench/contrib/search/browser/media/searchview.css +++ b/src/vs/workbench/contrib/search/browser/media/searchview.css @@ -6,6 +6,7 @@ .search-view .search-widgets-container { margin: 0px 12px 0 2px; padding-top: 6px; + padding-bottom: 6px; } .search-view .search-widget .toggle-replace-button { @@ -132,7 +133,7 @@ } .search-view .query-details.more .file-types:last-child { - padding-bottom: 10px; + padding-bottom: 4px; } .search-view .query-details.more h4 { @@ -257,7 +258,7 @@ } /* Adjusts spacing in high contrast mode so that actions are vertically centered */ -.hc-black .monaco-list .monaco-list-row .monaco-action-bar .action-label { +.hc-black .search-view .monaco-list .monaco-list-row .monaco-action-bar .action-label { margin-top: 2px; } diff --git a/src/vs/workbench/contrib/search/browser/openAnythingHandler.ts b/src/vs/workbench/contrib/search/browser/openAnythingHandler.ts deleted file mode 100644 index 7b3f04acbd6..00000000000 --- a/src/vs/workbench/contrib/search/browser/openAnythingHandler.ts +++ /dev/null @@ -1,249 +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 arrays from 'vs/base/common/arrays'; -import * as nls from 'vs/nls'; -import { ThrottledDelayer } from 'vs/base/common/async'; -import * as types from 'vs/base/common/types'; -import { IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen'; -import { QuickOpenEntry, QuickOpenModel, QuickOpenItemAccessor } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { QuickOpenHandler } from 'vs/workbench/browser/quickopen'; -import { FileEntry, OpenFileHandler, FileQuickOpenModel } from 'vs/workbench/contrib/search/browser/openFileHandler'; -import * as openSymbolHandler from 'vs/workbench/contrib/search/browser/openSymbolHandler'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IWorkbenchSearchConfiguration } from 'vs/workbench/contrib/search/common/search'; -import { IRange } from 'vs/editor/common/core/range'; -import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { isPromiseCanceledError } from 'vs/base/common/errors'; -import { CancellationToken } from 'vs/base/common/cancellation'; - -export import OpenSymbolHandler = openSymbolHandler.OpenSymbolHandler; // OpenSymbolHandler is used from an extension and must be in the main bundle file so it can load - -interface ISearchWithRange { - search: string; - range: IRange; -} - -export class OpenAnythingHandler extends QuickOpenHandler { - - static readonly ID = 'workbench.picker.anything'; - - private static readonly LINE_COLON_PATTERN = /[#:\(](\d*)([#:,](\d*))?\)?\s*$/; - - private static readonly TYPING_SEARCH_DELAY = 200; // This delay accommodates for the user typing a word and then stops typing to start searching - - private static readonly MAX_DISPLAYED_RESULTS = 512; - - private openSymbolHandler: OpenSymbolHandler; - private openFileHandler: OpenFileHandler; - private searchDelayer: ThrottledDelayer; - private isClosed: boolean | undefined; - private scorerCache: ScorerCache; - private includeSymbols: boolean | undefined; - - constructor( - @INotificationService private readonly notificationService: INotificationService, - @IInstantiationService instantiationService: IInstantiationService, - @IConfigurationService private readonly configurationService: IConfigurationService - ) { - super(); - - this.scorerCache = Object.create(null); - this.searchDelayer = new ThrottledDelayer(OpenAnythingHandler.TYPING_SEARCH_DELAY); - - this.openSymbolHandler = instantiationService.createInstance(OpenSymbolHandler); - this.openFileHandler = instantiationService.createInstance(OpenFileHandler); - - this.updateHandlers(this.configurationService.getValue()); - - this.registerListeners(); - } - - private registerListeners(): void { - this.configurationService.onDidChangeConfiguration(e => this.updateHandlers(this.configurationService.getValue())); - } - - private updateHandlers(configuration: IWorkbenchSearchConfiguration): void { - this.includeSymbols = configuration?.search?.quickOpen?.includeSymbols; - - // Files - this.openFileHandler.setOptions({ - forceUseIcons: this.includeSymbols // only need icons for file results if we mix with symbol results - }); - - // Symbols - this.openSymbolHandler.setOptions({ - skipDelay: true, // we have our own delay - skipLocalSymbols: true, // we only want global symbols - skipSorting: true // we sort combined with file results - }); - } - - getResults(searchValue: string, token: CancellationToken): Promise { - this.isClosed = false; // Treat this call as the handler being in use - - // Find a suitable range from the pattern looking for ":" and "#" - const searchWithRange = this.extractRange(searchValue); - if (searchWithRange) { - searchValue = searchWithRange.search; // ignore range portion in query - } - - // Prepare search for scoring - const query = prepareQuery(searchValue); - if (!query.value) { - return Promise.resolve(new QuickOpenModel()); // Respond directly to empty search - } - - // The throttler needs a factory for its promises - const resultsPromise = (): Promise => { - const resultPromises: Promise[] = []; - - // File Results - const filePromise = this.openFileHandler.getResults(query.original, token, OpenAnythingHandler.MAX_DISPLAYED_RESULTS); - resultPromises.push(filePromise); - - // Symbol Results (unless disabled or a range or absolute path is specified) - if (this.includeSymbols && !searchWithRange) { - resultPromises.push(this.openSymbolHandler.getResults(query.original, token)); - } - - // Join and sort unified - return Promise.all(resultPromises).then(results => { - - // If the quick open widget has been closed meanwhile, ignore the result - if (this.isClosed || token.isCancellationRequested) { - return Promise.resolve(new QuickOpenModel()); - } - - // Combine results. - const mergedResults: QuickOpenEntry[] = ([] as QuickOpenEntry[]).concat(...results.map(r => r.entries)); - - // Sort - const compare = (elementA: QuickOpenEntry, elementB: QuickOpenEntry) => compareItemsByScore(elementA, elementB, query, true, QuickOpenItemAccessor, this.scorerCache); - const viewResults = arrays.top(mergedResults, compare, OpenAnythingHandler.MAX_DISPLAYED_RESULTS); - - // Apply range and highlights to file entries - viewResults.forEach(entry => { - if (entry instanceof FileEntry) { - entry.setRange(searchWithRange ? searchWithRange.range : null); - - const itemScore = scoreItem(entry, query, true, QuickOpenItemAccessor, this.scorerCache); - entry.setHighlights(itemScore.labelMatch || [], itemScore.descriptionMatch); - } - }); - - return Promise.resolve(new QuickOpenModel(viewResults)); - }, error => { - if (!isPromiseCanceledError(error)) { - let message: Error | string; - if (error.message) { - message = error.message.replace(/[\*_\[\]]/g, '\\$&'); - } else { - message = error; - } - - this.notificationService.error(message); - } - - return null; - }); - }; - - // Trigger through delayer to prevent accumulation while the user is typing (except when expecting results to come from cache) - return this.hasShortResponseTime() ? resultsPromise() : this.searchDelayer.trigger(resultsPromise, OpenAnythingHandler.TYPING_SEARCH_DELAY); - } - - hasShortResponseTime(): boolean { - if (!this.includeSymbols) { - return this.openFileHandler.hasShortResponseTime(); - } - - return this.openFileHandler.hasShortResponseTime() && this.openSymbolHandler.hasShortResponseTime(); - } - - private extractRange(value: string): ISearchWithRange | null { - if (!value) { - return null; - } - - let range: IRange | null = null; - - // Find Line/Column number from search value using RegExp - const patternMatch = OpenAnythingHandler.LINE_COLON_PATTERN.exec(value); - if (patternMatch && patternMatch.length > 1) { - const startLineNumber = parseInt(patternMatch[1], 10); - - // Line Number - if (types.isNumber(startLineNumber)) { - range = { - startLineNumber: startLineNumber, - startColumn: 1, - endLineNumber: startLineNumber, - endColumn: 1 - }; - - // Column Number - if (patternMatch.length > 3) { - const startColumn = parseInt(patternMatch[3], 10); - if (types.isNumber(startColumn)) { - range = { - startLineNumber: range.startLineNumber, - startColumn: startColumn, - endLineNumber: range.endLineNumber, - endColumn: startColumn - }; - } - } - } - - // User has typed "something:" or "something#" without a line number, in this case treat as start of file - else if (patternMatch[1] === '') { - range = { - startLineNumber: 1, - startColumn: 1, - endLineNumber: 1, - endColumn: 1 - }; - } - } - - if (patternMatch && range) { - return { - search: value.substr(0, patternMatch.index), // clear range suffix from search value - range: range - }; - } - - return null; - } - - getGroupLabel(): string { - return this.includeSymbols ? nls.localize('fileAndTypeResults', "file and symbol results") : nls.localize('fileResults', "file results"); - } - - getAutoFocus(searchValue: string): IAutoFocus { - return { - autoFocusFirstEntry: true - }; - } - - onOpen(): void { - this.openSymbolHandler.onOpen(); - this.openFileHandler.onOpen(); - } - - onClose(canceled: boolean): void { - this.isClosed = true; - - // Clear Cache - this.scorerCache = Object.create(null); - - // Propagate - this.openSymbolHandler.onClose(canceled); - this.openFileHandler.onClose(canceled); - } -} diff --git a/src/vs/workbench/contrib/search/browser/openFileHandler.ts b/src/vs/workbench/contrib/search/browser/openFileHandler.ts deleted file mode 100644 index e3c5e565472..00000000000 --- a/src/vs/workbench/contrib/search/browser/openFileHandler.ts +++ /dev/null @@ -1,342 +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 { IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import * as errors from 'vs/base/common/errors'; -import { defaultGenerator } from 'vs/base/common/idGenerator'; -import { untildify } from 'vs/base/common/labels'; -import * as objects from 'vs/base/common/objects'; -import { basename, dirname, toLocalResource } from 'vs/base/common/resources'; -import { URI } from 'vs/base/common/uri'; -import { QuickOpenEntry, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen'; -import { IPreparedQuery, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; -import { IRange } from 'vs/editor/common/core/range'; -import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import * as nls from 'vs/nls'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; -import { IFileService } from 'vs/platform/files/common/files'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ILabelService } from 'vs/platform/label/common/label'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { EditorQuickOpenEntry, QuickOpenHandler } from 'vs/workbench/browser/quickopen'; -import { EditorInput, IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; -import { IFileQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; -import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/common/search'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; -import { IFileQuery, IFileSearchStats, ISearchComplete, ISearchService } from 'vs/workbench/services/search/common/search'; -import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; - -export class FileQuickOpenModel extends QuickOpenModel { - - constructor(entries: QuickOpenEntry[], stats?: IFileSearchStats) { - super(entries); - } -} - -export class FileEntry extends EditorQuickOpenEntry { - private range: IRange | null = null; - - constructor( - private resource: URI, - private name: string, - private description: string, - private icon: string | undefined, - @IEditorService editorService: IEditorService, - @IModeService private readonly modeService: IModeService, - @IModelService private readonly modelService: IModelService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IWorkspaceContextService contextService: IWorkspaceContextService - ) { - super(editorService); - } - - getLabel(): string { - return this.name; - } - - getLabelOptions(): IIconLabelValueOptions { - return { - extraClasses: getIconClasses(this.modelService, this.modeService, this.resource) - }; - } - - getAriaLabel(): string { - return nls.localize('entryAriaLabel', "{0}, file picker", this.getLabel()); - } - - getDescription(): string { - return this.description; - } - - getIcon(): string | undefined { - return this.icon; - } - - getResource(): URI { - return this.resource; - } - - setRange(range: IRange | null): void { - this.range = range; - } - - mergeWithEditorHistory(): boolean { - return true; - } - - getInput(): IResourceInput | EditorInput { - const input: IResourceInput = { - resource: this.resource, - options: { - pinned: !this.configurationService.getValue().workbench.editor.enablePreviewFromQuickOpen, - selection: this.range ? this.range : undefined - } - }; - - return input; - } -} - -export interface IOpenFileOptions { - forceUseIcons: boolean; -} - -export class OpenFileHandler extends QuickOpenHandler { - private options: IOpenFileOptions | undefined; - private queryBuilder: QueryBuilder; - private cacheState: CacheState | undefined; - - constructor( - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @ISearchService private readonly searchService: ISearchService, - @IRemotePathService private readonly remotePathService: IRemotePathService, - @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, - @IFileService private readonly fileService: IFileService, - @ILabelService private readonly labelService: ILabelService - ) { - super(); - - this.queryBuilder = this.instantiationService.createInstance(QueryBuilder); - } - - setOptions(options: IOpenFileOptions) { - this.options = options; - } - - getResults(searchValue: string, token: CancellationToken, maxSortedResults?: number): Promise { - const query = prepareQuery(searchValue); - - // Respond directly to empty search - if (!query.value) { - return Promise.resolve(new FileQuickOpenModel([])); - } - - // Do find results - return this.doFindResults(query, token, this.cacheState ? this.cacheState.cacheKey : undefined, maxSortedResults); - } - - private async doFindResults(query: IPreparedQuery, token: CancellationToken, cacheKey?: string, maxSortedResults?: number): Promise { - const queryOptions = this.doResolveQueryOptions(query, cacheKey, maxSortedResults); - - let iconClass: string | undefined = undefined; - if (this.options && this.options.forceUseIcons && !this.themeService.getFileIconTheme()) { - iconClass = 'file'; // only use a generic file icon if we are forced to use an icon and have no icon theme set otherwise - } - - let complete: ISearchComplete | undefined = undefined; - - const result = await this.getAbsolutePathResult(query); - if (token.isCancellationRequested) { - complete = { results: [] }; - } - - // If the original search value is an existing file on disk, return it immediately and bypass the search service - else if (result) { - complete = { results: [{ resource: result }] }; - } - - else { - complete = await this.searchService.fileSearch(this.queryBuilder.file(this.contextService.getWorkspace().folders.map(folder => folder.uri), queryOptions), token); - } - - const results: QuickOpenEntry[] = []; - - if (!token.isCancellationRequested) { - for (const fileMatch of complete.results) { - const label = basename(fileMatch.resource); - const description = this.labelService.getUriLabel(dirname(fileMatch.resource), { relative: true }); - - results.push(this.instantiationService.createInstance(FileEntry, fileMatch.resource, label, description, iconClass)); - } - } - - return new FileQuickOpenModel(results, complete.stats); - } - - private async getAbsolutePathResult(query: IPreparedQuery): Promise { - const detildifiedQuery = untildify(query.original, (await this.remotePathService.userHome).path); - if ((await this.remotePathService.path).isAbsolute(detildifiedQuery)) { - const resource = toLocalResource( - await this.remotePathService.fileURI(detildifiedQuery), - this.workbenchEnvironmentService.configuration.remoteAuthority - ); - - try { - const stat = await this.fileService.resolve(resource); - return stat.isDirectory ? undefined : resource; - } catch (error) { - // ignore - } - } - - return undefined; - } - - private doResolveQueryOptions(query: IPreparedQuery, cacheKey?: string, maxSortedResults?: number): IFileQueryBuilderOptions { - const queryOptions: IFileQueryBuilderOptions = { - _reason: 'openFileHandler', - extraFileResources: this.instantiationService.invokeFunction(getOutOfWorkspaceEditorResources), - filePattern: query.original, - cacheKey - }; - - if (typeof maxSortedResults === 'number') { - queryOptions.maxResults = maxSortedResults; - queryOptions.sortByScore = true; - } - - return queryOptions; - } - - hasShortResponseTime(): boolean { - return this.isCacheLoaded; - } - - onOpen(): void { - this.cacheState = new CacheState(cacheKey => this.cacheQuery(cacheKey), query => this.searchService.fileSearch(query), cacheKey => this.searchService.clearCache(cacheKey), this.cacheState); - this.cacheState.load(); - } - - private cacheQuery(cacheKey: string): IFileQuery { - const options: IFileQueryBuilderOptions = { - _reason: 'openFileHandler', - extraFileResources: this.instantiationService.invokeFunction(getOutOfWorkspaceEditorResources), - filePattern: '', - cacheKey: cacheKey, - maxResults: 0, - sortByScore: true, - }; - - const folderResources = this.contextService.getWorkspace().folders.map(folder => folder.uri); - const query = this.queryBuilder.file(folderResources, options); - - return query; - } - - get isCacheLoaded(): boolean { - return !!this.cacheState && this.cacheState.isLoaded; - } - - getGroupLabel(): string { - return nls.localize('searchResults', "search results"); - } - - getAutoFocus(searchValue: string): IAutoFocus { - return { - autoFocusFirstEntry: true - }; - } -} - -enum LoadingPhase { - Created = 1, - Loading, - Loaded, - Errored, - Disposed -} - -/** - * Exported for testing. - */ -export class CacheState { - - private _cacheKey = defaultGenerator.nextId(); - private query: IFileQuery; - - private loadingPhase = LoadingPhase.Created; - private promise: Promise | undefined; - - constructor(cacheQuery: (cacheKey: string) => IFileQuery, private doLoad: (query: IFileQuery) => Promise, private doDispose: (cacheKey: string) => Promise, private previous: CacheState | undefined) { - this.query = cacheQuery(this._cacheKey); - if (this.previous) { - const current = objects.assign({}, this.query, { cacheKey: null }); - const previous = objects.assign({}, this.previous.query, { cacheKey: null }); - if (!objects.equals(current, previous)) { - this.previous.dispose(); - this.previous = undefined; - } - } - } - - get cacheKey(): string { - return this.loadingPhase === LoadingPhase.Loaded || !this.previous ? this._cacheKey : this.previous.cacheKey; - } - - get isLoaded(): boolean { - const isLoaded = this.loadingPhase === LoadingPhase.Loaded; - return isLoaded || !this.previous ? isLoaded : this.previous.isLoaded; - } - - get isUpdating(): boolean { - const isUpdating = this.loadingPhase === LoadingPhase.Loading; - return isUpdating || !this.previous ? isUpdating : this.previous.isUpdating; - } - - load(): void { - if (this.isUpdating) { - return; - } - this.loadingPhase = LoadingPhase.Loading; - this.promise = this.doLoad(this.query) - .then(() => { - this.loadingPhase = LoadingPhase.Loaded; - if (this.previous) { - this.previous.dispose(); - this.previous = undefined; - } - }, err => { - this.loadingPhase = LoadingPhase.Errored; - errors.onUnexpectedError(err); - }); - } - - dispose(): void { - if (this.promise) { - this.promise.then(undefined, () => { }) - .then(() => { - this.loadingPhase = LoadingPhase.Disposed; - return this.doDispose(this._cacheKey); - }).then(undefined, err => { - errors.onUnexpectedError(err); - }); - } else { - this.loadingPhase = LoadingPhase.Disposed; - } - if (this.previous) { - this.previous.dispose(); - this.previous = undefined; - } - } -} diff --git a/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts deleted file mode 100644 index 54d2cfe03a6..00000000000 --- a/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts +++ /dev/null @@ -1,259 +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 nls from 'vs/nls'; -import { URI } from 'vs/base/common/uri'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { ThrottledDelayer } from 'vs/base/common/async'; -import { QuickOpenHandler, EditorQuickOpenEntry } from 'vs/workbench/browser/quickopen'; -import { QuickOpenModel, QuickOpenEntry, IHighlight } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { IAutoFocus, Mode, IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen'; -import * as filters from 'vs/base/common/filters'; -import * as strings from 'vs/base/common/strings'; -import { Range } from 'vs/editor/common/core/range'; -import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; -import { SymbolKinds, SymbolTag } from 'vs/editor/common/modes'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IWorkspaceSymbolProvider, getWorkspaceSymbols, IWorkspaceSymbol } from 'vs/workbench/contrib/search/common/search'; -import { basename } from 'vs/base/common/resources'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { ILabelService } from 'vs/platform/label/common/label'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { Schemas } from 'vs/base/common/network'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; - -class SymbolEntry extends EditorQuickOpenEntry { - - private bearingResolve?: Promise; - private score?: filters.FuzzyScore; - - constructor( - private bearing: IWorkspaceSymbol, - private provider: IWorkspaceSymbolProvider, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IEditorService editorService: IEditorService, - @ILabelService private readonly labelService: ILabelService, - @IOpenerService private readonly openerService: IOpenerService - ) { - super(editorService); - } - - setScore(score: filters.FuzzyScore | undefined) { - this.score = score; - } - - getHighlights(): [IHighlight[] | undefined /* Label */, IHighlight[] | undefined /* Description */, IHighlight[] | undefined /* Detail */] { - return [this.isDeprecated() ? [] : filters.createMatches(this.score), undefined, undefined]; - } - - getLabel(): string { - return this.bearing.name; - } - - getAriaLabel(): string { - return nls.localize('entryAriaLabel', "{0}, symbols picker", this.getLabel()); - } - - getDescription(): string | undefined { - const containerName = this.bearing.containerName; - if (this.bearing.location.uri) { - if (containerName) { - return `${containerName} — ${basename(this.bearing.location.uri)}`; - } - - return this.labelService.getUriLabel(this.bearing.location.uri, { relative: true }); - } - - return containerName; - } - - getIcon(): string { - return SymbolKinds.toCssClassName(this.bearing.kind); - } - - getLabelOptions(): IIconLabelValueOptions | undefined { - return this.isDeprecated() ? { extraClasses: ['deprecated'] } : undefined; - } - - getResource(): URI { - return this.bearing.location.uri; - } - - private isDeprecated(): boolean { - return this.bearing.tags ? this.bearing.tags.indexOf(SymbolTag.Deprecated) >= 0 : false; - } - - run(mode: Mode, context: IEntryRunContext): boolean { - - // resolve this type bearing if necessary - if (!this.bearingResolve && typeof this.provider.resolveWorkspaceSymbol === 'function' && !this.bearing.location.range) { - this.bearingResolve = Promise.resolve(this.provider.resolveWorkspaceSymbol(this.bearing, CancellationToken.None)).then(result => { - this.bearing = result || this.bearing; - - return this; - }, onUnexpectedError); - } - - // open after resolving - Promise.resolve(this.bearingResolve).then(() => { - const scheme = this.bearing.location.uri ? this.bearing.location.uri.scheme : undefined; - if (scheme === Schemas.http || scheme === Schemas.https) { - if (mode === Mode.OPEN || mode === Mode.OPEN_IN_BACKGROUND) { - this.openerService.open(this.bearing.location.uri, { fromUserGesture: true }); // support http/https resources (https://github.com/Microsoft/vscode/issues/58924)) - } - } else { - super.run(mode, context); - } - }); - - // hide if OPEN - return mode === Mode.OPEN; - } - - getInput(): IResourceInput { - const input: IResourceInput = { - resource: this.bearing.location.uri, - options: { - pinned: !this.configurationService.getValue().workbench.editor.enablePreviewFromQuickOpen, - selection: this.bearing.location.range ? Range.collapseToStart(this.bearing.location.range) : undefined - } - }; - - return input; - } - - static compare(a: SymbolEntry, b: SymbolEntry, searchValue: string): number { - // order: score, name, kind - if (a.score && b.score) { - if (a.score[0] > b.score[0]) { - return -1; - } else if (a.score[0] < b.score[0]) { - return 1; - } - } - const aName = a.getLabel().toLowerCase(); - const bName = b.getLabel().toLowerCase(); - let res = aName.localeCompare(bName); - if (res !== 0) { - return res; - } - let aKind = SymbolKinds.toCssClassName(a.bearing.kind); - let bKind = SymbolKinds.toCssClassName(b.bearing.kind); - return aKind.localeCompare(bKind); - } -} - -export interface IOpenSymbolOptions { - skipSorting: boolean; - skipLocalSymbols: boolean; - skipDelay: boolean; -} - -export class OpenSymbolHandler extends QuickOpenHandler { - - static readonly ID = 'workbench.picker.symbols'; - - private static readonly TYPING_SEARCH_DELAY = 200; // This delay accommodates for the user typing a word and then stops typing to start searching - - private delayer: ThrottledDelayer; - private options: IOpenSymbolOptions; - - constructor(@IInstantiationService private readonly instantiationService: IInstantiationService) { - super(); - - this.delayer = new ThrottledDelayer(OpenSymbolHandler.TYPING_SEARCH_DELAY); - this.options = Object.create(null); - } - - setOptions(options: IOpenSymbolOptions) { - this.options = options; - } - - canRun(): boolean | string { - return true; - } - - async getResults(searchValue: string, token: CancellationToken): Promise { - searchValue = searchValue.trim(); - - let entries: QuickOpenEntry[]; - if (!this.options.skipDelay) { - entries = await this.delayer.trigger(() => { - if (token.isCancellationRequested) { - return Promise.resolve([]); - } - - return this.doGetResults(searchValue, token); - }); - } else { - entries = await this.doGetResults(searchValue, token); - } - - return new QuickOpenModel(entries); - } - - private async doGetResults(searchValue: string, token: CancellationToken): Promise { - const tuples = await getWorkspaceSymbols(searchValue, token); - if (token.isCancellationRequested) { - return []; - } - - const result: SymbolEntry[] = []; - for (let tuple of tuples) { - const [provider, bearings] = tuple; - this.fillInSymbolEntries(result, provider, bearings, searchValue); - } - - // Sort (Standalone only) - if (!this.options.skipSorting) { - searchValue = searchValue ? strings.stripWildcards(searchValue.toLowerCase()) : searchValue; - return result.sort((a, b) => SymbolEntry.compare(a, b, searchValue)); - } - - return result; - } - - private fillInSymbolEntries(bucket: SymbolEntry[], provider: IWorkspaceSymbolProvider, types: IWorkspaceSymbol[], searchValue: string): void { - - const pattern = strings.stripWildcards(searchValue); - const patternLow = pattern.toLowerCase(); - - // Convert to Entries - for (let element of types) { - if (this.options.skipLocalSymbols && !!element.containerName) { - continue; // ignore local symbols if we are told so - } - - const entry = this.instantiationService.createInstance(SymbolEntry, element, provider); - entry.setScore(filters.fuzzyScore( - pattern, patternLow, 0, - entry.getLabel(), entry.getLabel().toLowerCase(), 0, - true - )); - bucket.push(entry); - } - } - - getGroupLabel(): string { - return nls.localize('symbols', "symbol results"); - } - - getEmptyLabel(searchString: string): string { - if (searchString.length > 0) { - return nls.localize('noSymbolsMatching', "No symbols matching"); - } - return nls.localize('noSymbolsWithoutInput', "Type to search for symbols"); - } - - getAutoFocus(searchValue: string): IAutoFocus { - return { - autoFocusFirstEntry: true, - autoFocusPrefixMatch: searchValue.trim() - }; - } -} diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 2bae8bb20ce..3f536c1e766 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -11,8 +11,6 @@ import * as objects from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; import { dirname } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { getSelectionSearchString } from 'vs/editor/contrib/find/findController'; import { ToggleCaseSensitiveKeybinding, ToggleRegexKeybinding, ToggleWholeWordKeybinding } from 'vs/editor/contrib/find/findModel'; import * as nls from 'vs/nls'; import { ICommandAction, MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; @@ -26,17 +24,13 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IListService, WorkbenchListFocusContextKey, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { Registry } from 'vs/platform/registry/common/platform'; -import { defaultQuickOpenContextKey } from 'vs/workbench/browser/parts/quickopen/quickopen'; -import { Extensions as QuickOpenExtensions, IQuickOpenRegistry, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; +import { defaultQuickAccessContextKeyValue } from 'vs/workbench/browser/quickaccess'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { Extensions as ViewExtensions, IViewsRegistry, IViewContainersRegistry, ViewContainerLocation, IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; import { getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files'; import { ExplorerFolderContext, ExplorerRootContext, FilesExplorerFocusCondition, IExplorerService, VIEWLET_ID as VIEWLET_ID_FILES } from 'vs/workbench/contrib/files/common/files'; -import { OpenAnythingHandler } from 'vs/workbench/contrib/search/browser/openAnythingHandler'; -import { OpenSymbolHandler } from 'vs/workbench/contrib/search/browser/openSymbolHandler'; import { registerContributions as replaceContributions } from 'vs/workbench/contrib/search/browser/replaceContributions'; import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand, ToggleSearchOnTypeAction, ExpandAllAction } from 'vs/workbench/contrib/search/browser/searchActions'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; @@ -47,14 +41,20 @@ import { getWorkspaceSymbols } from 'vs/workbench/contrib/search/common/search'; import { ISearchHistoryService, SearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'; import { FileMatchOrMatch, ISearchWorkbenchService, RenderableMatch, SearchWorkbenchService, FileMatch, Match, FolderMatch } from 'vs/workbench/contrib/search/common/searchModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { VIEWLET_ID, VIEW_ID, SearchSortOrder } from 'vs/workbench/services/search/common/search'; +import { VIEWLET_ID, VIEW_ID, SEARCH_EXCLUDE_CONFIG, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/explorerViewlet'; import { assertType, assertIsDefined } from 'vs/base/common/types'; -import { SearchViewPaneContainer } from 'vs/workbench/contrib/search/browser/searchViewlet'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; +import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; +import { SymbolsQuickAccessProvider } from 'vs/workbench/contrib/search/browser/symbolsQuickAccess'; +import { AnythingQuickAccessProvider } from 'vs/workbench/contrib/search/browser/anythingQuickAccess'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { AbstractGotoLineQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoLineQuickAccess'; +import { GotoSymbolQuickAccessProvider } from 'vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess'; registerSingleton(ISearchWorkbenchService, SearchWorkbenchService, true); registerSingleton(ISearchHistoryService, SearchHistoryService, true); @@ -72,7 +72,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: accessor => { const contextService = accessor.get(IContextKeyService).getContext(document.activeElement); if (contextService.getValue(SearchEditorConstants.InSearchEditor.serialize())) { - (accessor.get(IEditorService).activeControl as SearchEditor).toggleQueryDetails(); + (accessor.get(IEditorService).activeEditorPane as SearchEditor).toggleQueryDetails(); } else if (contextService.getValue(Constants.SearchViewFocusedKey.serialize())) { const searchView = getSearchView(accessor.get(IViewsService)); assertIsDefined(searchView).toggleQueryDetails(); @@ -80,6 +80,19 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'workbench.action.searchEditor.deleteResultBlock', + weight: KeybindingWeight.WorkbenchContrib, + when: SearchEditorConstants.InSearchEditor, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Backspace, + handler: accessor => { + const contextService = accessor.get(IContextKeyService).getContext(document.activeElement); + if (contextService.getValue(SearchEditorConstants.InSearchEditor.serialize())) { + (accessor.get(IEditorService).activeEditorPane as SearchEditor).deleteResultBlock(); + } + } +}); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: Constants.FocusSearchFromResults, weight: KeybindingWeight.WorkbenchContrib, @@ -454,45 +467,34 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { class ShowAllSymbolsAction extends Action { + static readonly ID = 'workbench.action.showAllSymbols'; static readonly LABEL = nls.localize('showTriggerActions', "Go to Symbol in Workspace..."); static readonly ALL_SYMBOLS_PREFIX = '#'; constructor( - actionId: string, actionLabel: string, - @IQuickOpenService private readonly quickOpenService: IQuickOpenService, - @ICodeEditorService private readonly editorService: ICodeEditorService) { + actionId: string, + actionLabel: string, + @IQuickInputService private readonly quickInputService: IQuickInputService + ) { super(actionId, actionLabel); - this.enabled = !!this.quickOpenService; } - run(context?: any): Promise { - - let prefix = ShowAllSymbolsAction.ALL_SYMBOLS_PREFIX; - let inputSelection: { start: number; end: number; } | undefined = undefined; - const editor = this.editorService.getFocusedCodeEditor(); - const word = editor && getSelectionSearchString(editor); - if (word) { - prefix = prefix + word; - inputSelection = { start: 1, end: word.length + 1 }; - } - - this.quickOpenService.show(prefix, { inputSelection }); - - return Promise.resolve(undefined); + async run(): Promise { + this.quickInputService.quickAccess.show(ShowAllSymbolsAction.ALL_SYMBOLS_PREFIX); } } const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: nls.localize('name', "Search"), - ctorDescriptor: new SyncDescriptor(SearchViewPaneContainer), + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), hideIfEmpty: true, icon: 'codicon-search', order: 1 }, ViewContainerLocation.Sidebar); -const viewDescriptor = { id: VIEW_ID, name: nls.localize('search', "Search"), ctorDescriptor: new SyncDescriptor(SearchView), canToggleVisibility: false, canMoveView: true }; +const viewDescriptor = { id: VIEW_ID, containerIcon: 'codicon-search', name: nls.localize('search', "Search"), ctorDescriptor: new SyncDescriptor(SearchView), canToggleVisibility: false, canMoveView: true }; // Register search default location to sidebar Registry.as(ViewExtensions.ViewsRegistry).registerViews([viewDescriptor], viewContainer); @@ -611,32 +613,24 @@ registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleSearchOnTypeA registry.registerWorkbenchAction(SyncActionDescriptor.create(RefreshAction, RefreshAction.ID, RefreshAction.LABEL), 'Search: Refresh', category); registry.registerWorkbenchAction(SyncActionDescriptor.create(ClearSearchResultsAction, ClearSearchResultsAction.ID, ClearSearchResultsAction.LABEL), 'Search: Clear Search Results', category); -// Register Quick Open Handler -Registry.as(QuickOpenExtensions.Quickopen).registerDefaultQuickOpenHandler( - QuickOpenHandlerDescriptor.create( - OpenAnythingHandler, - OpenAnythingHandler.ID, - '', - defaultQuickOpenContextKey, - nls.localize('openAnythingHandlerDescription', "Go to File") - ) -); +// Register Quick Access Handler +const quickAccessRegistry = Registry.as(QuickAccessExtensions.Quickaccess); -Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - QuickOpenHandlerDescriptor.create( - OpenSymbolHandler, - OpenSymbolHandler.ID, - ShowAllSymbolsAction.ALL_SYMBOLS_PREFIX, - 'inWorkspaceSymbolsPicker', - [ - { - prefix: ShowAllSymbolsAction.ALL_SYMBOLS_PREFIX, - needsEditor: false, - description: nls.localize('openSymbolDescriptionNormal', "Go to Symbol in Workspace") - } - ] - ) -); +quickAccessRegistry.registerQuickAccessProvider({ + ctor: AnythingQuickAccessProvider, + prefix: AnythingQuickAccessProvider.PREFIX, + placeholder: nls.localize('anythingQuickAccessPlaceholder', "Search files by name (append {0} to go to line or {1} to go to symbol)", AbstractGotoLineQuickAccessProvider.PREFIX, GotoSymbolQuickAccessProvider.PREFIX), + contextKey: defaultQuickAccessContextKeyValue, + helpEntries: [{ description: nls.localize('anythingQuickAccess', "Go to File"), needsEditor: false }] +}); + +quickAccessRegistry.registerQuickAccessProvider({ + ctor: SymbolsQuickAccessProvider, + prefix: SymbolsQuickAccessProvider.PREFIX, + placeholder: nls.localize('symbolsQuickAccessPlaceholder', "Type the name of a symbol to open."), + contextKey: 'inWorkspaceSymbolsPicker', + helpEntries: [{ description: nls.localize('symbolsQuickAccess', "Go to Symbol in Workspace"), needsEditor: false }] +}); // Configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -646,9 +640,9 @@ configurationRegistry.registerConfiguration({ title: nls.localize('searchConfigurationTitle', "Search"), type: 'object', properties: { - 'search.exclude': { + [SEARCH_EXCLUDE_CONFIG]: { type: 'object', - markdownDescription: nls.localize('exclude', "Configure glob patterns for excluding files and folders in searches. Inherits all glob patterns from the `#files.exclude#` setting. Read more about glob patterns [here](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)."), + markdownDescription: nls.localize('exclude', "Configure glob patterns for excluding files and folders in fulltext searches and quick open. Inherits all glob patterns from the `#files.exclude#` setting. Read more about glob patterns [here](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)."), default: { '**/node_modules': true, '**/bower_components': true, '**/*.code-search': true }, additionalProperties: { anyOf: [ @@ -704,6 +698,16 @@ configurationRegistry.registerConfiguration({ description: nls.localize('search.quickOpen.includeHistory', "Whether to include results from recently opened files in the file results for Quick Open."), default: true }, + 'search.quickOpen.history.filterSortOrder': { + 'type': 'string', + 'enum': ['default', 'recency'], + 'default': 'default', + 'enumDescriptions': [ + nls.localize('filterSortOrder.default', 'History entries are sorted by relevance based on the filter value used. More relevant entries appear first.'), + nls.localize('filterSortOrder.recency', 'History entries are sorted by recency. More recently opened entries appear first.') + ], + 'description': nls.localize('filterSortOrder', "Controls sorting order of editor history in quick open when filtering.") + }, 'search.followSymlinks': { type: 'boolean', description: nls.localize('search.followSymlinks', "Controls whether to follow symlinks while searching."), @@ -731,7 +735,7 @@ configurationRegistry.registerConfiguration({ type: 'string', enum: ['auto', 'alwaysCollapse', 'alwaysExpand'], enumDescriptions: [ - 'Files with less than 10 results are expanded. Others are collapsed.', + nls.localize('search.collapseResults.auto', "Files with less than 10 results are expanded. Others are collapsed."), '', '' ], diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index 574189c1a09..cc255f9057c 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -19,7 +19,7 @@ import { getSelectionKeyboardEvent, WorkbenchObjectTree } from 'vs/platform/list import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; -import { FolderMatch, FileMatch, FileMatchOrMatch, FolderMatchWithResource, Match, RenderableMatch, searchMatchComparer, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; +import { FolderMatch, FileMatch, FolderMatchWithResource, Match, RenderableMatch, searchMatchComparer, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ISearchConfiguration, VIEW_ID } from 'vs/workbench/services/search/common/search'; @@ -96,7 +96,7 @@ export class FocusNextInputAction extends Action { const input = this.editorService.activeEditor; if (input instanceof SearchEditorInput) { // cast as we cannot import SearchEditor as a value b/c cyclic dependency. - (this.editorService.activeControl as SearchEditor).focusNextInput(); + (this.editorService.activeEditorPane as SearchEditor).focusNextInput(); } const searchView = getSearchView(this.viewsService); @@ -121,7 +121,7 @@ export class FocusPreviousInputAction extends Action { const input = this.editorService.activeEditor; if (input instanceof SearchEditorInput) { // cast as we cannot import SearchEditor as a value b/c cyclic dependency. - (this.editorService.activeControl as SearchEditor).focusPrevInput(); + (this.editorService.activeEditorPane as SearchEditor).focusPrevInput(); } const searchView = getSearchView(this.viewsService); @@ -145,7 +145,7 @@ export abstract class FindOrReplaceInFilesAction extends Action { const searchAndReplaceWidget = openedView.searchAndReplaceWidget; searchAndReplaceWidget.toggleReplace(this.expandSearchReplaceWidget); - const updatedText = openedView.updateTextFromSelection(!this.expandSearchReplaceWidget); + const updatedText = openedView.updateTextFromSelection({ allowUnselectedWord: !this.expandSearchReplaceWidget }); openedView.searchAndReplaceWidget.focus(undefined, updatedText, updatedText); } }); @@ -172,7 +172,7 @@ export const FindInFilesCommand: ICommandHandler = (accessor, args: IFindInFiles if (typeof args.query === 'string') { openedView.setSearchParameters(args); } else { - updatedText = openedView.updateTextFromSelection((typeof args.replace !== 'string')); + updatedText = openedView.updateTextFromSelection({ allowUnselectedWord: typeof args.replace !== 'string' }); } openedView.searchAndReplaceWidget.focus(undefined, updatedText, updatedText); } @@ -498,7 +498,7 @@ export class FocusNextSearchResultAction extends Action { const input = this.editorService.activeEditor; if (input instanceof SearchEditorInput) { // cast as we cannot import SearchEditor as a value b/c cyclic dependency. - return (this.editorService.activeControl as SearchEditor).focusNextResult(); + return (this.editorService.activeEditorPane as SearchEditor).focusNextResult(); } return openSearchView(this.viewsService).then(searchView => { @@ -524,7 +524,7 @@ export class FocusPreviousSearchResultAction extends Action { const input = this.editorService.activeEditor; if (input instanceof SearchEditorInput) { // cast as we cannot import SearchEditor as a value b/c cyclic dependency. - return (this.editorService.activeControl as SearchEditor).focusPreviousResult(); + return (this.editorService.activeEditorPane as SearchEditor).focusPreviousResult(); } return openSearchView(this.viewsService).then(searchView => { @@ -711,16 +711,16 @@ export class ReplaceAction extends AbstractSearchAndReplaceAction { }); } - private getElementToFocusAfterReplace(): Match { - const navigator: ITreeNavigator = this.viewer.navigate(); + private getElementToFocusAfterReplace(): RenderableMatch { + const navigator: ITreeNavigator = this.viewer.navigate(); let fileMatched = false; - let elementToFocus: any = null; + let elementToFocus: RenderableMatch | null = null; do { elementToFocus = navigator.current(); if (elementToFocus instanceof Match) { if (elementToFocus.parent().id() === this.element.parent().id()) { fileMatched = true; - if (this.element.range().getStartPosition().isBeforeOrEqual((elementToFocus).range().getStartPosition())) { + if (this.element.range().getStartPosition().isBeforeOrEqual(elementToFocus.range().getStartPosition())) { // Closest next match in the same file break; } @@ -735,10 +735,10 @@ export class ReplaceAction extends AbstractSearchAndReplaceAction { } } } while (!!navigator.next()); - return elementToFocus; + return elementToFocus!; } - private async getElementToShowReplacePreview(elementToFocus: FileMatchOrMatch): Promise { + private async getElementToShowReplacePreview(elementToFocus: RenderableMatch): Promise { if (this.hasSameParent(elementToFocus)) { return elementToFocus; } diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index c782c6ef98f..d3024273740 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -31,17 +31,17 @@ import { IContextMenuService, IContextViewService } from 'vs/platform/contextvie import { IConfirmation, IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { TreeResourceNavigator, WorkbenchObjectTree, getSelectionKeyboardEvent } from 'vs/platform/list/browser/listService'; +import { ResourceNavigator, WorkbenchObjectTree, getSelectionKeyboardEvent } from 'vs/platform/list/browser/listService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IProgressService, IProgressStep, IProgress } from 'vs/platform/progress/common/progress'; -import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ITextQuery, VIEW_ID, SearchSortOrder, SearchCompletionExitCode } from 'vs/workbench/services/search/common/search'; +import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ITextQuery, SearchSortOrder, SearchCompletionExitCode } from 'vs/workbench/services/search/common/search'; import { ISearchHistoryService, ISearchHistoryValues } from 'vs/workbench/contrib/search/common/searchHistoryService'; import { diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, editorFindMatchHighlight, editorFindMatchHighlightBorder, listActiveSelectionForeground, foreground } from 'vs/platform/theme/common/colorRegistry'; -import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { OpenFileFolderAction, OpenFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; import { ResourceLabels } from 'vs/workbench/browser/labels'; -import { IEditor } from 'vs/workbench/common/editor'; +import { IEditorPane } from 'vs/workbench/common/editor'; import { ExcludePatternInputWidget, PatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget'; import { CancelSearchAction, ClearSearchResultsAction, CollapseDeepestExpandedLevelAction, RefreshAction, IFindInFilesArgs, appendKeyBindingLabel, ExpandAllAction, ToggleCollapseAndExpandAction } from 'vs/workbench/contrib/search/browser/searchActions'; import { FileMatchRenderer, FolderMatchRenderer, MatchRenderer, SearchAccessibilityProvider, SearchDelegate, SearchDND } from 'vs/workbench/contrib/search/browser/searchResultsView'; @@ -68,6 +68,7 @@ import { IViewDescriptorService } from 'vs/workbench/common/views'; import { OpenSearchEditorAction, createEditorFromSearchResult } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { Orientation } from 'vs/base/browser/ui/sash/sash'; const $ = dom.$; @@ -149,6 +150,8 @@ export class SearchView extends ViewPane { private triggerQueryDelayer: Delayer; private pauseSearching = false; + private treeAccessibilityProvider: SearchAccessibilityProvider; + constructor( options: IViewPaneOptions, @IFileService private readonly fileService: IFileService, @@ -177,7 +180,7 @@ export class SearchView extends ViewPane { @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...options, id: VIEW_ID, ariaHeaderLabel: nls.localize('searchView', "Search") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.container = dom.$('.search-view'); @@ -240,6 +243,8 @@ export class SearchView extends ViewPane { this.refreshAction = this._register(this.instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL)); this.cancelAction = this._register(this.instantiationService.createInstance(CancelSearchAction, CancelSearchAction.ID, CancelSearchAction.LABEL)); this.toggleCollapseAction = this._register(this.instantiationService.createInstance(ToggleCollapseAndExpandAction, ToggleCollapseAndExpandAction.ID, ToggleCollapseAndExpandAction.LABEL, collapseDeepestExpandedLevelAction, expandAllAction)); + + this.treeAccessibilityProvider = this.instantiationService.createInstance(SearchAccessibilityProvider, this.viewModel); } getContainer(): HTMLElement { @@ -716,7 +721,7 @@ export class SearchView extends ViewPane { ], { identityProvider, - accessibilityProvider: this.instantiationService.createInstance(SearchAccessibilityProvider, this.viewModel), + accessibilityProvider: this.treeAccessibilityProvider, dnd: this.instantiationService.createInstance(SearchDND), multipleSelectionSupport: false, overrideStyles: { @@ -728,7 +733,7 @@ export class SearchView extends ViewPane { this.toggleCollapseStateDelayer.trigger(() => this.toggleCollapseAction.onTreeCollapseStateChange()) )); - const resourceNavigator = this._register(new TreeResourceNavigator(this.tree, { openOnFocus: true, openOnSelection: false })); + const resourceNavigator = this._register(ResourceNavigator.createTreeResourceNavigator(this.tree, { openOnFocus: true, openOnSelection: false })); this._register(Event.debounce(resourceNavigator.onDidOpenResource, (last, event) => event, 75, true)(options => { if (options.element instanceof Match) { const selectedMatch: Match = options.element; @@ -819,6 +824,8 @@ export class SearchView extends ViewPane { } this.tree.setFocus([next], getSelectionKeyboardEvent(undefined, false)); this.tree.reveal(next); + const ariaLabel = this.treeAccessibilityProvider.getAriaLabel(next); + if (ariaLabel) { aria.alert(ariaLabel); } } } @@ -848,6 +855,8 @@ export class SearchView extends ViewPane { } this.tree.setFocus([prev], getSelectionKeyboardEvent(undefined, false)); this.tree.reveal(prev); + const ariaLabel = this.treeAccessibilityProvider.getAriaLabel(prev); + if (ariaLabel) { aria.alert(ariaLabel); } } } @@ -858,11 +867,11 @@ export class SearchView extends ViewPane { focus(): void { super.focus(); - const updatedText = this.updateTextFromSelection(); + const updatedText = this.updateTextFromSelection({ allowSearchOnType: false }); this.searchWidget.focus(undefined, undefined, updatedText); } - updateTextFromSelection(allowUnselectedWord = true): boolean { + updateTextFromSelection({ allowUnselectedWord = true, allowSearchOnType = true }): boolean { let updatedText = false; const seedSearchStringFromSelection = this.configurationService.getValue('editor').find!.seedSearchStringFromSelection; if (seedSearchStringFromSelection) { @@ -871,9 +880,14 @@ export class SearchView extends ViewPane { if (this.searchWidget.searchInput.getRegex()) { selectedText = strings.escapeRegExpCharacters(selectedText); } - this.pauseSearching = true; - this.searchWidget.setValue(selectedText); - this.pauseSearching = false; + + if (allowSearchOnType && !this.viewModel.searchResult.hasRemovedResults) { + this.searchWidget.setValue(selectedText); + } else { + this.pauseSearching = true; + this.searchWidget.setValue(selectedText); + this.pauseSearching = false; + } updatedText = true; } } @@ -1046,26 +1060,26 @@ export class SearchView extends ViewPane { return null; } - let activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (isDiffEditor(activeTextEditorWidget)) { - if (activeTextEditorWidget.getOriginalEditor().hasTextFocus()) { - activeTextEditorWidget = activeTextEditorWidget.getOriginalEditor(); + let activeTextEditorControl = this.editorService.activeTextEditorControl; + if (isDiffEditor(activeTextEditorControl)) { + if (activeTextEditorControl.getOriginalEditor().hasTextFocus()) { + activeTextEditorControl = activeTextEditorControl.getOriginalEditor(); } else { - activeTextEditorWidget = activeTextEditorWidget.getModifiedEditor(); + activeTextEditorControl = activeTextEditorControl.getModifiedEditor(); } } - if (!isCodeEditor(activeTextEditorWidget) || !activeTextEditorWidget.hasModel()) { + if (!isCodeEditor(activeTextEditorControl) || !activeTextEditorControl.hasModel()) { return null; } - const range = activeTextEditorWidget.getSelection(); + const range = activeTextEditorControl.getSelection(); if (!range) { return null; } if (range.isEmpty() && !this.searchWidget.searchInput.getValue() && allowUnselectedWord) { - const wordAtPosition = activeTextEditorWidget.getModel().getWordAtPosition(range.getStartPosition()); + const wordAtPosition = activeTextEditorControl.getModel().getWordAtPosition(range.getStartPosition()); if (wordAtPosition) { return wordAtPosition.word; } @@ -1074,7 +1088,7 @@ export class SearchView extends ViewPane { if (!range.isEmpty()) { let searchText = ''; for (let i = range.startLineNumber; i <= range.endLineNumber; i++) { - let lineText = activeTextEditorWidget.getModel().getLineContent(i); + let lineText = activeTextEditorControl.getModel().getLineContent(i); if (i === range.endLineNumber) { lineText = lineText.substring(0, range.endColumn - 1); } @@ -1173,7 +1187,7 @@ export class SearchView extends ViewPane { } if (!skipLayout && this.size) { - this.layout(this.size.height); + this.layout(this._orientation === Orientation.VERTICAL ? this.size.height : this.size.width); } } @@ -1532,7 +1546,7 @@ export class SearchView extends ViewPane { this.openSettings('.exclude'); }; - private openSettings(query: string): Promise { + private openSettings(query: string): Promise { const options: ISettingsEditorOptions = { query }; return this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? this.preferencesService.openWorkspaceSettings(undefined, options) : @@ -1846,7 +1860,7 @@ export class SearchView extends ViewPane { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const matchHighlightColor = theme.getColor(editorFindMatchHighlight); if (matchHighlightColor) { collector.addRule(`.monaco-workbench .search-view .findInFileMatch { background-color: ${matchHighlightColor}; }`); @@ -1882,9 +1896,11 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { collector.addRule(`.monaco-workbench .search-view .monaco-list.element-focused .monaco-list-row.focused.selected:not(.highlighted) .action-label:focus { outline-color: ${outlineSelectionColor} }`); } - const foregroundColor = theme.getColor(foreground); - if (foregroundColor) { - const fgWithOpacity = new Color(new RGBA(foregroundColor.rgba.r, foregroundColor.rgba.g, foregroundColor.rgba.b, 0.5)); - collector.addRule(`.vs-dark .search-view .message { color: ${fgWithOpacity}; }`); + if (theme.type === 'dark') { + const foregroundColor = theme.getColor(foreground); + if (foregroundColor) { + const fgWithOpacity = new Color(new RGBA(foregroundColor.rgba.r, foregroundColor.rgba.g, foregroundColor.rgba.b, 0.65)); + collector.addRule(`.search-view .message { color: ${fgWithOpacity}; }`); + } } }); diff --git a/src/vs/workbench/contrib/search/browser/searchViewlet.ts b/src/vs/workbench/contrib/search/browser/searchViewlet.ts deleted file mode 100644 index 2754b71d7b8..00000000000 --- a/src/vs/workbench/contrib/search/browser/searchViewlet.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 { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { VIEWLET_ID, VIEW_ID } from 'vs/workbench/services/search/common/search'; -import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; -import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { IViewDescriptorService } from 'vs/workbench/common/views'; - - -export class SearchViewPaneContainer extends ViewPaneContainer { - - constructor( - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @ITelemetryService telemetryService: ITelemetryService, - @IWorkspaceContextService protected contextService: IWorkspaceContextService, - @IStorageService protected storageService: IStorageService, - @IConfigurationService configurationService: IConfigurationService, - @IInstantiationService protected instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, - @IContextMenuService contextMenuService: IContextMenuService, - @IExtensionService extensionService: IExtensionService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService - ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); - } - - getSearchView(): SearchView | undefined { - const view = super.getView(VIEW_ID); - return view ? view as SearchView : undefined; - } -} diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 428becff1e7..f133d15905d 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -27,7 +27,7 @@ import { ISearchConfigurationProperties } from 'vs/workbench/services/search/com import { attachFindReplaceInputBoxStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/browser/contextScopedHistoryWidget'; -import { appendKeyBindingLabel, isSearchViewFocused } from 'vs/workbench/contrib/search/browser/searchActions'; +import { appendKeyBindingLabel, isSearchViewFocused, getSearchView } from 'vs/workbench/contrib/search/browser/searchActions'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { isMacintosh } from 'vs/base/common/platform'; @@ -52,19 +52,9 @@ export interface ISearchWidgetOptions { class ReplaceAllAction extends Action { - private static fgInstance: ReplaceAllAction | null = null; static readonly ID: string = 'search.action.replaceAll'; - static get INSTANCE(): ReplaceAllAction { - if (ReplaceAllAction.fgInstance === null) { - ReplaceAllAction.fgInstance = new ReplaceAllAction(); - } - return ReplaceAllAction.fgInstance; - } - - private _searchWidget: SearchWidget | null = null; - - constructor() { + constructor(private _searchWidget: SearchWidget) { super(ReplaceAllAction.ID, '', 'codicon-replace-all', false); } @@ -417,8 +407,7 @@ export class SearchWidget extends Widget { this._register(this.replaceInput.inputBox.onDidChange(() => this._onReplaceValueChanged.fire())); this._register(this.replaceInput.inputBox.onDidHeightChange(() => this._onDidHeightChange.fire())); - this.replaceAllAction = ReplaceAllAction.INSTANCE; - this.replaceAllAction.searchWidget = this; + this.replaceAllAction = new ReplaceAllAction(this); this.replaceAllAction.label = SearchWidget.REPLACE_ALL_DISABLED_LABEL; this.replaceActionBar = this._register(new ActionBar(this.replaceContainer)); this.replaceActionBar.push([this.replaceAllAction], { icon: true, label: false }); @@ -634,10 +623,17 @@ export class SearchWidget extends Widget { this._onSearchSubmit.fire({ triggeredOnType, delay }); } - contextLines() { + getContextLines() { return this.showContextCheckbox.checked ? +this.contextLinesInput.value : 0; } + modifyContextLines(increase: boolean) { + const current = +this.contextLinesInput.value; + const modified = current + (increase ? 1 : -1); + this.showContextCheckbox.checked = modified !== 0; + this.contextLinesInput.value = '' + modified; + } + toggleContextLines() { this.showContextCheckbox.checked = !this.showContextCheckbox.checked; this.onContextLinesChanged(); @@ -660,8 +656,12 @@ export function registerContributions() { when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceActiveKey, CONTEXT_FIND_WIDGET_NOT_VISIBLE), primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.Enter, handler: accessor => { - if (isSearchViewFocused(accessor.get(IViewsService))) { - ReplaceAllAction.INSTANCE.run(); + const viewsService = accessor.get(IViewsService); + if (isSearchViewFocused(viewsService)) { + const searchView = getSearchView(viewsService); + if (searchView) { + new ReplaceAllAction(searchView.searchAndReplaceWidget).run(); + } } } }); diff --git a/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts new file mode 100644 index 00000000000..51451109f43 --- /dev/null +++ b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts @@ -0,0 +1,273 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IPickerQuickAccessItem, PickerQuickAccessProvider, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ThrottledDelayer } from 'vs/base/common/async'; +import { getWorkspaceSymbols, IWorkspaceSymbol, IWorkspaceSymbolProvider } from 'vs/workbench/contrib/search/common/search'; +import { SymbolKinds, SymbolTag, SymbolKind } from 'vs/editor/common/modes'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { Schemas } from 'vs/base/common/network'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { Range } from 'vs/editor/common/core/range'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; +import { IKeyMods, IQuickPickItemWithResource } from 'vs/platform/quickinput/common/quickInput'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { getSelectionSearchString } from 'vs/editor/contrib/find/findController'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { prepareQuery, IPreparedQuery, scoreFuzzy2, pieceToQuery } from 'vs/base/common/fuzzyScorer'; +import { IMatch } from 'vs/base/common/filters'; + +interface ISymbolQuickPickItem extends IPickerQuickAccessItem, IQuickPickItemWithResource { + score?: number; + symbol?: IWorkspaceSymbol; +} + +export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider { + + static PREFIX = '#'; + + private static readonly TYPING_SEARCH_DELAY = 200; // this delay accommodates for the user typing a word and then stops typing to start searching + + private static TREAT_AS_GLOBAL_SYMBOL_TYPES = new Set([ + SymbolKind.Class, + SymbolKind.Enum, + SymbolKind.File, + SymbolKind.Interface, + SymbolKind.Namespace, + SymbolKind.Package, + SymbolKind.Module + ]); + + private delayer = this._register(new ThrottledDelayer(SymbolsQuickAccessProvider.TYPING_SEARCH_DELAY)); + + get defaultFilterValue(): string | undefined { + + // Prefer the word under the cursor in the active editor as default filter + const editor = this.codeEditorService.getFocusedCodeEditor(); + if (editor) { + return withNullAsUndefined(getSelectionSearchString(editor)); + } + + return undefined; + } + + constructor( + @ILabelService private readonly labelService: ILabelService, + @IOpenerService private readonly openerService: IOpenerService, + @IEditorService private readonly editorService: IEditorService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @ICodeEditorService private readonly codeEditorService: ICodeEditorService + ) { + super(SymbolsQuickAccessProvider.PREFIX, { + canAcceptInBackground: true, + noResultsPick: { + label: localize('noSymbolResults', "No matching workspace symbols") + } + }); + } + + private get configuration() { + const editorConfig = this.configurationService.getValue().workbench.editor; + + return { + openEditorPinned: !editorConfig.enablePreviewFromQuickOpen, + openSideBySideDirection: editorConfig.openSideBySideDirection + }; + } + + protected getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Promise> { + return this.getSymbolPicks(filter, undefined, token); + } + + async getSymbolPicks(filter: string, options: { skipLocal?: boolean, skipSorting?: boolean, delay?: number } | undefined, token: CancellationToken): Promise> { + return this.delayer.trigger(async () => { + if (token.isCancellationRequested) { + return []; + } + + return this.doGetSymbolPicks(prepareQuery(filter), options, token); + }, options?.delay); + } + + private async doGetSymbolPicks(query: IPreparedQuery, options: { skipLocal?: boolean, skipSorting?: boolean } | undefined, token: CancellationToken): Promise> { + + // Split between symbol and container query + let symbolQuery: IPreparedQuery; + let containerQuery: IPreparedQuery | undefined; + if (query.values && query.values.length > 1) { + symbolQuery = pieceToQuery(query.values[0]); // symbol: only match on first part + containerQuery = pieceToQuery(query.values.slice(1)); // container: match on all but first parts + } else { + symbolQuery = query; + } + + // Run the workspace symbol query + const workspaceSymbols = await getWorkspaceSymbols(symbolQuery.original, token); + if (token.isCancellationRequested) { + return []; + } + + const symbolPicks: Array = []; + + // Convert to symbol picks and apply filtering + const openSideBySideDirection = this.configuration.openSideBySideDirection; + for (const [provider, symbols] of workspaceSymbols) { + for (const symbol of symbols) { + + // Depending on the workspace symbols filter setting, skip over symbols that: + // - do not have a container + // - and are not treated explicitly as global symbols (e.g. classes) + if (options?.skipLocal && !SymbolsQuickAccessProvider.TREAT_AS_GLOBAL_SYMBOL_TYPES.has(symbol.kind) && !!symbol.containerName) { + continue; + } + + const symbolLabel = symbol.name; + const symbolLabelWithIcon = `$(symbol-${SymbolKinds.toString(symbol.kind) || 'property'}) ${symbolLabel}`; + + + // Score by symbol label if searching + let symbolScore: number | undefined = undefined; + let symbolMatches: IMatch[] | undefined = undefined; + if (symbolQuery.original.length > 0) { + [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabel, symbolQuery, 0, symbolLabelWithIcon.length - symbolLabel.length /* Readjust matches to account for codicons in label */); + if (!symbolScore) { + continue; + } + } + + const symbolUri = symbol.location.uri; + let containerLabel: string | undefined = undefined; + if (symbolUri) { + const containerPath = this.labelService.getUriLabel(symbolUri, { relative: true }); + if (symbol.containerName) { + containerLabel = `${symbol.containerName} • ${containerPath}`; + } else { + containerLabel = containerPath; + } + } + + // Score by container if specified and searching + let containerScore: number | undefined = undefined; + let containerMatches: IMatch[] | undefined = undefined; + if (containerQuery && containerQuery.original.length > 0) { + if (containerLabel) { + [containerScore, containerMatches] = scoreFuzzy2(containerLabel, containerQuery); + } + + if (!containerScore) { + continue; + } + + if (symbolScore) { + symbolScore += containerScore; // boost symbolScore by containerScore + } + } + + const deprecated = symbol.tags ? symbol.tags.indexOf(SymbolTag.Deprecated) >= 0 : false; + + symbolPicks.push({ + symbol, + resource: symbolUri, + score: symbolScore, + label: symbolLabelWithIcon, + ariaLabel: symbolLabel, + highlights: deprecated ? undefined : { + label: symbolMatches, + description: containerMatches + }, + description: containerLabel, + strikethrough: deprecated, + buttons: [ + { + iconClass: openSideBySideDirection === 'right' ? 'codicon-split-horizontal' : 'codicon-split-vertical', + tooltip: openSideBySideDirection === 'right' ? localize('openToSide', "Open to the Side") : localize('openToBottom', "Open to the Bottom") + } + ], + trigger: (buttonIndex, keyMods) => { + this.openSymbol(provider, symbol, token, { keyMods, forceOpenSideBySide: true }); + + return TriggerAction.CLOSE_PICKER; + }, + accept: async (keyMods, event) => this.openSymbol(provider, symbol, token, { keyMods, preserveFocus: event.inBackground }), + }); + } + } + + // Sort picks (unless disabled) + if (!options?.skipSorting) { + symbolPicks.sort((symbolA, symbolB) => this.compareSymbols(symbolA, symbolB)); + } + + return symbolPicks; + } + + private async openSymbol(provider: IWorkspaceSymbolProvider, symbol: IWorkspaceSymbol, token: CancellationToken, options: { keyMods: IKeyMods, forceOpenSideBySide?: boolean, preserveFocus?: boolean }): Promise { + + // Resolve actual symbol to open for providers that can resolve + let symbolToOpen = symbol; + if (typeof provider.resolveWorkspaceSymbol === 'function' && !symbol.location.range) { + symbolToOpen = await provider.resolveWorkspaceSymbol(symbol, token) || symbol; + + if (token.isCancellationRequested) { + return; + } + } + + // Open HTTP(s) links with opener service + if (symbolToOpen.location.uri.scheme === Schemas.http || symbolToOpen.location.uri.scheme === Schemas.https) { + await this.openerService.open(symbolToOpen.location.uri, { fromUserGesture: true }); + } + + // Otherwise open as editor + else { + await this.editorService.openEditor({ + resource: symbolToOpen.location.uri, + options: { + preserveFocus: options?.preserveFocus, + pinned: options.keyMods.alt || this.configuration.openEditorPinned, + selection: symbolToOpen.location.range ? Range.collapseToStart(symbolToOpen.location.range) : undefined + } + }, options.keyMods.ctrlCmd || options?.forceOpenSideBySide ? SIDE_GROUP : ACTIVE_GROUP); + } + } + + private compareSymbols(symbolA: ISymbolQuickPickItem, symbolB: ISymbolQuickPickItem): number { + + // By score + if (symbolA.score && symbolB.score) { + if (symbolA.score > symbolB.score) { + return -1; + } + + if (symbolA.score < symbolB.score) { + return 1; + } + } + + // By name + if (symbolA.symbol && symbolB.symbol) { + const symbolAName = symbolA.symbol.name.toLowerCase(); + const symbolBName = symbolB.symbol.name.toLowerCase(); + const res = symbolAName.localeCompare(symbolBName); + if (res !== 0) { + return res; + } + } + + // By kind + if (symbolA.symbol && symbolB.symbol) { + const symbolAKind = SymbolKinds.toCssClassName(symbolA.symbol.kind); + const symbolBKind = SymbolKinds.toCssClassName(symbolB.symbol.kind); + return symbolAKind.localeCompare(symbolBKind); + } + + return 0; + } +} diff --git a/src/vs/workbench/contrib/search/common/cacheState.ts b/src/vs/workbench/contrib/search/common/cacheState.ts new file mode 100644 index 00000000000..ff33a3f44f8 --- /dev/null +++ b/src/vs/workbench/contrib/search/common/cacheState.ts @@ -0,0 +1,110 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { defaultGenerator } from 'vs/base/common/idGenerator'; +import { IFileQuery } from 'vs/workbench/services/search/common/search'; +import { assign, equals } from 'vs/base/common/objects'; + +enum LoadingPhase { + Created = 1, + Loading = 2, + Loaded = 3, + Errored = 4, + Disposed = 5 +} + +export class FileQueryCacheState { + + private readonly _cacheKey = defaultGenerator.nextId(); + get cacheKey(): string { + if (this.loadingPhase === LoadingPhase.Loaded || !this.previousCacheState) { + return this._cacheKey; + } + + return this.previousCacheState.cacheKey; + } + + get isLoaded(): boolean { + const isLoaded = this.loadingPhase === LoadingPhase.Loaded; + + return isLoaded || !this.previousCacheState ? isLoaded : this.previousCacheState.isLoaded; + } + + get isUpdating(): boolean { + const isUpdating = this.loadingPhase === LoadingPhase.Loading; + + return isUpdating || !this.previousCacheState ? isUpdating : this.previousCacheState.isUpdating; + } + + private readonly query = this.cacheQuery(this._cacheKey); + + private loadingPhase = LoadingPhase.Created; + private loadPromise: Promise | undefined; + + constructor( + private cacheQuery: (cacheKey: string) => IFileQuery, + private loadFn: (query: IFileQuery) => Promise, + private disposeFn: (cacheKey: string) => Promise, + private previousCacheState: FileQueryCacheState | undefined + ) { + if (this.previousCacheState) { + const current = assign({}, this.query, { cacheKey: null }); + const previous = assign({}, this.previousCacheState.query, { cacheKey: null }); + if (!equals(current, previous)) { + this.previousCacheState.dispose(); + this.previousCacheState = undefined; + } + } + } + + load(): FileQueryCacheState { + if (this.isUpdating) { + return this; + } + + this.loadingPhase = LoadingPhase.Loading; + + this.loadPromise = (async () => { + try { + await this.loadFn(this.query); + + this.loadingPhase = LoadingPhase.Loaded; + + if (this.previousCacheState) { + this.previousCacheState.dispose(); + this.previousCacheState = undefined; + } + } catch (error) { + this.loadingPhase = LoadingPhase.Errored; + + throw error; + } + })(); + + return this; + } + + dispose(): void { + if (this.loadPromise) { + (async () => { + try { + await this.loadPromise; + } catch (error) { + // ignore + } + + this.loadingPhase = LoadingPhase.Disposed; + this.disposeFn(this._cacheKey); + })(); + } else { + this.loadingPhase = LoadingPhase.Disposed; + } + + if (this.previousCacheState) { + this.previousCacheState.dispose(); + this.previousCacheState = undefined; + } + } +} diff --git a/src/vs/workbench/contrib/search/common/queryBuilder.ts b/src/vs/workbench/contrib/search/common/queryBuilder.ts index ee91d6edcf2..92c69e2691d 100644 --- a/src/vs/workbench/contrib/search/common/queryBuilder.ts +++ b/src/vs/workbench/contrib/search/common/queryBuilder.ts @@ -16,7 +16,7 @@ import { isMultilineRegexSource } from 'vs/editor/common/model/textModelSearch'; import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, WorkbenchState, toWorkspaceFolder, IWorkspaceFolderData } from 'vs/platform/workspace/common/workspace'; import { getExcludes, ICommonQueryProps, IFileQuery, IFolderQuery, IPatternInfo, ISearchConfiguration, ITextQuery, ITextSearchPreviewOptions, pathIncludedInQuery, QueryType } from 'vs/workbench/services/search/common/search'; import { Schemas } from 'vs/base/common/network'; @@ -94,7 +94,7 @@ export class QueryBuilder { return !folderConfig.search.useRipgrep; }); - const commonQuery = this.commonQuery(folderResources, options); + const commonQuery = this.commonQuery(folderResources?.map(toWorkspaceFolder), options); return { ...commonQuery, type: QueryType.Text, @@ -134,8 +134,8 @@ export class QueryBuilder { return newPattern; } - file(folderResources: uri[] | undefined, options: IFileQueryBuilderOptions = {}): IFileQuery { - const commonQuery = this.commonQuery(folderResources, options); + file(folders: IWorkspaceFolderData[], options: IFileQueryBuilderOptions = {}): IFileQuery { + const commonQuery = this.commonQuery(folders, options); return { ...commonQuery, type: QueryType.File, @@ -144,11 +144,11 @@ export class QueryBuilder { : options.filePattern, exists: options.exists, sortByScore: options.sortByScore, - cacheKey: options.cacheKey + cacheKey: options.cacheKey, }; } - private commonQuery(folderResources: uri[] = [], options: ICommonQueryBuilderOptions = {}): ICommonQueryProps { + private commonQuery(folderResources: IWorkspaceFolderData[] = [], options: ICommonQueryBuilderOptions = {}): ICommonQueryProps { let includeSearchPathsInfo: ISearchPathsInfo = {}; if (options.includePattern) { const includePattern = normalizeSlashes(options.includePattern); @@ -166,9 +166,10 @@ export class QueryBuilder { } // Build folderQueries from searchPaths, if given, otherwise folderResources + const includeFolderName = folderResources.length > 1; const folderQueries = (includeSearchPathsInfo.searchPaths && includeSearchPathsInfo.searchPaths.length ? includeSearchPathsInfo.searchPaths.map(searchPath => this.getFolderQueryForSearchPath(searchPath, options, excludeSearchPathsInfo)) : - folderResources.map(uri => this.getFolderQueryForRoot(uri, options, excludeSearchPathsInfo))) + folderResources.map(folder => this.getFolderQueryForRoot(folder, options, excludeSearchPathsInfo, includeFolderName))) .filter(query => !!query) as IFolderQuery[]; const queryProps: ICommonQueryProps = { @@ -403,7 +404,7 @@ export class QueryBuilder { } private getFolderQueryForSearchPath(searchPath: ISearchPathPattern, options: ICommonQueryBuilderOptions, searchPathExcludes: ISearchPathsInfo): IFolderQuery | null { - const rootConfig = this.getFolderQueryForRoot(searchPath.searchPath, options, searchPathExcludes); + const rootConfig = this.getFolderQueryForRoot(toWorkspaceFolder(searchPath.searchPath), options, searchPathExcludes, false); if (!rootConfig) { return null; } @@ -416,10 +417,10 @@ export class QueryBuilder { }; } - private getFolderQueryForRoot(folder: uri, options: ICommonQueryBuilderOptions, searchPathExcludes: ISearchPathsInfo): IFolderQuery | null { + private getFolderQueryForRoot(folder: IWorkspaceFolderData, options: ICommonQueryBuilderOptions, searchPathExcludes: ISearchPathsInfo, includeFolderName: boolean): IFolderQuery | null { let thisFolderExcludeSearchPathPattern: glob.IExpression | undefined; if (searchPathExcludes.searchPaths) { - const thisFolderExcludeSearchPath = searchPathExcludes.searchPaths.filter(sp => isEqual(sp.searchPath, folder))[0]; + const thisFolderExcludeSearchPath = searchPathExcludes.searchPaths.filter(sp => isEqual(sp.searchPath, folder.uri))[0]; if (thisFolderExcludeSearchPath && !thisFolderExcludeSearchPath.pattern) { // entire folder is excluded return null; @@ -428,7 +429,7 @@ export class QueryBuilder { } } - const folderConfig = this.configurationService.getValue({ resource: folder }); + const folderConfig = this.configurationService.getValue({ resource: folder.uri }); const settingExcludes = this.getExcludesForFolder(folderConfig, options); const excludePattern: glob.IExpression = { ...(settingExcludes || {}), @@ -436,7 +437,8 @@ export class QueryBuilder { }; return { - folder, + folder: folder.uri, + folderName: includeFolderName ? folder.name : undefined, excludePattern: Object.keys(excludePattern).length > 0 ? excludePattern : undefined, fileEncoding: folderConfig.files && folderConfig.files.encoding, disregardIgnoreFiles: typeof options.disregardIgnoreFiles === 'boolean' ? options.disregardIgnoreFiles : !folderConfig.search.useIgnoreFiles, diff --git a/src/vs/workbench/contrib/search/common/search.ts b/src/vs/workbench/contrib/search/common/search.ts index 10e3f093834..a3fd05bf82c 100644 --- a/src/vs/workbench/contrib/search/common/search.ts +++ b/src/vs/workbench/contrib/search/common/search.ts @@ -14,6 +14,8 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { CancellationToken } from 'vs/base/common/cancellation'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IFileService } from 'vs/platform/files/common/files'; +import { IRange } from 'vs/editor/common/core/range'; +import { isNumber } from 'vs/base/common/types'; export interface IWorkspaceSymbol { name: string; @@ -74,6 +76,10 @@ export function getWorkspaceSymbols(query: string, token: CancellationToken = Ca export interface IWorkbenchSearchConfigurationProperties extends ISearchConfigurationProperties { quickOpen: { includeSymbols: boolean; + includeHistory: boolean; + history: { + filterSortOrder: 'default' | 'recency' + } }; } @@ -95,3 +101,67 @@ export function getOutOfWorkspaceEditorResources(accessor: ServicesAccessor): UR return resources as URI[]; } + +// Supports patterns of <#|:|(><#|:|,> +const LINE_COLON_PATTERN = /\s?[#:\(](\d*)([#:,](\d*))?\)?\s*$/; + +export interface IFilterAndRange { + filter: string; + range: IRange; +} + +export function extractRangeFromFilter(filter: string, unless?: string[]): IFilterAndRange | undefined { + if (!filter || unless?.some(value => filter.indexOf(value) !== -1)) { + return undefined; + } + + let range: IRange | undefined = undefined; + + // Find Line/Column number from search value using RegExp + const patternMatch = LINE_COLON_PATTERN.exec(filter); + if (patternMatch && patternMatch.length > 1) { + const startLineNumber = parseInt(patternMatch[1], 10); + + // Line Number + if (isNumber(startLineNumber)) { + range = { + startLineNumber: startLineNumber, + startColumn: 1, + endLineNumber: startLineNumber, + endColumn: 1 + }; + + // Column Number + if (patternMatch.length > 3) { + const startColumn = parseInt(patternMatch[3], 10); + if (isNumber(startColumn)) { + range = { + startLineNumber: range.startLineNumber, + startColumn: startColumn, + endLineNumber: range.endLineNumber, + endColumn: startColumn + }; + } + } + } + + // User has typed "something:" or "something#" without a line number, in this case treat as start of file + else if (patternMatch[1] === '') { + range = { + startLineNumber: 1, + startColumn: 1, + endLineNumber: 1, + endColumn: 1 + }; + } + } + + if (patternMatch && range) { + return { + filter: filter.substr(0, patternMatch.index), // clear range suffix from search value + range + }; + } + + return undefined; +} diff --git a/src/vs/workbench/contrib/search/common/searchModel.ts b/src/vs/workbench/contrib/search/common/searchModel.ts index 1505579db92..ca24d175027 100644 --- a/src/vs/workbench/contrib/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/common/searchModel.ts @@ -606,7 +606,7 @@ export class FolderMatch extends Disposable { fileMatches = [fileMatches]; } - for (let match of fileMatches) { + for (let match of fileMatches as FileMatch[]) { this._fileMatches.delete(match.resource); if (dispose) { match.dispose(); @@ -703,6 +703,8 @@ export class SearchResult extends Disposable { private _rangeHighlightDecorations: RangeHighlightDecorations; private disposePastResults: () => void = () => { }; + private _hasRemovedResults = false; + constructor( private _searchModel: SearchModel, @IReplaceService private readonly replaceService: IReplaceService, @@ -714,6 +716,16 @@ export class SearchResult extends Disposable { this._rangeHighlightDecorations = this.instantiationService.createInstance(RangeHighlightDecorations); this._register(this.modelService.onModelAdded(model => this.onModelAdded(model))); + + this._register(this.onChange(e => { + if (e.removed) { + this._hasRemovedResults = true; + } + })); + } + + get hasRemovedResults(): boolean { + return this._hasRemovedResults; } get query(): ITextQuery | null { @@ -725,7 +737,8 @@ export class SearchResult extends Disposable { const oldFolderMatches = this.folderMatches(); new Promise(resolve => this.disposePastResults = resolve) .then(() => oldFolderMatches.forEach(match => match.clear())) - .then(() => oldFolderMatches.forEach(match => match.dispose())); + .then(() => oldFolderMatches.forEach(match => match.dispose())) + .then(() => this._hasRemovedResults = false); this._rangeHighlightDecorations.removeHighlightRange(); this._folderMatchesMap = TernarySearchTree.forPaths(); @@ -1028,7 +1041,6 @@ export class SearchModel extends Disposable { search(query: ITextQuery, onProgress?: (result: ISearchProgressItem) => void): Promise { this.cancelSearch(true); - this._searchQuery = query; if (!this.searchConfig.searchOnType) { this.searchResult.clear(); diff --git a/src/vs/workbench/contrib/search/test/browser/queryBuilder.test.ts b/src/vs/workbench/contrib/search/test/browser/queryBuilder.test.ts index 26f6e129337..09b44564799 100644 --- a/src/vs/workbench/contrib/search/test/browser/queryBuilder.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/queryBuilder.test.ts @@ -13,8 +13,9 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { IFolderQuery, IPatternInfo, QueryType, ITextQuery, IFileQuery } from 'vs/workbench/services/search/common/search'; import { IWorkspaceContextService, toWorkspaceFolder, Workspace, toWorkspaceFolders } from 'vs/platform/workspace/common/workspace'; import { ISearchPathsInfo, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; -import { TestContextService, TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices'; import { isWindows } from 'vs/base/common/platform'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; const DEFAULT_EDITOR_CONFIG = {}; const DEFAULT_USER_CONFIG = { useRipgrep: true, useIgnoreFiles: true, useGlobalIgnoreFiles: true }; @@ -25,6 +26,7 @@ suite('QueryBuilder', () => { const PATTERN_INFO: IPatternInfo = { pattern: 'a' }; const ROOT_1 = fixPath('/foo/root1'); const ROOT_1_URI = getUri(ROOT_1); + const ROOT_1_NAMED_FOLDER = toWorkspaceFolder(ROOT_1_URI); const WS_CONFIG_PATH = getUri('/bar/test.code-workspace'); // location of the workspace file (not important except that it is a file URI) let instantiationService: TestInstantiationService; @@ -89,7 +91,10 @@ suite('QueryBuilder', () => { test('does not split glob pattern when expandPatterns disabled', () => { assertEqualQueries( - queryBuilder.file([ROOT_1_URI], { includePattern: '**/foo, **/bar' }), + queryBuilder.file( + [ROOT_1_NAMED_FOLDER], + { includePattern: '**/foo, **/bar' }, + ), { folderQueries: [{ folder: ROOT_1_URI @@ -362,7 +367,7 @@ suite('QueryBuilder', () => { const content = 'content'; assertEqualQueries( queryBuilder.file( - undefined, + [], { filePattern: ` ${content} ` } ), { @@ -902,10 +907,13 @@ suite('QueryBuilder', () => { suite('file', () => { test('simple file query', () => { const cacheKey = 'asdf'; - const query = queryBuilder.file([ROOT_1_URI], { - cacheKey, - sortByScore: true - }); + const query = queryBuilder.file( + [ROOT_1_NAMED_FOLDER], + { + cacheKey, + sortByScore: true + }, + ); assert.equal(query.folderQueries.length, 1); assert.equal(query.cacheKey, cacheKey); diff --git a/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts b/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts index a2eb0ba8458..1b212bb21cb 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts @@ -18,6 +18,8 @@ import { IFileMatch } from 'vs/workbench/services/search/common/search'; import { ReplaceAction } from 'vs/workbench/contrib/search/browser/searchActions'; import { FileMatch, FileMatchOrMatch, Match } from 'vs/workbench/contrib/search/common/searchModel'; import { MockObjectTree } from 'vs/workbench/contrib/search/test/browser/mockSearchTree'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; suite('Search Actions', () => { @@ -153,6 +155,7 @@ suite('Search Actions', () => { function stubModelService(instantiationService: TestInstantiationService): IModelService { instantiationService.stub(IConfigurationService, new TestConfigurationService()); + instantiationService.stub(IThemeService, new TestThemeService()); return instantiationService.createInstance(ModelServiceImpl); } }); diff --git a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts index a1c96e21aa2..39499ce4639 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts @@ -13,8 +13,10 @@ import { IFileMatch, ITextSearchMatch, OneLineRange, QueryType, SearchSortOrder import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { FileMatch, Match, searchMatchComparer, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; -import { TestContextService } from 'vs/workbench/test/browser/workbenchTestServices'; import { isWindows } from 'vs/base/common/platform'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; suite('Search - Viewlet', () => { let instantiation: TestInstantiationService; @@ -105,6 +107,7 @@ suite('Search - Viewlet', () => { function stubModelService(instantiationService: TestInstantiationService): IModelService { instantiationService.stub(IConfigurationService, new TestConfigurationService()); + instantiationService.stub(IThemeService, new TestThemeService()); return instantiationService.createInstance(ModelServiceImpl); } }); diff --git a/src/vs/workbench/contrib/search/test/browser/openFileHandler.test.ts b/src/vs/workbench/contrib/search/test/common/cacheState.test.ts similarity index 96% rename from src/vs/workbench/contrib/search/test/browser/openFileHandler.test.ts rename to src/vs/workbench/contrib/search/test/common/cacheState.test.ts index d6887d88e82..6baa982999a 100644 --- a/src/vs/workbench/contrib/search/test/browser/openFileHandler.test.ts +++ b/src/vs/workbench/contrib/search/test/common/cacheState.test.ts @@ -6,11 +6,11 @@ import * as assert from 'assert'; import * as errors from 'vs/base/common/errors'; import * as objects from 'vs/base/common/objects'; -import { CacheState } from 'vs/workbench/contrib/search/browser/openFileHandler'; import { DeferredPromise } from 'vs/base/test/common/utils'; import { QueryType, IFileQuery } from 'vs/workbench/services/search/common/search'; +import { FileQueryCacheState } from 'vs/workbench/contrib/search/common/cacheState'; -suite('CacheState', () => { +suite('FileQueryCacheState', () => { test('reuse old cacheKey until new cache is loaded', async function () { @@ -162,8 +162,8 @@ suite('CacheState', () => { assert.strictEqual(third.cacheKey, thirdKey); // recover with next successful load }); - function createCacheState(cache: MockCache, previous?: CacheState): CacheState { - return new CacheState( + function createCacheState(cache: MockCache, previous?: FileQueryCacheState): FileQueryCacheState { + return new FileQueryCacheState( cacheKey => cache.query(cacheKey), query => cache.load(query), cacheKey => cache.dispose(cacheKey), diff --git a/src/vs/workbench/contrib/search/test/common/extractRange.test.ts b/src/vs/workbench/contrib/search/test/common/extractRange.test.ts new file mode 100644 index 00000000000..a0894323734 --- /dev/null +++ b/src/vs/workbench/contrib/search/test/common/extractRange.test.ts @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { extractRangeFromFilter } from 'vs/workbench/contrib/search/common/search'; + +suite('extractRangeFromFilter', () => { + + test('basics', async function () { + assert.ok(!extractRangeFromFilter('')); + assert.ok(!extractRangeFromFilter('/some/path')); + assert.ok(!extractRangeFromFilter('/some/path/file.txt')); + + for (const lineSep of [':', '#', '(']) { + for (const colSep of [':', '#', ',']) { + const base = '/some/path/file.txt'; + + let res = extractRangeFromFilter(`${base}${lineSep}20`); + assert.equal(res?.filter, base); + assert.equal(res?.range.startLineNumber, 20); + assert.equal(res?.range.startColumn, 1); + + res = extractRangeFromFilter(`${base}${lineSep}20${colSep}`); + assert.equal(res?.filter, base); + assert.equal(res?.range.startLineNumber, 20); + assert.equal(res?.range.startColumn, 1); + + res = extractRangeFromFilter(`${base}${lineSep}20${colSep}3`); + assert.equal(res?.filter, base); + assert.equal(res?.range.startLineNumber, 20); + assert.equal(res?.range.startColumn, 3); + } + } + }); + + test('allow space after path', async function () { + let res = extractRangeFromFilter('/some/path/file.txt (19,20)'); + + assert.equal(res?.filter, '/some/path/file.txt'); + assert.equal(res?.range.startLineNumber, 19); + assert.equal(res?.range.startColumn, 20); + }); + + test('unless', async function () { + let res = extractRangeFromFilter('/some/path/file.txt@ (19,20)', ['@']); + + assert.ok(!res); + }); +}); diff --git a/src/vs/workbench/contrib/search/test/common/searchModel.test.ts b/src/vs/workbench/contrib/search/test/common/searchModel.test.ts index 4bd8e74e94e..545d7de9943 100644 --- a/src/vs/workbench/contrib/search/test/common/searchModel.test.ts +++ b/src/vs/workbench/contrib/search/test/common/searchModel.test.ts @@ -19,6 +19,8 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { SearchModel } from 'vs/workbench/contrib/search/common/searchModel'; import * as process from 'vs/base/common/process'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; const nullEvent = new class { id: number = -1; @@ -328,6 +330,7 @@ suite('SearchModel', () => { function stubModelService(instantiationService: TestInstantiationService): IModelService { instantiationService.stub(IConfigurationService, new TestConfigurationService()); + instantiationService.stub(IThemeService, new TestThemeService()); return instantiationService.createInstance(ModelServiceImpl); } diff --git a/src/vs/workbench/contrib/search/test/common/searchResult.test.ts b/src/vs/workbench/contrib/search/test/common/searchResult.test.ts index 82e8ecc9fdd..f46f158ac29 100644 --- a/src/vs/workbench/contrib/search/test/common/searchResult.test.ts +++ b/src/vs/workbench/contrib/search/test/common/searchResult.test.ts @@ -16,6 +16,8 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/ import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; const lineOneRange = new OneLineRange(1, 0, 1); @@ -352,6 +354,7 @@ suite('SearchResult', () => { function stubModelService(instantiationService: TestInstantiationService): IModelService { instantiationService.stub(IConfigurationService, new TestConfigurationService()); + instantiationService.stub(IThemeService, new TestThemeService()); return instantiationService.createInstance(ModelServiceImpl); } }); diff --git a/src/vs/workbench/contrib/search/test/electron-browser/queryBuilder.test.ts b/src/vs/workbench/contrib/search/test/electron-browser/queryBuilder.test.ts index 5222422e2b4..6f1e394203f 100644 --- a/src/vs/workbench/contrib/search/test/electron-browser/queryBuilder.test.ts +++ b/src/vs/workbench/contrib/search/test/electron-browser/queryBuilder.test.ts @@ -8,9 +8,9 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IWorkspaceContextService, toWorkspaceFolder, Workspace } from 'vs/platform/workspace/common/workspace'; import { ISearchPathsInfo, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; -import { TestContextService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestEnvironmentService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { assertEqualSearchPathResults, getUri, patternsToIExpression, globalGlob, fixPath } from 'vs/workbench/contrib/search/test/browser/queryBuilder.test'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; const DEFAULT_EDITOR_CONFIG = {}; const DEFAULT_USER_CONFIG = { useRipgrep: true, useIgnoreFiles: true, useGlobalIgnoreFiles: true }; diff --git a/src/vs/workbench/contrib/searchEditor/browser/constants.ts b/src/vs/workbench/contrib/searchEditor/browser/constants.ts index 7de8978e745..39c264c57c9 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/constants.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/constants.ts @@ -7,11 +7,15 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export const OpenInEditorCommandId = 'search.action.openInEditor'; export const OpenNewEditorCommandId = 'search.action.openNewEditor'; +export const OpenNewEditorToSideCommandId = 'search.action.openNewEditorToSide'; export const ToggleSearchEditorCaseSensitiveCommandId = 'toggleSearchEditorCaseSensitive'; export const ToggleSearchEditorWholeWordCommandId = 'toggleSearchEditorWholeWord'; export const ToggleSearchEditorRegexCommandId = 'toggleSearchEditorRegex'; export const ToggleSearchEditorContextLinesCommandId = 'toggleSearchEditorContextLines'; +export const IncreaseSearchEditorContextLinesCommandId = 'increaseSearchEditorContextLines'; +export const DecreaseSearchEditorContextLinesCommandId = 'decreaseSearchEditorContextLines'; + export const RerunSearchEditorSearchCommandId = 'rerunSearchEditorSearch'; export const CleanSearchEditorStateCommandId = 'cleanSearchEditorState'; export const SelectAllSearchEditorMatchesCommandId = 'selectAllSearchEditorMatches'; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index eb2646630b4..db326f5ed3e 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -24,7 +24,7 @@ import { Extensions as EditorInputExtensions, IEditorInputFactory, IEditorInputF import * as SearchConstants from 'vs/workbench/contrib/search/common/constants'; import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants'; import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; -import { OpenResultsInEditorAction, OpenSearchEditorAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand, selectAllSearchEditorMatchesCommand, RerunSearchEditorSearchAction } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; +import { OpenResultsInEditorAction, OpenSearchEditorAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand, selectAllSearchEditorMatchesCommand, RerunSearchEditorSearchAction, OpenSearchEditorToSideAction, modifySearchEditorContextLinesCommand } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; import { getOrMakeSearchEditorInput, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -151,6 +151,22 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_L } }); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: SearchEditorConstants.IncreaseSearchEditorContextLinesCommandId, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor), + handler: (accessor: ServicesAccessor) => modifySearchEditorContextLinesCommand(accessor, true), + primary: KeyMod.Alt | KeyCode.US_EQUAL +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: SearchEditorConstants.DecreaseSearchEditorContextLinesCommandId, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor), + handler: (accessor: ServicesAccessor) => modifySearchEditorContextLinesCommand(accessor, false), + primary: KeyMod.Alt | KeyCode.US_MINUS +}); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: SearchEditorConstants.SelectAllSearchEditorMatchesCommandId, weight: KeybindingWeight.WorkbenchContrib, @@ -162,9 +178,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ CommandsRegistry.registerCommand( SearchEditorConstants.CleanSearchEditorStateCommandId, (accessor: ServicesAccessor) => { - const activeControl = accessor.get(IEditorService).activeControl; - if (activeControl instanceof SearchEditor) { - activeControl.cleanState(); + const activeEditorPane = accessor.get(IEditorService).activeEditorPane; + if (activeEditorPane instanceof SearchEditor) { + activeEditorPane.cleanState(); } }); //#endregion @@ -183,9 +199,13 @@ registry.registerWorkbenchAction( SyncActionDescriptor.create(OpenSearchEditorAction, OpenSearchEditorAction.ID, OpenSearchEditorAction.LABEL), 'Search Editor: Open New Search Editor', category); +registry.registerWorkbenchAction( + SyncActionDescriptor.create(OpenSearchEditorToSideAction, OpenSearchEditorToSideAction.ID, OpenSearchEditorToSideAction.LABEL), + 'Search Editor: Open New Search Editor to Side', category); + registry.registerWorkbenchAction(SyncActionDescriptor.create(RerunSearchEditorSearchAction, RerunSearchEditorSearchAction.ID, RerunSearchEditorSearchAction.LABEL, { mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_R } }, ContextKeyExpr.and(SearchEditorConstants.InSearchEditor)), - 'Search Editor: Rerun', category); + 'Search Editor: Search Again', category); //#endregion diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index 523e97cdac5..5af24dbdcfb 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -49,6 +49,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; +import { alert } from 'vs/base/browser/ui/aria/aria'; const RESULT_LINE_REGEX = /^(\s+)(\d+)(:| )(\s+)(.*)$/; const FILE_LINE_REGEX = /^(\S.*):$/; @@ -294,10 +295,65 @@ export class SearchEditor extends BaseTextEditor { this.queryEditorWidget.toggleContextLines(); } + modifyContextLines(increase: boolean) { + this.queryEditorWidget.modifyContextLines(increase); + } + toggleQueryDetails() { this.toggleIncludesExcludes(); } + deleteResultBlock() { + const linesToDelete = new Set(); + + const selections = this.searchResultEditor.getSelections(); + const model = this.searchResultEditor.getModel(); + if (!(selections && model)) { return; } + + const maxLine = model.getLineCount(); + const minLine = 1; + + const deleteUp = (start: number) => { + for (let cursor = start; cursor >= minLine; cursor--) { + const line = model.getLineContent(cursor); + linesToDelete.add(cursor); + if (line[0] !== undefined && line[0] !== ' ') { + break; + } + } + }; + + const deleteDown = (start: number): number | undefined => { + linesToDelete.add(start); + for (let cursor = start + 1; cursor <= maxLine; cursor++) { + const line = model.getLineContent(cursor); + if (line[0] !== undefined && line[0] !== ' ') { + return cursor; + } + linesToDelete.add(cursor); + } + return; + }; + + const endingCursorLines: Array = []; + for (const selection of selections) { + const lineNumber = selection.startLineNumber; + endingCursorLines.push(deleteDown(lineNumber)); + deleteUp(lineNumber); + for (let inner = selection.startLineNumber; inner <= selection.endLineNumber; inner++) { + linesToDelete.add(inner); + } + } + + if (endingCursorLines.length === 0) { endingCursorLines.push(1); } + + const isDefined = (x: T | undefined): x is T => x !== undefined; + + model.pushEditOperations(this.searchResultEditor.getSelections(), + [...linesToDelete].map(line => ({ range: new Range(line, 1, line + 1, 1), text: '' })), + () => endingCursorLines.filter(isDefined).map(line => new Selection(line, 1, line, 1))); + } + cleanState() { this.getInput()?.setDirty(false); } @@ -325,6 +381,15 @@ export class SearchEditor extends BaseTextEditor { this.searchResultEditor.setSelection(matchRange); this.searchResultEditor.revealLineInCenterIfOutsideViewport(matchRange.startLineNumber); this.searchResultEditor.focus(); + + const matchLineText = model.getLineContent(matchRange.startLineNumber); + const matchText = model.getValueInRange(matchRange); + let file = ''; + for (let line = matchRange.startLineNumber; line >= 1; line--) { + let lineText = model.getValueInRange(new Range(line, 1, line, 2)); + if (lineText !== ' ') { file = model.getLineContent(line); break; } + } + alert(localize('searchResultItem', "Matched {0} at {1} in file {2}", matchText, matchLineText, file.slice(0, file.length - 1))); } focusNextResult() { @@ -360,7 +425,7 @@ export class SearchEditor extends BaseTextEditor { private readConfigFromWidget() { return { caseSensitive: this.queryEditorWidget.searchInput.getCaseSensitive(), - contextLines: this.queryEditorWidget.contextLines(), + contextLines: this.queryEditorWidget.getContextLines(), excludes: this.inputPatternExcludes.getValue(), includes: this.inputPatternIncludes.getValue(), query: this.queryEditorWidget.searchInput.getValue(), @@ -397,8 +462,8 @@ export class SearchEditor extends BaseTextEditor { _reason: 'searchEditor', extraFileResources: this.instantiationService.invokeFunction(getOutOfWorkspaceEditorResources), maxResults: 10000, - disregardIgnoreFiles: !config.useIgnores, - disregardExcludeSettings: !config.useIgnores, + disregardIgnoreFiles: !config.useIgnores || undefined, + disregardExcludeSettings: !config.useIgnores || undefined, excludePattern: config.excludes, includePattern: config.includes, previewOptions: { diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts index 464d2bb6e51..29d36746319 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts @@ -19,14 +19,14 @@ import * as Constants from 'vs/workbench/contrib/searchEditor/browser/constants' import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; import { getOrMakeSearchEditorInput, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; import { serializeSearchResultForEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; export const toggleSearchEditorCaseSensitiveCommand = (accessor: ServicesAccessor) => { const editorService = accessor.get(IEditorService); const input = editorService.activeEditor; if (input instanceof SearchEditorInput) { - (editorService.activeControl as SearchEditor).toggleCaseSensitive(); + (editorService.activeEditorPane as SearchEditor).toggleCaseSensitive(); } }; @@ -34,7 +34,7 @@ export const toggleSearchEditorWholeWordCommand = (accessor: ServicesAccessor) = const editorService = accessor.get(IEditorService); const input = editorService.activeEditor; if (input instanceof SearchEditorInput) { - (editorService.activeControl as SearchEditor).toggleWholeWords(); + (editorService.activeEditorPane as SearchEditor).toggleWholeWords(); } }; @@ -42,7 +42,7 @@ export const toggleSearchEditorRegexCommand = (accessor: ServicesAccessor) => { const editorService = accessor.get(IEditorService); const input = editorService.activeEditor; if (input instanceof SearchEditorInput) { - (editorService.activeControl as SearchEditor).toggleRegex(); + (editorService.activeEditorPane as SearchEditor).toggleRegex(); } }; @@ -50,7 +50,15 @@ export const toggleSearchEditorContextLinesCommand = (accessor: ServicesAccessor const editorService = accessor.get(IEditorService); const input = editorService.activeEditor; if (input instanceof SearchEditorInput) { - (editorService.activeControl as SearchEditor).toggleContextLines(); + (editorService.activeEditorPane as SearchEditor).toggleContextLines(); + } +}; + +export const modifySearchEditorContextLinesCommand = (accessor: ServicesAccessor, increase: boolean) => { + const editorService = accessor.get(IEditorService); + const input = editorService.activeEditor; + if (input instanceof SearchEditorInput) { + (editorService.activeEditorPane as SearchEditor).modifyContextLines(increase); } }; @@ -58,7 +66,7 @@ export const selectAllSearchEditorMatchesCommand = (accessor: ServicesAccessor) const editorService = accessor.get(IEditorService); const input = editorService.activeEditor; if (input instanceof SearchEditorInput) { - (editorService.activeControl as SearchEditor).focusAllResults(); + (editorService.activeEditorPane as SearchEditor).focusAllResults(); } }; @@ -87,6 +95,22 @@ export class OpenSearchEditorAction extends Action { } } +export class OpenSearchEditorToSideAction extends Action { + + static readonly ID: string = Constants.OpenNewEditorToSideCommandId; + static readonly LABEL = localize('search.openNewEditorToSide', "Open New Search Editor to Side"); + + constructor(id: string, label: string, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { + super(id, label, 'codicon-new-file'); + } + + async run() { + await this.instantiationService.invokeFunction(openNewSearchEditor, true); + } +} + export class OpenResultsInEditorAction extends Action { static readonly ID: string = Constants.OpenInEditorCommandId; @@ -129,36 +153,36 @@ export class RerunSearchEditorSearchAction extends Action { async run() { const input = this.editorService.activeEditor; if (input instanceof SearchEditorInput) { - (this.editorService.activeControl as SearchEditor).triggerSearch({ resetCursor: false }); + (this.editorService.activeEditorPane as SearchEditor).triggerSearch({ resetCursor: false }); } } } const openNewSearchEditor = - async (accessor: ServicesAccessor) => { + async (accessor: ServicesAccessor, toSide = false) => { const editorService = accessor.get(IEditorService); const telemetryService = accessor.get(ITelemetryService); const instantiationService = accessor.get(IInstantiationService); const configurationService = accessor.get(IConfigurationService); - const activeEditor = editorService.activeTextEditorWidget; + const activeEditorControl = editorService.activeTextEditorControl; let activeModel: ICodeEditor | undefined; let selected = ''; - if (activeEditor) { - if (isDiffEditor(activeEditor)) { - if (activeEditor.getOriginalEditor().hasTextFocus()) { - activeModel = activeEditor.getOriginalEditor(); + if (activeEditorControl) { + if (isDiffEditor(activeEditorControl)) { + if (activeEditorControl.getOriginalEditor().hasTextFocus()) { + activeModel = activeEditorControl.getOriginalEditor(); } else { - activeModel = activeEditor.getModifiedEditor(); + activeModel = activeEditorControl.getModifiedEditor(); } } else { - activeModel = activeEditor as ICodeEditor; + activeModel = activeEditorControl as ICodeEditor; } const selection = activeModel?.getSelection(); selected = (selection && activeModel?.getModel()?.getValueInRange(selection)) ?? ''; } else { if (editorService.activeEditor instanceof SearchEditorInput) { - const active = editorService.activeControl as SearchEditor; + const active = editorService.activeEditorPane as SearchEditor; selected = active.getSelected(); } } @@ -166,7 +190,7 @@ const openNewSearchEditor = telemetryService.publicLog2('searchEditor/openNewSearchEditor'); const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { config: { query: selected } }); - const editor = await editorService.openEditor(input, { pinned: true }) as SearchEditor; + const editor = await editorService.openEditor(input, { pinned: true }, toSide ? SIDE_GROUP : ACTIVE_GROUP) as SearchEditor; if (selected && configurationService.getValue('search').searchOnType) { editor.triggerSearch(); diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts index 44b819f8abc..b5b76186eee 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts @@ -53,7 +53,7 @@ export class SearchEditorInput extends EditorInput { private _cachedContentsModel: ITextModel | undefined; private _cachedConfig?: SearchConfiguration; - private readonly _onDidChangeContent = new Emitter(); + private readonly _onDidChangeContent = this._register(new Emitter()); readonly onDidChangeContent: Event = this._onDidChangeContent.event; private oldDecorationsIDs: string[] = []; @@ -101,7 +101,6 @@ export class SearchEditorInput extends EditorInput { this.contentsModel = modelLoader.then(({ contentsModel }) => contentsModel); this.headerModel = modelLoader.then(({ headerModel }) => headerModel); - const input = this; const workingCopyAdapter = new class implements IWorkingCopy { readonly resource = input.resource; @@ -112,10 +111,10 @@ export class SearchEditorInput extends EditorInput { isDirty(): boolean { return input.isDirty(); } backup(): Promise { return input.backup(); } save(options?: ISaveOptions): Promise { return input.save(0, options).then(editor => !!editor); } - revert(options?: IRevertOptions): Promise { return input.revert(0, options); } + revert(options?: IRevertOptions): Promise { return input.revert(0, options); } }; - this.workingCopyService.registerWorkingCopy(workingCopyAdapter); + this._register(this.workingCopyService.registerWorkingCopy(workingCopyAdapter)); } async save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { @@ -249,6 +248,7 @@ export class SearchEditorInput extends EditorInput { public getMatchRanges(): Range[] { return (this._cachedContentsModel?.getAllDecorations() ?? []) .filter(decoration => decoration.options.className === SearchEditorFindMatchClass) + .filter(({ range }) => !(range.startColumn === 1 && range.endColumn === 1)) .map(({ range }) => range); } @@ -261,7 +261,6 @@ export class SearchEditorInput extends EditorInput { // TODO: this should actually revert the contents. But it needs to set dirty false. super.revert(group, options); this.setDirty(false); - return true; } private async backup(): Promise { diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts index 038519308e4..91d818eff12 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts @@ -22,11 +22,10 @@ const translateRangeLines = (range: Range) => new Range(range.startLineNumber + n, range.startColumn, range.endLineNumber + n, range.endColumn); -const matchToSearchResultFormat = (match: Match): { line: string, ranges: Range[], lineNumber: string }[] => { +const matchToSearchResultFormat = (match: Match, longestLineNumber: number): { line: string, ranges: Range[], lineNumber: string }[] => { const getLinePrefix = (i: number) => `${match.range().startLineNumber + i}`; const fullMatchLines = match.fullPreviewLines(); - const largestPrefixSize = fullMatchLines.reduce((largest, _, i) => Math.max(getLinePrefix(i).length, largest), 0); const results: { line: string, ranges: Range[], lineNumber: string }[] = []; @@ -34,8 +33,8 @@ const matchToSearchResultFormat = (match: Match): { line: string, ranges: Range[ fullMatchLines .forEach((sourceLine, i) => { const lineNumber = getLinePrefix(i); - const paddingStr = repeat(' ', largestPrefixSize - lineNumber.length); - const prefix = ` ${lineNumber}: ${paddingStr}`; + const paddingStr = repeat(' ', longestLineNumber - lineNumber.length); + const prefix = ` ${paddingStr}${lineNumber}: `; const prefixOffset = prefix.length; const line = (prefix + sourceLine).replace(/\r?\n?$/, ''); @@ -60,9 +59,9 @@ const matchToSearchResultFormat = (match: Match): { line: string, ranges: Range[ type SearchResultSerialization = { text: string[], matchRanges: Range[] }; function fileMatchToSearchResultFormat(fileMatch: FileMatch, labelFormatter: (x: URI) => string): SearchResultSerialization { - const serializedMatches = flatten(fileMatch.matches() - .sort(searchMatchComparer) - .map(match => matchToSearchResultFormat(match))); + const sortedMatches = fileMatch.matches().sort(searchMatchComparer); + const longestLineNumber = sortedMatches[sortedMatches.length - 1].range().endLineNumber.toString().length; + const serializedMatches = flatten(sortedMatches.map(match => matchToSearchResultFormat(match, longestLineNumber))); const uriString = labelFormatter(fileMatch.resource); let text: string[] = [`${uriString}:`]; @@ -84,7 +83,7 @@ function fileMatchToSearchResultFormat(fileMatch: FileMatch, labelFormatter: (x: if (lastLine !== undefined && lineNumber !== lastLine + 1) { text.push(''); } - text.push(` ${lineNumber} ${line}`); + text.push(` ${repeat(' ', longestLineNumber - `${lineNumber}`.length)}${lineNumber} ${line}`); lastLine = lineNumber; } diff --git a/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts b/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts index e2073066f37..755b5b40fed 100644 --- a/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts +++ b/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts @@ -12,7 +12,6 @@ import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution'; -import { values } from 'vs/base/common/map'; import { IQuickPickItem, IQuickInputService, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -73,7 +72,7 @@ async function computePicks(snippetService: ISnippetsService, envService: IEnvir filepath: file.location, description: names.size === 0 ? nls.localize('global.scope', "(global)") - : nls.localize('global.1', "({0})", values(names).join(', ')) + : nls.localize('global.1', "({0})", [...names].join(', ')) }); } else { @@ -88,7 +87,7 @@ async function computePicks(snippetService: ISnippetsService, envService: IEnvir } } - const dir = joinPath(envService.userRoamingDataHome, 'snippets'); + const dir = envService.snippetsHome; for (const mode of modeService.getRegisteredModes()) { const label = modeService.getLanguageName(mode); if (label && !seen.has(mode)) { @@ -220,7 +219,7 @@ CommandsRegistry.registerCommand(id, async (accessor): Promise => { const globalSnippetPicks: SnippetPick[] = [{ scope: nls.localize('new.global_scope', 'global'), label: nls.localize('new.global', "New Global Snippets file..."), - uri: joinPath(envService.userRoamingDataHome, 'snippets') + uri: envService.snippetsHome }]; const workspaceSnippetPicks: SnippetPick[] = []; diff --git a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts index 2e834ea32dc..e9a0b7e5388 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { MarkdownString } from 'vs/base/common/htmlContent'; -import { compare, startsWith } from 'vs/base/common/strings'; +import { compare } from 'vs/base/common/strings'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; @@ -55,8 +55,6 @@ export class SnippetCompletion implements CompletionItem { export class SnippetCompletionProvider implements CompletionItemProvider { - private static readonly _maxPrefix = 10000; - readonly _debugDisplayName = 'snippetCompletions'; constructor( @@ -66,92 +64,84 @@ export class SnippetCompletionProvider implements CompletionItemProvider { // } - provideCompletionItems(model: ITextModel, position: Position, context: CompletionContext): Promise | undefined { - - if (position.column >= SnippetCompletionProvider._maxPrefix) { - return undefined; - } + async provideCompletionItems(model: ITextModel, position: Position, context: CompletionContext): Promise { if (context.triggerKind === CompletionTriggerKind.TriggerCharacter && context.triggerCharacter === ' ') { // no snippets when suggestions have been triggered by space - return undefined; + return { suggestions: [] }; } const languageId = this._getLanguageIdAtPosition(model, position); - return this._snippets.getSnippets(languageId).then(snippets => { + const snippets = await this._snippets.getSnippets(languageId); - let suggestions: SnippetCompletion[]; - let pos = { lineNumber: position.lineNumber, column: 1 }; - let lineOffsets: number[] = []; - const lineContent = model.getLineContent(position.lineNumber); - const linePrefixLow = lineContent.substr(0, position.column - 1).toLowerCase(); - let endsInWhitespace = linePrefixLow.match(/\s$/); + let pos = { lineNumber: position.lineNumber, column: 1 }; + let lineOffsets: number[] = []; + const lineContent = model.getLineContent(position.lineNumber).toLowerCase(); + const endsInWhitespace = /\s/.test(lineContent[position.column - 2]); - while (pos.column < position.column) { - let word = model.getWordAtPosition(pos); - if (word) { - // at a word - lineOffsets.push(word.startColumn - 1); - pos.column = word.endColumn + 1; - if (word.endColumn - 1 < linePrefixLow.length && !/\s/.test(linePrefixLow[word.endColumn - 1])) { - lineOffsets.push(word.endColumn - 1); - } - } - else if (!/\s/.test(linePrefixLow[pos.column - 1])) { - // at a none-whitespace character - lineOffsets.push(pos.column - 1); - pos.column += 1; - } - else { - // always advance! - pos.column += 1; + while (pos.column < position.column) { + let word = model.getWordAtPosition(pos); + if (word) { + // at a word + lineOffsets.push(word.startColumn - 1); + pos.column = word.endColumn + 1; + if (word.endColumn < position.column && !/\s/.test(lineContent[word.endColumn - 1])) { + lineOffsets.push(word.endColumn - 1); } } - - const lineSuffixLow = lineContent.substr(position.column - 1).toLowerCase(); - let availableSnippets = new Set(); - snippets.forEach(availableSnippets.add, availableSnippets); - suggestions = []; - for (let start of lineOffsets) { - availableSnippets.forEach(snippet => { - if (isPatternInWord(linePrefixLow, start, linePrefixLow.length, snippet.prefixLow, 0, snippet.prefixLow.length)) { - const snippetPrefixSubstr = snippet.prefixLow.substr(linePrefixLow.length - start); - const endColumn = startsWith(lineSuffixLow, snippetPrefixSubstr) ? position.column + snippetPrefixSubstr.length : position.column; - const replace = Range.fromPositions(position.delta(0, -(linePrefixLow.length - start)), { lineNumber: position.lineNumber, column: endColumn }); - const insert = replace.setEndPosition(position.lineNumber, position.column); - - suggestions.push(new SnippetCompletion(snippet, { replace, insert })); - availableSnippets.delete(snippet); - } - }); + else if (!/\s/.test(lineContent[pos.column - 1])) { + // at a none-whitespace character + lineOffsets.push(pos.column - 1); + pos.column += 1; } - if (endsInWhitespace || lineOffsets.length === 0) { - // add remaing snippets when the current prefix ends in whitespace or when no - // interesting positions have been found - availableSnippets.forEach(snippet => { - let insert = Range.fromPositions(position); - let replace = startsWith(lineSuffixLow, snippet.prefixLow) ? insert.setEndPosition(position.lineNumber, position.column + snippet.prefixLow.length) : insert; + else { + // always advance! + pos.column += 1; + } + } + + const availableSnippets = new Set(snippets); + const suggestions: SnippetCompletion[] = []; + + for (let start of lineOffsets) { + availableSnippets.forEach(snippet => { + if (isPatternInWord(lineContent, start, position.column - 1, snippet.prefixLow, 0, snippet.prefixLow.length)) { + const snippetPrefixSubstr = snippet.prefixLow.substr(position.column - (1 + start)); + const endColumn = lineContent.indexOf(snippetPrefixSubstr, position.column - 1) >= 0 ? position.column + snippetPrefixSubstr.length : position.column; + const replace = Range.fromPositions(position.delta(0, -(position.column - (1 + start))), { lineNumber: position.lineNumber, column: endColumn }); + const insert = replace.setEndPosition(position.lineNumber, position.column); + suggestions.push(new SnippetCompletion(snippet, { replace, insert })); - }); - } - - - // dismbiguate suggestions with same labels - suggestions.sort(SnippetCompletion.compareByLabel); - for (let i = 0; i < suggestions.length; i++) { - let item = suggestions[i]; - let to = i + 1; - for (; to < suggestions.length && item.label === suggestions[to].label; to++) { - suggestions[to].label.name = localize('snippetSuggest.longLabel', "{0}, {1}", suggestions[to].label.name, suggestions[to].snippet.name); + availableSnippets.delete(snippet); } - if (to > i + 1) { - suggestions[i].label.name = localize('snippetSuggest.longLabel', "{0}, {1}", suggestions[i].label.name, suggestions[i].snippet.name); - i = to; - } - } + }); + } + if (endsInWhitespace || lineOffsets.length === 0) { + // add remaing snippets when the current prefix ends in whitespace or when no + // interesting positions have been found + availableSnippets.forEach(snippet => { + const insert = Range.fromPositions(position); + const replace = lineContent.indexOf(snippet.prefixLow, position.column - 1) >= 0 ? insert.setEndPosition(position.lineNumber, position.column + snippet.prefixLow.length) : insert; + suggestions.push(new SnippetCompletion(snippet, { replace, insert })); + }); + } - return { suggestions }; - }); + + // dismbiguate suggestions with same labels + suggestions.sort(SnippetCompletion.compareByLabel); + for (let i = 0; i < suggestions.length; i++) { + let item = suggestions[i]; + let to = i + 1; + for (; to < suggestions.length && item.label === suggestions[to].label; to++) { + suggestions[to].label.name = localize('snippetSuggest.longLabel', "{0}, {1}", suggestions[to].label.name, suggestions[to].snippet.name); + } + if (to > i + 1) { + suggestions[i].label.name = localize('snippetSuggest.longLabel', "{0}, {1}", suggestions[i].label.name, suggestions[i].snippet.name); + i = to; + } + } + + return { suggestions }; } resolveCompletionItem(_model: ITextModel, _position: Position, item: CompletionItem): CompletionItem { diff --git a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts index 775835ce4ec..fd3f479462b 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts @@ -17,7 +17,7 @@ export interface ISnippetsService { _serviceBrand: undefined; - getSnippetFiles(): Promise; + getSnippetFiles(): Promise>; getSnippets(languageId: LanguageId): Promise; diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts index 93fcf1bc0af..0355799e9ad 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts @@ -5,7 +5,6 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { combinedDisposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { values } from 'vs/base/common/map'; import * as resources from 'vs/base/common/resources'; import { endsWith, isFalsyOrWhitespace } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; @@ -162,8 +161,9 @@ class SnippetsService implements ISnippetsService { return Promise.all(promises); } - getSnippetFiles(): Promise { - return this._joinSnippets().then(() => values(this._files)); + async getSnippetFiles(): Promise> { + await this._joinSnippets(); + return this._files.values(); } getSnippets(languageId: LanguageId): Promise { @@ -289,7 +289,7 @@ class SnippetsService implements ISnippetsService { } private _initUserSnippets(): Promise { - const userSnippetsFolder = resources.joinPath(this._environmentService.userRoamingDataHome, 'snippets'); + const userSnippetsFolder = this._environmentService.snippetsHome; return this._fileService.createFolder(userSnippetsFolder).then(() => this._initFolderSnippets(SnippetSource.User, userSnippetsFolder, this._disposables)); } diff --git a/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts index 15a23398c98..8fe4ddd1fc2 100644 --- a/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts +++ b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts @@ -24,7 +24,7 @@ import { URI } from 'vs/base/common/uri'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import * as perf from 'vs/base/common/performance'; -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { assertIsDefined } from 'vs/base/common/types'; class PartsSplash { @@ -41,8 +41,7 @@ class PartsSplash { @IThemeService private readonly _themeService: IThemeService, @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, @ITextFileService private readonly _textFileService: ITextFileService, - @IWorkbenchEnvironmentService private readonly _envService: IWorkbenchEnvironmentService, - @IElectronEnvironmentService private readonly _electronEnvService: IElectronEnvironmentService, + @IWorkbenchEnvironmentService private readonly _envService: INativeWorkbenchEnvironmentService, @ILifecycleService lifecycleService: ILifecycleService, @IEditorGroupsService editorGroupsService: IEditorGroupsService, @IConfigurationService configService: IConfigurationService, @@ -63,7 +62,7 @@ class PartsSplash { } }, this, this._disposables); - _themeService.onThemeChange(_ => { + _themeService.onDidColorThemeChange(_ => { this._savePartsSplash(); }, this, this._disposables); } @@ -73,7 +72,7 @@ class PartsSplash { } private _savePartsSplash() { - const baseTheme = getThemeTypeSelector(this._themeService.getTheme().type); + const baseTheme = getThemeTypeSelector(this._themeService.getColorTheme().type); const colorInfo = { foreground: this._getThemeColor(foreground), editorBackground: this._getThemeColor(editorBackground), @@ -111,14 +110,14 @@ class PartsSplash { this._lastBackground = colorInfo.editorBackground; // the color needs to be in hex - const backgroundColor = this._themeService.getTheme().getColor(editorBackground) || themes.WORKBENCH_BACKGROUND(this._themeService.getTheme()); + const backgroundColor = this._themeService.getColorTheme().getColor(editorBackground) || themes.WORKBENCH_BACKGROUND(this._themeService.getColorTheme()); const payload = JSON.stringify({ baseTheme, background: Color.Format.CSS.formatHex(backgroundColor) }); - ipc.send('vscode:changeColorTheme', this._electronEnvService.windowId, payload); + ipc.send('vscode:changeColorTheme', this._envService.configuration.windowId, payload); } } private _getThemeColor(id: ColorIdentifier): string | undefined { - const theme = this._themeService.getTheme(); + const theme = this._themeService.getColorTheme(); const color = theme.getColor(id); return color ? color.toString() : undefined; } diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 27418989dde..ad11459555e 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -74,18 +74,16 @@ import { IRemotePathService } from 'vs/workbench/services/path/common/remotePath import { format } from 'vs/base/common/jsonFormatter'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { applyEdits } from 'vs/base/common/jsonEdit'; -import { ITextEditor } from 'vs/workbench/common/editor'; +import { ITextEditorPane } from 'vs/workbench/common/editor'; import { ITextEditorSelection, TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { find } from 'vs/base/common/arrays'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { IViewsService } from 'vs/workbench/common/views'; -import { ProviderProgressMananger } from 'vs/workbench/contrib/tasks/browser/providerProgressManager'; +import { IViewsService, IViewDescriptorService } from 'vs/workbench/common/views'; +import { isWorkspaceFolder, TaskQuickPickEntry, QUICKOPEN_DETAIL_CONFIG, TaskQuickPick, QUICKOPEN_SKIP_CONFIG } from 'vs/workbench/contrib/tasks/browser/taskQuickPick'; const QUICKOPEN_HISTORY_LIMIT_CONFIG = 'task.quickOpen.history'; -const QUICKOPEN_DETAIL_CONFIG = 'task.quickOpen.detail'; const PROBLEM_MATCHER_NEVER_CONFIG = 'task.problemMatchers.neverPrompt'; -const QUICKOPEN_SKIP_CONFIG = 'task.quickOpen.skip'; export namespace ConfigureTaskAction { export const ID = 'workbench.action.tasks.configureTaskRunner'; @@ -137,10 +135,6 @@ interface TaskCustomizationTelemetryEvent { properties: string[]; } -function isWorkspaceFolder(folder: IWorkspace | IWorkspaceFolder): folder is IWorkspaceFolder { - return 'uri' in folder; -} - class TaskMap { private _store: Map = new Map(); @@ -186,10 +180,6 @@ class TaskMap { } } -interface TaskQuickPickEntry extends IQuickPickItem { - task: Task | undefined | null; -} - interface ProblemMatcherDisableMetrics { type: string; } @@ -201,6 +191,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer // private static autoDetectTelemetryName: string = 'taskServer.autoDetect'; private static readonly RecentlyUsedTasks_Key = 'workbench.tasks.recentlyUsedTasks'; + private static readonly RecentlyUsedTasks_KeyV2 = 'workbench.tasks.recentlyUsedTasks2'; private static readonly IgnoreTask010DonotShowAgain_key = 'workbench.tasks.ignoreTask010Shown'; private static CustomizationTelemetryEventName: string = 'taskService.customize'; @@ -219,13 +210,13 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private _providers: Map; private _providerTypes: Map; protected _taskSystemInfos: Map; - private _providerProgressManager: ProviderProgressMananger | undefined; protected _workspaceTasksPromise?: Promise>; protected _areJsonTasksSupportedPromise: Promise = Promise.resolve(false); protected _taskSystem?: ITaskSystem; protected _taskSystemListener?: IDisposable; + private _recentlyUsedTasksV1: LRUCache | undefined; private _recentlyUsedTasks: LRUCache | undefined; protected _taskRunningState: IContextKey; @@ -262,7 +253,8 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer @ITerminalInstanceService private readonly terminalInstanceService: ITerminalInstanceService, @IRemotePathService private readonly remotePathService: IRemotePathService, @ITextModelService private readonly textModelResolverService: ITextModelService, - @IPreferencesService private readonly preferencesService: IPreferencesService + @IPreferencesService private readonly preferencesService: IPreferencesService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService ) { super(); @@ -560,39 +552,42 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); } - protected abstract versionAndEngineCompatible(filter?: TaskFilter): boolean; - - private tasksAndGroupedTasks(filter?: TaskFilter): { tasks: Promise, grouped: Promise } { - if (!this.versionAndEngineCompatible(filter)) { - return { tasks: Promise.resolve([]), grouped: Promise.resolve(new TaskMap()) }; - } - const grouped = this.getGroupedTasks(filter ? filter.type : undefined); - const tasks = grouped.then((map) => { - if (!filter || !filter.type) { - return map.all(); + public async tryResolveTask(configuringTask: ConfiguringTask): Promise { + let matchingProvider: ITaskProvider | undefined; + for (const [handle, provider] of this._providers) { + if (configuringTask.type === this._providerTypes.get(handle)) { + matchingProvider = provider; + break; } - let result: Task[] = []; - map.forEach((tasks) => { - for (let task of tasks) { - if (ContributedTask.is(task) && task.defines.type === filter.type) { - result.push(task); - } else if (CustomTask.is(task)) { - if (task.type === filter.type) { - result.push(task); - } else { - let customizes = task.customizes(); - if (customizes && customizes.type === filter.type) { - result.push(task); - } - } - } - } - }); - return result; - }); - return { tasks, grouped }; + } + + if (!matchingProvider) { + return; + } + + // Try to resolve the task first + try { + const resolvedTask = await matchingProvider.resolveTask(configuringTask); + if (resolvedTask && (resolvedTask._id === configuringTask._id)) { + return TaskConfig.createCustomTask(resolvedTask, configuringTask); + } + } catch (error) { + // Ignore errors. The task could not be provided by any of the providers. + } + + // The task couldn't be resolved. Instead, use the less efficient provideTask. + const tasks = await this.tasks({ type: configuringTask.type }); + for (const task of tasks) { + if (task._id === configuringTask._id) { + return TaskConfig.createCustomTask(task, configuringTask); + } + } + + return; } + protected abstract versionAndEngineCompatible(filter?: TaskFilter): boolean; + public tasks(filter?: TaskFilter): Promise { if (!this.versionAndEngineCompatible(filter)) { return Promise.resolve([]); @@ -622,6 +617,19 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); } + public taskTypes(): string[] { + const types: string[] = []; + if (this.isProvideTasksEnabled()) { + for (const [handle] of this._providers) { + const type = this._providerTypes.get(handle); + if (type) { + types.push(type); + } + } + } + return types; + } + public createSorter(): TaskSorter { return new TaskSorter(this.contextService.getWorkspace() ? this.contextService.getWorkspace().folders : []); } @@ -647,12 +655,12 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return Promise.resolve(this._taskSystem.getBusyTasks()); } - public getRecentlyUsedTasks(): LRUCache { - if (this._recentlyUsedTasks) { - return this._recentlyUsedTasks; + public getRecentlyUsedTasksV1(): LRUCache { + if (this._recentlyUsedTasksV1) { + return this._recentlyUsedTasksV1; } const quickOpenHistoryLimit = this.configurationService.getValue(QUICKOPEN_HISTORY_LIMIT_CONFIG); - this._recentlyUsedTasks = new LRUCache(quickOpenHistoryLimit); + this._recentlyUsedTasksV1 = new LRUCache(quickOpenHistoryLimit); let storageValue = this.storageService.get(AbstractTaskService.RecentlyUsedTasks_Key, StorageScope.WORKSPACE); if (storageValue) { @@ -660,7 +668,30 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer let values: string[] = JSON.parse(storageValue); if (Array.isArray(values)) { for (let value of values) { - this._recentlyUsedTasks.set(value, value); + this._recentlyUsedTasksV1.set(value, value); + } + } + } catch (error) { + // Ignore. We use the empty result + } + } + return this._recentlyUsedTasksV1; + } + + public getRecentlyUsedTasks(): LRUCache { + if (this._recentlyUsedTasks) { + return this._recentlyUsedTasks; + } + const quickOpenHistoryLimit = this.configurationService.getValue(QUICKOPEN_HISTORY_LIMIT_CONFIG); + this._recentlyUsedTasks = new LRUCache(quickOpenHistoryLimit); + + let storageValue = this.storageService.get(AbstractTaskService.RecentlyUsedTasks_KeyV2, StorageScope.WORKSPACE); + if (storageValue) { + try { + let values: [string, string][] = JSON.parse(storageValue); + if (Array.isArray(values)) { + for (let value of values) { + this._recentlyUsedTasks.set(value[0], value[1]); } } } catch (error) { @@ -670,6 +701,59 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return this._recentlyUsedTasks; } + private getFolderFromTaskKey(key: string): string | undefined { + const keyValue: { folder: string | undefined } = JSON.parse(key); + return keyValue.folder; + } + + public async readRecentTasks(): Promise<(Task | ConfiguringTask)[]> { + const folderMap: IStringDictionary = Object.create(null); + this.workspaceFolders.forEach(folder => { + folderMap[folder.uri.toString()] = folder; + }); + const folderToTasksMap: Map = new Map(); + const recentlyUsedTasks = this.getRecentlyUsedTasks(); + const tasks: (Task | ConfiguringTask)[] = []; + for (const key of recentlyUsedTasks.keys()) { + const folder = this.getFolderFromTaskKey(key); + const task = JSON.parse(recentlyUsedTasks.get(key)!); + if (folder && !folderToTasksMap.has(folder)) { + folderToTasksMap.set(folder, []); + } + if (folder && (folderMap[folder] || (folder === USER_TASKS_GROUP_KEY)) && task) { + folderToTasksMap.get(folder).push(task); + } + } + const readTasksMap: Map = new Map(); + for (const key of folderToTasksMap.keys()) { + let custom: CustomTask[] = []; + let customized: IStringDictionary = Object.create(null); + await this.computeTasksForSingleConfig(folderMap[key] ?? this.workspaceFolders[0], { + version: '2.0.0', + tasks: folderToTasksMap.get(key) + }, TaskRunSource.System, custom, customized, folderMap[key] ? TaskConfig.TaskConfigSource.TasksJson : TaskConfig.TaskConfigSource.User, true); + custom.forEach(task => { + const taskKey = task.getRecentlyUsedKey(); + if (taskKey) { + readTasksMap.set(taskKey, task); + } + }); + for (const configuration in customized) { + const taskKey = customized[configuration].getRecentlyUsedKey(); + if (taskKey) { + readTasksMap.set(taskKey, customized[configuration]); + } + } + } + + for (const key of recentlyUsedTasks.keys()) { + if (readTasksMap.has(key)) { + tasks.push(readTasksMap.get(key)!); + } + } + return tasks; + } + private setTaskLRUCacheLimit() { const quickOpenHistoryLimit = this.configurationService.getValue(QUICKOPEN_HISTORY_LIMIT_CONFIG); if (this._recentlyUsedTasks) { @@ -677,13 +761,16 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } - private setRecentlyUsedTask(key: string): void { - this.getRecentlyUsedTasks().set(key, key); - this.saveRecentlyUsedTasks(); + private setRecentlyUsedTask(task: Task): void { + const key = task.getRecentlyUsedKey(); + if (!InMemoryTask.is(task) && key) { + this.getRecentlyUsedTasks().set(key, JSON.stringify(this.createCustomizableTask(task))); + this.saveRecentlyUsedTasks(); + } } private saveRecentlyUsedTasks(): void { - if (!this._taskSystem || !this._recentlyUsedTasks) { + if (!this._recentlyUsedTasks) { return; } const quickOpenHistoryLimit = this.configurationService.getValue(QUICKOPEN_HISTORY_LIMIT_CONFIG); @@ -691,11 +778,15 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (quickOpenHistoryLimit === 0) { return; } - let values = this._recentlyUsedTasks.values(); - if (values.length > quickOpenHistoryLimit) { - values = values.slice(0, quickOpenHistoryLimit); + let keys = this._recentlyUsedTasks.keys(); + if (keys.length > quickOpenHistoryLimit) { + keys = keys.slice(0, quickOpenHistoryLimit); } - this.storageService.store(AbstractTaskService.RecentlyUsedTasks_Key, JSON.stringify(values), StorageScope.WORKSPACE); + const keyValues: [string, string][] = []; + for (const key of keys) { + keyValues.push([key, this._recentlyUsedTasks.get(key)!]); + } + this.storageService.store(AbstractTaskService.RecentlyUsedTasks_KeyV2, JSON.stringify(keyValues), StorageScope.WORKSPACE); } private openDocumentation(): void { @@ -736,22 +827,22 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); } - public run(task: Task | undefined, options?: ProblemMatcherRunOptions, runSource: TaskRunSource = TaskRunSource.System, grouped?: Promise): Promise { + public run(task: Task | undefined, options?: ProblemMatcherRunOptions, runSource: TaskRunSource = TaskRunSource.System): Promise { if (!task) { throw new TaskError(Severity.Info, nls.localize('TaskServer.noTask', 'Task to execute is undefined'), TaskErrors.TaskNotFound); } - return (grouped ?? this.getGroupedTasks()).then((grouped) => { - let resolver = this.createResolver(grouped); + + return new Promise(async (resolve) => { + let resolver = this.createResolver(); if (options && options.attachProblemMatcher && this.shouldAttachProblemMatcher(task) && !InMemoryTask.is(task)) { - return this.attachProblemMatcher(task).then((toExecute) => { - if (toExecute) { - return this.executeTask(toExecute, resolver); - } else { - return Promise.resolve(undefined); - } - }); + const toExecute = await this.attachProblemMatcher(task); + if (toExecute) { + resolve(this.executeTask(toExecute, resolver)); + } else { + resolve(undefined); + } } - return this.executeTask(task, resolver); + resolve(this.executeTask(task, resolver)); }).then((value) => { if (runSource === TaskRunSource.User) { this.getWorkspaceTasks().then(workspaceTasks => { @@ -945,7 +1036,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return false; } - private openEditorAtTask(resource: URI | undefined, task: TaskConfig.CustomTask | TaskConfig.ConfiguringTask | string | undefined): Promise { + private openEditorAtTask(resource: URI | undefined, task: TaskConfig.CustomTask | TaskConfig.ConfiguringTask | string | undefined): Promise { if (resource === undefined) { return Promise.resolve(undefined); } @@ -999,23 +1090,10 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); } - public customize(task: ContributedTask | CustomTask, properties?: CustomizationProperties, openConfig?: boolean): Promise { - const workspaceFolder = task.getWorkspaceFolder(); - if (!workspaceFolder) { - return Promise.resolve(undefined); - } - let configuration = this.getConfiguration(workspaceFolder, task._source.kind); - if (configuration.hasParseErrors) { - this.notificationService.warn(nls.localize('customizeParseErrors', 'The current task configuration has errors. Please fix the errors first before customizing a task.')); - return Promise.resolve(undefined); - } - - let fileConfig = configuration.config; - let index: number | undefined; + private createCustomizableTask(task: ContributedTask | CustomTask): TaskConfig.CustomTask | TaskConfig.ConfiguringTask | undefined { let toCustomize: TaskConfig.CustomTask | TaskConfig.ConfiguringTask | undefined; let taskConfig = CustomTask.is(task) ? task._source.config : undefined; if (taskConfig && taskConfig.element) { - index = taskConfig.index; toCustomize = { ...(taskConfig.element) }; } else if (ContributedTask.is(task)) { toCustomize = { @@ -1031,8 +1109,37 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } if (!toCustomize) { + return undefined; + } + if (toCustomize.problemMatcher === undefined && task.configurationProperties.problemMatchers === undefined || (task.configurationProperties.problemMatchers && task.configurationProperties.problemMatchers.length === 0)) { + toCustomize.problemMatcher = []; + } + if (task._source.label !== 'Workspace') { + toCustomize.label = task.configurationProperties.identifier; + } else { + toCustomize.label = task._label; + } + toCustomize.detail = task.configurationProperties.detail; + return toCustomize; + } + + public customize(task: ContributedTask | CustomTask, properties?: CustomizationProperties, openConfig?: boolean): Promise { + const workspaceFolder = task.getWorkspaceFolder(); + if (!workspaceFolder) { return Promise.resolve(undefined); } + let configuration = this.getConfiguration(workspaceFolder, task._source.kind); + if (configuration.hasParseErrors) { + this.notificationService.warn(nls.localize('customizeParseErrors', 'The current task configuration has errors. Please fix the errors first before customizing a task.')); + return Promise.resolve(undefined); + } + + let fileConfig = configuration.config; + const toCustomize = this.createCustomizableTask(task); + if (!toCustomize) { + return Promise.resolve(undefined); + } + const index: number | undefined = CustomTask.is(task) ? task._source.config.index : undefined; if (properties) { for (let property of Object.getOwnPropertyNames(properties)) { let value = (properties)[property]; @@ -1040,10 +1147,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer (toCustomize)[property] = value; } } - } else { - if (toCustomize.problemMatcher === undefined && task.configurationProperties.problemMatchers === undefined || (task.configurationProperties.problemMatchers && task.configurationProperties.problemMatchers.length === 0)) { - toCustomize.problemMatcher = []; - } } let promise: Promise | undefined; @@ -1137,7 +1240,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } - private getResourceForTask(task: CustomTask | ContributedTask): URI { + private getResourceForTask(task: CustomTask | ConfiguringTask | ContributedTask): URI { if (CustomTask.is(task)) { let uri = this.getResourceForKind(task._source.kind); if (!uri) { @@ -1154,7 +1257,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } - public openConfig(task: CustomTask | undefined): Promise { + public openConfig(task: CustomTask | ConfiguringTask | undefined): Promise { let resource: URI | undefined; if (task) { resource = this.getResourceForTask(task); @@ -1200,7 +1303,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } }); let resolver: ITaskResolver = { - resolve: (uri: URI | string, alias: string) => { + resolve: async (uri: URI | string, alias: string) => { let data = resolverData.get(typeof uri === 'string' ? uri : uri.toString()); if (!data) { return undefined; @@ -1240,34 +1343,37 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } - private createResolver(grouped: TaskMap): ITaskResolver { + private createResolver(grouped?: TaskMap): ITaskResolver { interface ResolverData { label: Map; identifier: Map; taskIdentifier: Map; } - let resolverData: Map = new Map(); - grouped.forEach((tasks, folder) => { - let data = resolverData.get(folder); - if (!data) { - data = { label: new Map(), identifier: new Map(), taskIdentifier: new Map() }; - resolverData.set(folder, data); - } - for (let task of tasks) { - data.label.set(task._label, task); - if (task.configurationProperties.identifier) { - data.identifier.set(task.configurationProperties.identifier, task); - } - let keyedIdentifier = task.getDefinition(true); - if (keyedIdentifier !== undefined) { - data.taskIdentifier.set(keyedIdentifier._key, task); - } - } - }); + let resolverData: Map | undefined; return { - resolve: (uri: URI | string, identifier: string | TaskIdentifier | undefined) => { + resolve: async (uri: URI | string, identifier: string | TaskIdentifier | undefined) => { + if (resolverData === undefined) { + resolverData = new Map(); + (grouped || await this.getGroupedTasks()).forEach((tasks, folder) => { + let data = resolverData!.get(folder); + if (!data) { + data = { label: new Map(), identifier: new Map(), taskIdentifier: new Map() }; + resolverData!.set(folder, data); + } + for (let task of tasks) { + data.label.set(task._label, task); + if (task.configurationProperties.identifier) { + data.identifier.set(task.configurationProperties.identifier, task); + } + let keyedIdentifier = task.getDefinition(true); + if (keyedIdentifier !== undefined) { + data.taskIdentifier.set(keyedIdentifier._key, task); + } + } + }); + } let data = resolverData.get(typeof uri === 'string' ? uri : uri.toString()); if (!data || !identifier) { return undefined; @@ -1284,7 +1390,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private executeTask(task: Task, resolver: ITaskResolver): Promise { return ProblemMatcherRegistry.onReady().then(() => { - return this.editorService.saveAll().then((value) => { // make sure all dirty editors are saved + return this.editorService.saveAll().then(() => { // make sure all dirty editors are saved let executeResult = this.getTaskSystem().run(task, resolver); return this.handleExecuteResult(executeResult); }); @@ -1299,10 +1405,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this.showOutput(); } - let key = executeResult.task.getRecentlyUsedKey(); - if (key) { - this.setRecentlyUsedTask(key); - } + this.setRecentlyUsedTask(executeResult.task); if (executeResult.kind === TaskExecuteKind.Active) { let active = executeResult.active; if (active && active.same) { @@ -1366,7 +1469,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this.modelService, this.configurationResolverService, this.telemetryService, this.contextService, this.environmentService, AbstractTaskService.OutputChannelId, this.fileService, this.terminalInstanceService, - this.remotePathService, + this.remotePathService, this.viewDescriptorService, (workspaceFolder: IWorkspaceFolder) => { if (!workspaceFolder) { return undefined; @@ -1378,36 +1481,13 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer protected abstract getTaskSystem(): ITaskSystem; - private async provideTasksWithWarning(provider: ITaskProvider, type: string, validTypes: IStringDictionary): Promise { - return new Promise(async (resolve, reject) => { - let isDone = false; - let disposable: IDisposable | undefined; - const providePromise = provider.provideTasks(validTypes); - this._providerProgressManager?.addProvider(type, providePromise); - disposable = this._providerProgressManager?.canceled.token.onCancellationRequested(() => { - if (!isDone) { - resolve(); - } - }); - providePromise.then((value) => { - isDone = true; - disposable?.dispose(); - resolve(value); - }, (e) => { - isDone = true; - disposable?.dispose(); - reject(e); - }); - }); - } - private getGroupedTasks(type?: string): Promise { + const needsRecentTasksMigration = this.needsRecentTasksMigration(); return Promise.all([this.extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask'), this.extensionService.whenInstalledExtensionsRegistered()]).then(() => { let validTypes: IStringDictionary = Object.create(null); TaskDefinitionRegistry.all().forEach(definition => validTypes[definition.taskType] = true); validTypes['shell'] = true; validTypes['process'] = true; - this._providerProgressManager = new ProviderProgressMananger(); return new Promise(resolve => { let result: TaskSet[] = []; let counter: number = 0; @@ -1440,7 +1520,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer for (const [handle, provider] of this._providers) { if ((type === undefined) || (type === this._providerTypes.get(handle))) { counter++; - this.provideTasksWithWarning(provider, this._providerTypes.get(handle)!, validTypes).then(done, error); + provider.provideTasks(validTypes).then(done, error); } } } else { @@ -1525,6 +1605,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer const unUsedConfigurationPromises = unUsedConfigurationsAsArray.map(async (value) => { let configuringTask = configurations!.byIdentifier[value]; + if (type && (type !== configuringTask.configures.type)) { + return; + } for (const [handle, provider] of this._providers) { if (configuringTask.type === this._providerTypes.get(handle)) { @@ -1558,7 +1641,10 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); await Promise.all(customTasksPromises); - + if (needsRecentTasksMigration) { + // At this point we have all the tasks and can migrate the recently used tasks. + this.migrateRecentTasks(result.all()); + } return result; }, () => { // If we can't read the tasks.json file provide at least the contributed tasks @@ -1746,13 +1832,13 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return { workspaceFolder, set: undefined, configurations: undefined, hasErrors: false }; } - private async computeTasksForSingleConfig(workspaceFolder: IWorkspaceFolder, config: TaskConfig.ExternalTaskRunnerConfiguration | undefined, runSource: TaskRunSource, custom: CustomTask[], customized: IStringDictionary, source: TaskConfig.TaskConfigSource): Promise { + private async computeTasksForSingleConfig(workspaceFolder: IWorkspaceFolder, config: TaskConfig.ExternalTaskRunnerConfiguration | undefined, runSource: TaskRunSource, custom: CustomTask[], customized: IStringDictionary, source: TaskConfig.TaskConfigSource, isRecentTask: boolean = false): Promise { if (!config) { return false; } let taskSystemInfo: TaskSystemInfo | undefined = workspaceFolder ? this._taskSystemInfos.get(workspaceFolder.uri.scheme) : undefined; let problemReporter = new ProblemReporter(this._outputChannel); - let parseResult = TaskConfig.parse(workspaceFolder, this._workspace, taskSystemInfo ? taskSystemInfo.platform : Platform.platform, config, problemReporter, source); + let parseResult = TaskConfig.parse(workspaceFolder, this._workspace, taskSystemInfo ? taskSystemInfo.platform : Platform.platform, config, problemReporter, source, isRecentTask); let hasErrors = false; if (!parseResult.validationStatus.isOK()) { this.showOutput(runSource); @@ -1989,19 +2075,18 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return this.configurationService.getValue(QUICKOPEN_DETAIL_CONFIG); } - private createTaskQuickPickEntries(tasks: Task[], group: boolean = false, sort: boolean = false, selectedEntry?: TaskQuickPickEntry): TaskQuickPickEntry[] { + private createTaskQuickPickEntries(tasks: Task[], group: boolean = false, sort: boolean = false, selectedEntry?: TaskQuickPickEntry, includeRecents: boolean = true): TaskQuickPickEntry[] { let count: { [key: string]: number; } = {}; if (tasks === undefined || tasks === null || tasks.length === 0) { return []; } const TaskQuickPickEntry = (task: Task): TaskQuickPickEntry => { let entryLabel = task._label; - let commonKey = task._id.split('|')[0]; - if (count[commonKey]) { - entryLabel = entryLabel + ' (' + count[commonKey].toString() + ')'; - count[commonKey]++; + if (count[task._id]) { + entryLabel = entryLabel + ' (' + count[task._id].toString() + ')'; + count[task._id]++; } else { - count[commonKey] = 1; + count[task._id] = 1; } return { label: entryLabel, description: this.getTaskDescription(task), task, detail: this.showDetail() ? task.configurationProperties.detail : undefined }; @@ -2054,7 +2139,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } const sorter = this.createSorter(); - fillEntries(entries, recent, nls.localize('recentlyUsed', 'recently used tasks')); + if (includeRecents) { + fillEntries(entries, recent, nls.localize('recentlyUsed', 'recently used tasks')); + } configured = configured.sort((a, b) => sorter.compare(a, b)); fillEntries(entries, configured, nls.localize('configured', 'configured tasks')); detected = detected.sort((a, b) => sorter.compare(a, b)); @@ -2071,6 +2158,10 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return entries; } + private async showTwoLevelQuickPick(placeHolder: string, defaultEntry?: TaskQuickPickEntry) { + return TaskQuickPick.show(this, this.configurationService, this.quickInputService, placeHolder, defaultEntry); + } + private async showQuickPick(tasks: Promise | Task[], placeHolder: string, defaultEntry?: TaskQuickPickEntry, group: boolean = false, sort: boolean = false, selectedEntry?: TaskQuickPickEntry, additionalEntries?: TaskQuickPickEntry[]): Promise { const tokenSource = new CancellationTokenSource(); const cancellationToken: CancellationToken = tokenSource.token; @@ -2123,32 +2214,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } }); picker.busy = true; - const progressManager = this._providerProgressManager; - const progressTimeout = setTimeout(() => { - if (progressManager) { - progressManager.showProgress = (stillProviding, total) => { - let message = undefined; - if (stillProviding.length > 0) { - message = nls.localize('pickProgressManager.description', 'Detecting tasks ({0} of {1}): {2} in progress', total - stillProviding.length, total, stillProviding.join(', ')); - } - picker.description = message; - }; - progressManager.addOnDoneListener(() => { - picker.focusOnInput(); - picker.customButton = false; - }); - if (!progressManager.isDone) { - picker.customLabel = nls.localize('taskQuickPick.cancel', "Stop detecting"); - picker.onDidCustom(() => { - this._providerProgressManager?.cancel(); - }); - picker.customButton = true; - } - } - }, 1000); pickEntries.then(entries => { - clearTimeout(progressTimeout); - progressManager?.dispose(); picker.busy = false; picker.items = entries; }); @@ -2173,6 +2239,31 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); } + private needsRecentTasksMigration(): boolean { + return (this.getRecentlyUsedTasksV1().size > 0) && (this.getRecentlyUsedTasks().size === 0); + } + + public migrateRecentTasks(tasks: Task[]) { + if (!this.needsRecentTasksMigration()) { + return; + } + let recentlyUsedTasks = this.getRecentlyUsedTasksV1(); + let taskMap: IStringDictionary = Object.create(null); + tasks.forEach(task => { + let key = task.getRecentlyUsedKey(); + if (key) { + taskMap[key] = task; + } + }); + recentlyUsedTasks.keys().reverse().forEach(key => { + let task = taskMap[key]; + if (task) { + this.setRecentlyUsedTask(task); + } + }); + this.storageService.remove(AbstractTaskService.RecentlyUsedTasks_Key, StorageScope.WORKSPACE); + } + private showIgnoredFoldersMessage(): Promise { if (this.ignoredWorkspaceFolders.length === 0 || !this.showIgnoreMessage) { return Promise.resolve(undefined); @@ -2200,12 +2291,12 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } let identifier = this.getTaskIdentifier(arg); if (identifier !== undefined) { - this.getGroupedTasks().then((grouped) => { + this.getGroupedTasks().then(async (grouped) => { let resolver = this.createResolver(grouped); let folders: (IWorkspaceFolder | string)[] = this.contextService.getWorkspace().folders; folders = folders.concat([USER_TASKS_GROUP_KEY]); for (let folder of folders) { - let task = resolver.resolve(typeof folder === 'string' ? folder : folder.uri, identifier); + let task = await resolver.resolve(typeof folder === 'string' ? folder : folder.uri, identifier); if (task) { this.run(task).then(undefined, reason => { // eat the error, it has already been surfaced to the user and we don't care about it here @@ -2224,26 +2315,20 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private doRunTaskCommand(tasks?: Task[]): void { this.showIgnoredFoldersMessage().then(() => { - let taskResult: { tasks: Promise, grouped: Promise } | undefined = undefined; - if (!tasks) { - taskResult = this.tasksAndGroupedTasks(); - } - this.showQuickPick(tasks ? tasks : taskResult!.tasks, + this.showTwoLevelQuickPick( nls.localize('TaskService.pickRunTask', 'Select the task to run'), { - label: nls.localize('TaskService.noEntryToRun', 'No task to run found. Configure Tasks...'), + label: nls.localize('TaskService.noEntryToRun', 'No configured tasks. Configure Tasks...'), task: null - }, - true). - then((entry) => { - let task: Task | undefined | null = entry ? entry.task : undefined; + }). + then((task) => { if (task === undefined) { return; } if (task === null) { this.runConfigureTasks(); } else { - this.run(task, { attachProblemMatcher: true }, TaskRunSource.User, taskResult?.grouped).then(undefined, reason => { + this.run(task, { attachProblemMatcher: true }, TaskRunSource.User).then(undefined, reason => { // eat the error, it has already been surfaced to the user and we don't care about it here }); } @@ -2257,7 +2342,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } ProblemMatcherRegistry.onReady().then(() => { - return this.editorService.saveAll().then((value) => { // make sure all dirty editors are saved + return this.editorService.saveAll().then(() => { // make sure all dirty editors are saved let executeResult = this.getTaskSystem().rerun(); if (executeResult) { return this.handleExecuteResult(executeResult); @@ -2511,6 +2596,10 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return result; } + private configHasTasks(taskConfig?: TaskConfig.ExternalTaskRunnerConfiguration): boolean { + return !!taskConfig && !!taskConfig.tasks && taskConfig.tasks.length > 0; + } + private openTaskFile(resource: URI, taskSource: string) { let configFileCreated = false; this.fileService.resolve(resource).then((stat) => stat, () => undefined).then(async (stat) => { @@ -2519,9 +2608,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer let tasksExistInFile: boolean; let target: ConfigurationTarget; switch (taskSource) { - case TaskSourceKind.User: tasksExistInFile = !!configValue.userValue; target = ConfigurationTarget.USER; break; - case TaskSourceKind.WorkspaceFile: tasksExistInFile = !!configValue.workspaceValue; target = ConfigurationTarget.WORKSPACE; break; - default: tasksExistInFile = !!configValue.value; target = ConfigurationTarget.WORKSPACE_FOLDER; + case TaskSourceKind.User: tasksExistInFile = this.configHasTasks(configValue.userValue); target = ConfigurationTarget.USER; break; + case TaskSourceKind.WorkspaceFile: tasksExistInFile = this.configHasTasks(configValue.workspaceValue); target = ConfigurationTarget.WORKSPACE; break; + default: tasksExistInFile = this.configHasTasks(configValue.value); target = ConfigurationTarget.WORKSPACE_FOLDER; } let content; if (!tasksExistInFile) { @@ -2599,7 +2688,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } - public getTaskDescription(task: Task): string | undefined { + public getTaskDescription(task: Task | ConfiguringTask): string | undefined { let description: string | undefined; if (task._source.kind === TaskSourceKind.User) { description = nls.localize('taskQuickPick.userSettings', 'User Settings'); diff --git a/src/vs/workbench/contrib/tasks/browser/providerProgressManager.ts b/src/vs/workbench/contrib/tasks/browser/providerProgressManager.ts deleted file mode 100644 index 7c48da7f73c..00000000000 --- a/src/vs/workbench/contrib/tasks/browser/providerProgressManager.ts +++ /dev/null @@ -1,61 +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 { TaskSet } from 'vs/workbench/contrib/tasks/common/tasks'; -import { Emitter } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; - -export class ProviderProgressMananger extends Disposable { - private _onProviderComplete: Emitter = new Emitter(); - private _stillProviding: Set = new Set(); - private _totalProviders: number = 0; - private _onDone: Emitter = new Emitter(); - private _isDone: boolean = false; - private _showProgress: ((remaining: string[], total: number) => void) | undefined; - public canceled: CancellationTokenSource = new CancellationTokenSource(); - - constructor() { - super(); - this._register(this._onProviderComplete.event(taskType => { - this._stillProviding.delete(taskType); - if (this._stillProviding.size === 0) { - this._isDone = true; - this._onDone.fire(); - } - if (this._showProgress) { - this._showProgress(Array.from(this._stillProviding), this._totalProviders); - } - })); - } - - public addProvider(taskType: string, provider: Promise) { - this._totalProviders++; - this._stillProviding.add(taskType); - provider.then(() => this._onProviderComplete.fire(taskType)); - } - - public addOnDoneListener(onDoneListener: () => void) { - this._register(this._onDone.event(onDoneListener)); - } - - set showProgress(progressDisplayFunction: (remaining: string[], total: number) => void) { - this._showProgress = progressDisplayFunction; - this._showProgress(Array.from(this._stillProviding), this._totalProviders); - } - - get isDone(): boolean { - return this._isDone; - } - - public cancel() { - this._isDone = true; - if (this._showProgress) { - this._showProgress([], 0); - } - this._onDone.fire(); - this.canceled.cancel(); - } -} diff --git a/src/vs/workbench/contrib/tasks/browser/quickOpen.ts b/src/vs/workbench/contrib/tasks/browser/quickOpen.ts deleted file mode 100644 index a9d3f6b61a4..00000000000 --- a/src/vs/workbench/contrib/tasks/browser/quickOpen.ts +++ /dev/null @@ -1,231 +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 nls from 'vs/nls'; -import * as Filters from 'vs/base/common/filters'; -import { Action, IAction } from 'vs/base/common/actions'; -import { IStringDictionary } from 'vs/base/common/collections'; - -import * as Quickopen from 'vs/workbench/browser/quickopen'; -import * as QuickOpen from 'vs/base/parts/quickopen/common/quickOpen'; -import * as Model from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; - -import { CustomTask, ContributedTask } from 'vs/workbench/contrib/tasks/common/tasks'; -import { ITaskService, ProblemMatcherRunOptions } from 'vs/workbench/contrib/tasks/common/taskService'; -import { ActionBarContributor, ContributableActionProvider } from 'vs/workbench/browser/actions'; -import { CancellationToken } from 'vs/base/common/cancellation'; - -export class TaskEntry extends Model.QuickOpenEntry { - - constructor(protected quickOpenService: IQuickOpenService, protected taskService: ITaskService, protected _task: CustomTask | ContributedTask, highlights: Model.IHighlight[] = []) { - super(highlights); - } - - public getLabel(): string { - return this.task._label; - } - - public getDescription(): string | undefined { - return this.taskService.getTaskDescription(this.task); - } - - public getAriaLabel(): string { - return nls.localize('entryAriaLabel', "{0}, tasks", this.getLabel()); - } - - public get task(): CustomTask | ContributedTask { - return this._task; - } - - protected doRun(task: CustomTask | ContributedTask, options?: ProblemMatcherRunOptions): boolean { - this.taskService.run(task, options).then(undefined, reason => { - // eat the error, it has already been surfaced to the user and we don't care about it here - }); - if (!task.command || (task.command.presentation && task.command.presentation.focus)) { - this.quickOpenService.close(); - return false; - } - return true; - } -} - -export class TaskGroupEntry extends Model.QuickOpenEntryGroup { - constructor(entry: TaskEntry, groupLabel: string, withBorder: boolean) { - super(entry, groupLabel, withBorder); - } -} - -export abstract class QuickOpenHandler extends Quickopen.QuickOpenHandler { - - private tasks?: Promise>; - - constructor( - protected quickOpenService: IQuickOpenService, - protected taskService: ITaskService - ) { - super(); - - this.quickOpenService = quickOpenService; - this.taskService = taskService; - } - - public onOpen(): void { - this.tasks = this.getTasks(); - } - - public onClose(canceled: boolean): void { - this.tasks = undefined; - } - - public getResults(input: string, token: CancellationToken): Promise { - if (!this.tasks) { - return Promise.resolve(null); - } - return this.tasks.then((tasks) => { - let entries: Model.QuickOpenEntry[] = []; - if (tasks.length === 0 || token.isCancellationRequested) { - return new Model.QuickOpenModel(entries); - } - let recentlyUsedTasks = this.taskService.getRecentlyUsedTasks(); - let recent: Array = []; - let configured: CustomTask[] = []; - let detected: ContributedTask[] = []; - let taskMap: IStringDictionary = Object.create(null); - tasks.forEach(task => { - let key = task.getRecentlyUsedKey(); - if (key) { - taskMap[key] = task; - } - }); - recentlyUsedTasks.keys().forEach(key => { - let task = taskMap[key]; - if (task) { - recent.push(task); - } - }); - for (let task of tasks) { - let key = task.getRecentlyUsedKey(); - if (!key || !recentlyUsedTasks.has(key)) { - if (CustomTask.is(task)) { - configured.push(task); - } else { - detected.push(task); - } - } - } - const sorter = this.taskService.createSorter(); - let hasRecentlyUsed: boolean = recent.length > 0; - this.fillEntries(entries, input, recent, nls.localize('recentlyUsed', 'recently used tasks')); - configured = configured.sort((a, b) => sorter.compare(a, b)); - let hasConfigured = configured.length > 0; - this.fillEntries(entries, input, configured, nls.localize('configured', 'configured tasks'), hasRecentlyUsed); - detected = detected.sort((a, b) => sorter.compare(a, b)); - this.fillEntries(entries, input, detected, nls.localize('detected', 'detected tasks'), hasRecentlyUsed || hasConfigured); - return new Model.QuickOpenModel(entries, new ContributableActionProvider()); - }); - } - - private fillEntries(entries: Model.QuickOpenEntry[], input: string, tasks: Array, groupLabel: string, withBorder: boolean = false) { - let first = true; - for (let task of tasks) { - let highlights = Filters.matchesFuzzy(input, task._label); - if (!highlights) { - continue; - } - if (first) { - first = false; - entries.push(new TaskGroupEntry(this.createEntry(task, highlights), groupLabel, withBorder)); - } else { - entries.push(this.createEntry(task, highlights)); - } - } - } - - protected abstract getTasks(): Promise>; - - protected abstract createEntry(task: CustomTask | ContributedTask, highlights: Model.IHighlight[]): TaskEntry; - - public getAutoFocus(input: string): QuickOpen.IAutoFocus { - return { - autoFocusFirstEntry: !!input - }; - } -} - -class CustomizeTaskAction extends Action { - - private static readonly ID = 'workbench.action.tasks.customizeTask'; - private static readonly LABEL = nls.localize('customizeTask', "Configure Task"); - - constructor(private taskService: ITaskService, private quickOpenService: IQuickOpenService) { - super(CustomizeTaskAction.ID, CustomizeTaskAction.LABEL); - this.updateClass(); - } - - public updateClass(): void { - this.class = 'codicon-gear'; - } - - public run(element: any): Promise { - let task = this.getTask(element); - if (ContributedTask.is(task)) { - return this.taskService.customize(task, undefined, true).then(() => { - this.quickOpenService.close(); - }); - } else { - return this.taskService.openConfig(task).then(() => { - this.quickOpenService.close(); - }); - } - } - - private getTask(element: any): CustomTask | ContributedTask | undefined { - if (element instanceof TaskEntry) { - return element.task; - } else if (element instanceof TaskGroupEntry) { - return (element.getEntry() as TaskEntry).task; - } - return undefined; - } -} - -export class QuickOpenActionContributor extends ActionBarContributor { - - private action: CustomizeTaskAction; - - constructor(@ITaskService taskService: ITaskService, @IQuickOpenService quickOpenService: IQuickOpenService) { - super(); - this.action = new CustomizeTaskAction(taskService, quickOpenService); - } - - public hasActions(context: any): boolean { - let task = this.getTask(context); - - return !!task; - } - - public getActions(context: any): ReadonlyArray { - let actions: Action[] = []; - let task = this.getTask(context); - if (task && ContributedTask.is(task) || CustomTask.is(task)) { - actions.push(this.action); - } - return actions; - } - - private getTask(context: any): CustomTask | ContributedTask | undefined { - if (!context) { - return undefined; - } - let element = context.element; - if (element instanceof TaskEntry) { - return element.task; - } else if (element instanceof TaskGroupEntry) { - return (element.getEntry() as TaskEntry).task; - } - return undefined; - } -} diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index ffeb8859239..6b30ba65f61 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -5,7 +5,6 @@ import * as nls from 'vs/nls'; -import { QuickOpenHandler } from 'vs/workbench/contrib/tasks/browser/taskQuickOpen'; import { Disposable } from 'vs/base/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; @@ -18,16 +17,12 @@ import * as jsonContributionRegistry from 'vs/platform/jsonschemas/common/jsonCo import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; -import { IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { IOutputChannelRegistry, Extensions as OutputExt } from 'vs/workbench/services/output/common/output'; -import { Scope, IActionBarRegistry, Extensions as ActionBarExtensions } from 'vs/workbench/browser/actions'; import { TaskEvent, TaskEventKind, TaskGroup, TASK_RUNNING_STATE } from 'vs/workbench/contrib/tasks/common/tasks'; import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; -import { QuickOpenActionContributor } from '../browser/quickOpen'; - import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { RunAutomaticTasks, ManageAutomaticTaskRunning } from 'vs/workbench/contrib/tasks/browser/runAutomaticTasks'; @@ -39,6 +34,8 @@ import { AbstractTaskService, ConfigureTaskAction } from 'vs/workbench/contrib/t import { tasksSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys'; +import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; +import { TasksQuickAccessProvider } from 'vs/workbench/contrib/tasks/browser/tasksQuickAccess'; let tasksCategory = nls.localize('tasksCategory', "Tasks"); @@ -260,22 +257,18 @@ KeybindingsRegistry.registerKeybindingRule({ let outputChannelRegistry = Registry.as(OutputExt.OutputChannels); outputChannelRegistry.registerChannel({ id: AbstractTaskService.OutputChannelId, label: AbstractTaskService.OutputChannelLabel, log: false }); -// Register Quick Open -const quickOpenRegistry = (Registry.as(QuickOpenExtensions.Quickopen)); + +// Register Quick Access +const quickAccessRegistry = (Registry.as(QuickAccessExtensions.Quickaccess)); const tasksPickerContextKey = 'inTasksPicker'; -quickOpenRegistry.registerQuickOpenHandler( - QuickOpenHandlerDescriptor.create( - QuickOpenHandler, - QuickOpenHandler.ID, - 'task ', - tasksPickerContextKey, - nls.localize('quickOpen.task', "Run Task") - ) -); - -const actionBarRegistry = Registry.as(ActionBarExtensions.Actionbar); -actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionContributor); +quickAccessRegistry.registerQuickAccessProvider({ + ctor: TasksQuickAccessProvider, + prefix: TasksQuickAccessProvider.PREFIX, + contextKey: tasksPickerContextKey, + placeholder: nls.localize('tasksQuickAccessPlaceholder', "Type the name of a task to run."), + helpEntries: [{ description: nls.localize('tasksQuickAccessHelp', "Run Task"), needsEditor: false }] +}); // tasks.json validation let schema: IJSONSchema = { diff --git a/src/vs/workbench/contrib/tasks/browser/taskQuickOpen.ts b/src/vs/workbench/contrib/tasks/browser/taskQuickOpen.ts deleted file mode 100644 index c7cbb227b24..00000000000 --- a/src/vs/workbench/contrib/tasks/browser/taskQuickOpen.ts +++ /dev/null @@ -1,66 +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 nls from 'vs/nls'; -import * as QuickOpen from 'vs/base/parts/quickopen/common/quickOpen'; -import * as Model from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; - -import { CustomTask, ContributedTask } from 'vs/workbench/contrib/tasks/common/tasks'; -import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; - -import * as base from './quickOpen'; - -class TaskEntry extends base.TaskEntry { - constructor(quickOpenService: IQuickOpenService, taskService: ITaskService, task: CustomTask | ContributedTask, highlights: Model.IHighlight[] = []) { - super(quickOpenService, taskService, task, highlights); - } - - public run(mode: QuickOpen.Mode, context: QuickOpen.IEntryRunContext): boolean { - if (mode === QuickOpen.Mode.PREVIEW) { - return false; - } - let task = this._task; - return this.doRun(task, { attachProblemMatcher: true }); - } -} - -export class QuickOpenHandler extends base.QuickOpenHandler { - - public static readonly ID = 'workbench.picker.tasks'; - - private activationPromise: Promise; - - constructor( - @IQuickOpenService quickOpenService: IQuickOpenService, - @IExtensionService extensionService: IExtensionService, - @ITaskService taskService: ITaskService - ) { - super(quickOpenService, taskService); - this.activationPromise = extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask'); - } - - public getAriaLabel(): string { - return nls.localize('tasksAriaLabel', "Type the name of a task to run"); - } - - protected getTasks(): Promise> { - return this.activationPromise.then(() => { - return this.taskService.tasks().then(tasks => tasks.filter((task): task is CustomTask | ContributedTask => ContributedTask.is(task) || CustomTask.is(task))); - }); - } - - protected createEntry(task: CustomTask | ContributedTask, highlights: Model.IHighlight[]): base.TaskEntry { - return new TaskEntry(this.quickOpenService, this.taskService, task, highlights); - } - - public getEmptyLabel(searchString: string): string { - if (searchString.length > 0) { - return nls.localize('noTasksMatching', "No tasks matching"); - } - return nls.localize('noTasksFound', "No tasks found"); - } -} \ No newline at end of file diff --git a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts new file mode 100644 index 00000000000..aff97565782 --- /dev/null +++ b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts @@ -0,0 +1,270 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import * as Objects from 'vs/base/common/objects'; +import { Task, ContributedTask, CustomTask, ConfiguringTask, TaskSorter } from 'vs/workbench/contrib/tasks/common/tasks'; +import { IWorkspace, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import * as Types from 'vs/base/common/types'; +import { ITaskService, WorkspaceFolderTaskResult } from 'vs/workbench/contrib/tasks/common/taskService'; +import { IQuickPickItem, QuickPickInput, IQuickPick } from 'vs/base/parts/quickinput/common/quickInput'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; + +export const QUICKOPEN_DETAIL_CONFIG = 'task.quickOpen.detail'; +export const QUICKOPEN_SKIP_CONFIG = 'task.quickOpen.skip'; + +export function isWorkspaceFolder(folder: IWorkspace | IWorkspaceFolder): folder is IWorkspaceFolder { + return 'uri' in folder; +} + +export interface TaskQuickPickEntry extends IQuickPickItem { + task: Task | undefined | null; +} +export interface TaskTwoLevelQuickPickEntry extends IQuickPickItem { + task: Task | ConfiguringTask | string | undefined | null; +} + +const SHOW_ALL: string = nls.localize('taskQuickPick.showAll', "Show All Tasks..."); + +export class TaskQuickPick extends Disposable { + private sorter: TaskSorter; + private topLevelEntries: QuickPickInput[] | undefined; + constructor( + private taskService: ITaskService, + private configurationService: IConfigurationService, + private quickInputService: IQuickInputService) { + super(); + this.sorter = this.taskService.createSorter(); + } + + private showDetail(): boolean { + return this.configurationService.getValue(QUICKOPEN_DETAIL_CONFIG); + } + + private guessTaskLabel(task: Task | ConfiguringTask): string { + if (task._label) { + return task._label; + } + if (ConfiguringTask.is(task)) { + let label: string = task.configures.type; + const configures = Objects.deepClone(task.configures); + delete configures['_key']; + delete configures['type']; + Object.keys(configures).forEach(key => label += `: ${configures[key]}`); + return label; + } + return ''; + } + + private createTaskEntry(task: Task | ConfiguringTask): TaskTwoLevelQuickPickEntry { + const entry: TaskTwoLevelQuickPickEntry = { label: this.guessTaskLabel(task), description: this.taskService.getTaskDescription(task), task, detail: this.showDetail() ? task.configurationProperties.detail : undefined }; + entry.buttons = [{ iconClass: 'codicon-gear', tooltip: nls.localize('configureTask', "Configure Task") }]; + return entry; + } + + private createEntriesForGroup(entries: QuickPickInput[], tasks: (Task | ConfiguringTask)[], groupLabel: string) { + entries.push({ type: 'separator', label: groupLabel }); + tasks.forEach(task => { + entries.push(this.createTaskEntry(task)); + }); + } + + private createTypeEntries(entries: QuickPickInput[], types: string[]) { + entries.push({ type: 'separator', label: nls.localize('contributedTasks', "contributed") }); + types.forEach(type => { + entries.push({ label: `$(folder) ${type}`, task: type }); + }); + entries.push({ label: SHOW_ALL, task: SHOW_ALL }); + } + + private handleFolderTaskResult(result: Map): (Task | ConfiguringTask)[] { + let tasks: (Task | ConfiguringTask)[] = []; + Array.from(result).forEach(([key, folderTasks]) => { + if (folderTasks.set) { + tasks.push(...folderTasks.set.tasks); + } + if (folderTasks.configurations) { + for (const configuration in folderTasks.configurations.byIdentifier) { + tasks.push(folderTasks.configurations.byIdentifier[configuration]); + } + } + }); + return tasks; + } + + private dedupeConfiguredAndRecent(recentTasks: (Task | ConfiguringTask)[], configuredTasks: (Task | ConfiguringTask)[]): { configuredTasks: (Task | ConfiguringTask)[], recentTasks: (Task | ConfiguringTask)[] } { + let dedupedConfiguredTasks: (Task | ConfiguringTask)[] = []; + const foundRecentTasks: boolean[] = Array(recentTasks.length).fill(false); + for (let j = 0; j < configuredTasks.length; j++) { + const workspaceFolder = configuredTasks[j].getWorkspaceFolder()?.uri.toString(); + const definition = configuredTasks[j].getDefinition()?._key; + const recentKey = configuredTasks[j].getRecentlyUsedKey(); + const findIndex = recentTasks.findIndex((value) => { + return (workspaceFolder && definition && value.getWorkspaceFolder()?.uri.toString() === workspaceFolder && value.getDefinition()?._key === definition) + || (recentKey && value.getRecentlyUsedKey() === recentKey); + }); + if (findIndex === -1) { + dedupedConfiguredTasks.push(configuredTasks[j]); + } else { + recentTasks[findIndex] = configuredTasks[j]; + foundRecentTasks[findIndex] = true; + } + } + dedupedConfiguredTasks = dedupedConfiguredTasks.sort((a, b) => this.sorter.compare(a, b)); + const prunedRecentTasks: (Task | ConfiguringTask)[] = []; + for (let i = 0; i < recentTasks.length; i++) { + if (foundRecentTasks[i] || ConfiguringTask.is(recentTasks[i])) { + prunedRecentTasks.push(recentTasks[i]); + } + } + return { configuredTasks: dedupedConfiguredTasks, recentTasks: prunedRecentTasks }; + } + + public async getTopLevelEntries(defaultEntry?: TaskQuickPickEntry): Promise<{ entries: QuickPickInput[], isSingleConfigured?: Task | ConfiguringTask }> { + if (this.topLevelEntries !== undefined) { + return { entries: this.topLevelEntries }; + } + let recentTasks: (Task | ConfiguringTask)[] = (await this.taskService.readRecentTasks()).reverse(); + const configuredTasks: (Task | ConfiguringTask)[] = this.handleFolderTaskResult(await this.taskService.getWorkspaceTasks()); + const extensionTaskTypes = this.taskService.taskTypes(); + this.topLevelEntries = []; + // Dedupe will update recent tasks if they've changed in tasks.json. + const dedupeAndPrune = this.dedupeConfiguredAndRecent(recentTasks, configuredTasks); + let dedupedConfiguredTasks: (Task | ConfiguringTask)[] = dedupeAndPrune.configuredTasks; + recentTasks = dedupeAndPrune.recentTasks; + if (recentTasks.length > 0) { + this.createEntriesForGroup(this.topLevelEntries, recentTasks, nls.localize('recentlyUsed', 'recently used')); + } + if (configuredTasks.length > 0) { + if (dedupedConfiguredTasks.length > 0) { + this.createEntriesForGroup(this.topLevelEntries, dedupedConfiguredTasks, nls.localize('configured', 'configured')); + } + } + + if (defaultEntry && (configuredTasks.length === 0)) { + this.topLevelEntries.push({ type: 'separator', label: nls.localize('configured', 'configured') }); + this.topLevelEntries.push(defaultEntry); + } + + if (extensionTaskTypes.length > 0) { + this.createTypeEntries(this.topLevelEntries, extensionTaskTypes); + } + return { entries: this.topLevelEntries, isSingleConfigured: configuredTasks.length === 1 ? configuredTasks[0] : undefined }; + } + + public async show(placeHolder: string, defaultEntry?: TaskQuickPickEntry, startAtType?: string): Promise { + const picker: IQuickPick = this.quickInputService.createQuickPick(); + picker.placeholder = placeHolder; + picker.matchOnDescription = true; + picker.ignoreFocusOut = false; + picker.show(); + + picker.onDidTriggerItemButton(context => { + let task = context.item.task; + this.quickInputService.cancel(); + if (ContributedTask.is(task)) { + this.taskService.customize(task, undefined, true); + } else if (CustomTask.is(task) || ConfiguringTask.is(task)) { + this.taskService.openConfig(task); + } + }); + + let firstLevelTask: Task | ConfiguringTask | string | undefined | null = startAtType; + if (!firstLevelTask) { + // First show recent tasks configured tasks. Other tasks will be available at a second level + const topLevelEntriesResult = await this.getTopLevelEntries(defaultEntry); + if (topLevelEntriesResult.isSingleConfigured && this.configurationService.getValue(QUICKOPEN_SKIP_CONFIG)) { + picker.dispose(); + return this.toTask(topLevelEntriesResult.isSingleConfigured); + } + const taskQuickPickEntries: QuickPickInput[] = topLevelEntriesResult.entries; + firstLevelTask = await this.doPickerFirstLevel(picker, taskQuickPickEntries); + } + do { + if (Types.isString(firstLevelTask)) { + // Proceed to second level of quick pick + const selectedEntry = await this.doPickerSecondLevel(picker, firstLevelTask); + if (selectedEntry && selectedEntry.task === null) { + // The user has chosen to go back to the first level + firstLevelTask = await this.doPickerFirstLevel(picker, (await this.getTopLevelEntries(defaultEntry)).entries); + } else { + picker.dispose(); + return (selectedEntry?.task && !Types.isString(selectedEntry?.task)) ? this.toTask(selectedEntry?.task) : undefined; + } + } else if (firstLevelTask) { + picker.dispose(); + return this.toTask(firstLevelTask); + } else { + picker.dispose(); + return firstLevelTask; + } + } while (1); + return; + } + + private async doPickerFirstLevel(picker: IQuickPick, taskQuickPickEntries: QuickPickInput[]): Promise { + picker.items = taskQuickPickEntries; + const firstLevelPickerResult = await new Promise(resolve => { + Event.once(picker.onDidAccept)(async () => { + resolve(picker.selectedItems ? picker.selectedItems[0] : undefined); + }); + }); + return firstLevelPickerResult?.task; + } + + private async doPickerSecondLevel(picker: IQuickPick, type: string) { + picker.busy = true; + picker.value = ''; + if (type === SHOW_ALL) { + picker.items = (await this.taskService.tasks()).sort((a, b) => this.sorter.compare(a, b)).map(task => this.createTaskEntry(task)); + } else { + picker.items = await this.getEntriesForProvider(type); + } + picker.busy = false; + const secondLevelPickerResult = await new Promise(resolve => { + Event.once(picker.onDidAccept)(async () => { + resolve(picker.selectedItems ? picker.selectedItems[0] : undefined); + }); + }); + + return secondLevelPickerResult; + } + + private async getEntriesForProvider(type: string): Promise[]> { + const tasks = (await this.taskService.tasks({ type })).sort((a, b) => this.sorter.compare(a, b)); + let taskQuickPickEntries: QuickPickInput[]; + if (tasks.length > 0) { + taskQuickPickEntries = tasks.map(task => this.createTaskEntry(task)); + taskQuickPickEntries.push({ + type: 'separator' + }, { + label: nls.localize('TaskQuickPick.goBack', 'Go back ↩'), + task: null + }); + } else { + taskQuickPickEntries = [{ + label: nls.localize('TaskQuickPick.noTasksForType', 'No {0} tasks found. Go back ↩', type), + task: null + }]; + } + return taskQuickPickEntries; + } + + private async toTask(task: Task | ConfiguringTask): Promise { + if (!ConfiguringTask.is(task)) { + return task; + } + + return this.taskService.tryResolveTask(task); + } + + static async show(taskService: ITaskService, configurationService: IConfigurationService, quickInputService: IQuickInputService, placeHolder: string, defaultEntry?: TaskQuickPickEntry) { + const taskQuickPick = new TaskQuickPick(taskService, configurationService, quickInputService); + return taskQuickPick.show(placeHolder, defaultEntry); + } +} diff --git a/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts b/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts new file mode 100644 index 00000000000..514f84c421b --- /dev/null +++ b/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts @@ -0,0 +1,94 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IQuickPickSeparator, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IPickerQuickAccessItem, PickerQuickAccessProvider, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess'; +import { matchesFuzzy } from 'vs/base/common/filters'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { ITaskService, Task } from 'vs/workbench/contrib/tasks/common/taskService'; +import { CustomTask, ContributedTask, ConfiguringTask } from 'vs/workbench/contrib/tasks/common/tasks'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { TaskQuickPick, TaskTwoLevelQuickPickEntry } from 'vs/workbench/contrib/tasks/browser/taskQuickPick'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { isString } from 'vs/base/common/types'; + +export class TasksQuickAccessProvider extends PickerQuickAccessProvider { + + static PREFIX = 'task '; + + private activationPromise: Promise; + + constructor( + @IExtensionService extensionService: IExtensionService, + @ITaskService private taskService: ITaskService, + @IConfigurationService private configurationService: IConfigurationService, + @IQuickInputService private quickInputService: IQuickInputService + ) { + super(TasksQuickAccessProvider.PREFIX, { + noResultsPick: { + label: localize('noTaskResults', "No matching tasks") + } + }); + + this.activationPromise = extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask'); + } + + protected async getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Promise> { + // always await extensions + await this.activationPromise; + + if (token.isCancellationRequested) { + return []; + } + + const taskQuickPick = new TaskQuickPick(this.taskService, this.configurationService, this.quickInputService); + const topLevelPicks = await taskQuickPick.getTopLevelEntries(); + const taskPicks: Array = []; + + for (const entry of topLevelPicks.entries) { + const highlights = matchesFuzzy(filter, entry.label!, true); + if (!highlights) { + continue; + } + + if (entry.type === 'separator') { + taskPicks.push(entry); + } + + const task: Task | ConfiguringTask | string = (entry).task!; + const quickAccessEntry: IPickerQuickAccessItem = entry; + quickAccessEntry.highlights = { label: highlights }; + quickAccessEntry.trigger = () => { + if (ContributedTask.is(task)) { + this.taskService.customize(task, undefined, true); + } else if (CustomTask.is(task)) { + this.taskService.openConfig(task); + } + return TriggerAction.CLOSE_PICKER; + }; + quickAccessEntry.accept = async () => { + if (isString(task)) { + // switch to quick pick and show second level + taskQuickPick.show(localize('TaskService.pickRunTask', 'Select the task to run'), undefined, task); + } else { + this.taskService.run(await this.toTask(task), { attachProblemMatcher: true }); + } + }; + + taskPicks.push(quickAccessEntry); + } + return taskPicks; + } + + private async toTask(task: Task | ConfiguringTask): Promise { + if (!ConfiguringTask.is(task)) { + return task; + } + + return this.taskService.tryResolveTask(task); + } +} diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index bf6617e3b90..144d5552c5a 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -33,7 +33,7 @@ import { IOutputService } from 'vs/workbench/contrib/output/common/output'; import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEventKind, ProblemHandlingStrategy } from 'vs/workbench/contrib/tasks/common/problemCollectors'; import { Task, CustomTask, ContributedTask, RevealKind, CommandOptions, ShellConfiguration, RuntimeType, PanelKind, - TaskEvent, TaskEventKind, ShellQuotingOptions, ShellQuoting, CommandString, CommandConfiguration, ExtensionTaskSource, TaskScope, RevealProblemKind, DependsOrder + TaskEvent, TaskEventKind, ShellQuotingOptions, ShellQuoting, CommandString, CommandConfiguration, ExtensionTaskSource, TaskScope, RevealProblemKind, DependsOrder, TaskSourceKind, InMemoryTask } from 'vs/workbench/contrib/tasks/common/tasks'; import { ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, ITaskResolver, @@ -45,7 +45,7 @@ import { Schemas } from 'vs/base/common/network'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; import { env as processEnv, cwd as processCwd } from 'vs/base/common/process'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService, IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; interface TerminalData { terminal: ITerminalInstance; @@ -201,6 +201,7 @@ export class TerminalTaskSystem implements ITaskSystem { private fileService: IFileService, private terminalInstanceService: ITerminalInstanceService, private remotePathService: IRemotePathService, + private viewDescriptorService: IViewDescriptorService, taskSystemInfoResolver: TaskSystemInfoResolver, ) { @@ -228,16 +229,13 @@ export class TerminalTaskSystem implements ITaskSystem { } public run(task: Task, resolver: ITaskResolver, trigger: string = Triggers.command): ITaskExecuteResult { - let commonKey = task._id.split('|')[0]; - let validInstance = task.runOptions && task.runOptions.instanceLimit && this.instances[commonKey] && this.instances[commonKey].instances < task.runOptions.instanceLimit; - let instance = this.instances[commonKey] ? this.instances[commonKey].instances : 0; + const recentTaskKey = task.getRecentlyUsedKey() ?? ''; + let validInstance = task.runOptions && task.runOptions.instanceLimit && this.instances[recentTaskKey] && this.instances[recentTaskKey].instances < task.runOptions.instanceLimit; + let instance = this.instances[recentTaskKey] ? this.instances[recentTaskKey].instances : 0; this.currentTask = new VerifiedTask(task, resolver, trigger); - let taskClone = undefined; if (instance > 0) { - taskClone = task.clone(); - taskClone._id += '|' + this.instances[commonKey].counter.toString(); + task.instance = this.instances[recentTaskKey].counter; } - let taskToExecute = taskClone ?? task; let lastTaskInstance = this.getLastInstance(task); let terminalData = lastTaskInstance ? this.activeTasks[lastTaskInstance.getMapKey()] : undefined; if (terminalData && terminalData.promise && !validInstance) { @@ -246,14 +244,16 @@ export class TerminalTaskSystem implements ITaskSystem { } try { - const executeResult = { kind: TaskExecuteKind.Started, task, started: {}, promise: this.executeTask(taskToExecute, resolver, trigger) }; + const executeResult = { kind: TaskExecuteKind.Started, task, started: {}, promise: this.executeTask(task, resolver, trigger) }; executeResult.promise.then(summary => { this.lastTask = this.currentTask; }); - if (!this.instances[commonKey]) { - this.instances[commonKey] = new InstanceManager(); + if (InMemoryTask.is(task) || !this.isTaskEmpty(task)) { + if (!this.instances[recentTaskKey]) { + this.instances[recentTaskKey] = new InstanceManager(); + } + this.instances[recentTaskKey].addInstance(); } - this.instances[commonKey].addInstance(); return executeResult; } catch (error) { if (error instanceof TaskError) { @@ -299,7 +299,8 @@ export class TerminalTaskSystem implements ITaskSystem { if (!terminalData) { return false; } - if (this.isTaskVisible(task)) { + const isTerminalInPanel: boolean = this.viewDescriptorService.getViewLocation(TERMINAL_VIEW_ID) === ViewContainerLocation.Panel; + if (isTerminalInPanel && this.isTaskVisible(task)) { if (this.previousPanelId) { if (this.previousTerminalInstance) { this.terminalService.setActiveInstance(this.previousTerminalInstance); @@ -311,9 +312,11 @@ export class TerminalTaskSystem implements ITaskSystem { this.previousPanelId = undefined; this.previousTerminalInstance = undefined; } else { - this.previousPanelId = this.panelService.getActivePanel()?.getId(); - if (this.previousPanelId === TERMINAL_VIEW_ID) { - this.previousTerminalInstance = this.terminalService.getActiveInstance() ?? undefined; + if (isTerminalInPanel) { + this.previousPanelId = this.panelService.getActivePanel()?.getId(); + if (this.previousPanelId === TERMINAL_VIEW_ID) { + this.previousTerminalInstance = this.terminalService.getActiveInstance() ?? undefined; + } } this.terminalService.setActiveInstance(terminalData.terminal); if (CustomTask.is(task) || ContributedTask.is(task)) { @@ -341,9 +344,9 @@ export class TerminalTaskSystem implements ITaskSystem { public getLastInstance(task: Task): Task | undefined { let lastInstance = undefined; - let commonId = task._id.split('|')[0]; + const recentKey = task.getRecentlyUsedKey(); Object.keys(this.activeTasks).forEach((key) => { - if (commonId === this.activeTasks[key].task._id.split('|')[0]) { + if (recentKey && recentKey === this.activeTasks[key].task.getRecentlyUsedKey()) { lastInstance = this.activeTasks[key].task; } }); @@ -366,18 +369,22 @@ export class TerminalTaskSystem implements ITaskSystem { }); } + private removeInstances(task: Task) { + const recentTaskKey = task.getRecentlyUsedKey() ?? ''; + if (this.instances[recentTaskKey]) { + this.instances[recentTaskKey].removeInstance(); + if (this.instances[recentTaskKey].instances === 0) { + delete this.instances[recentTaskKey]; + } + } + } + private removeFromActiveTasks(task: Task): void { if (!this.activeTasks[task.getMapKey()]) { return; } delete this.activeTasks[task.getMapKey()]; - let commonKey = task._id.split('|')[0]; - if (this.instances[commonKey]) { - this.instances[commonKey].removeInstance(); - if (this.instances[commonKey].instances === 0) { - delete this.instances[commonKey]; - } - } + this.removeInstances(task); } public terminate(task: Task): Promise { @@ -430,7 +437,7 @@ export class TerminalTaskSystem implements ITaskSystem { let promises: Promise[] = []; if (task.configurationProperties.dependsOn) { for (const dependency of task.configurationProperties.dependsOn) { - let dependencyTask = resolver.resolve(dependency.uri, dependency.task!); + let dependencyTask = await resolver.resolve(dependency.uri, dependency.task!); if (dependencyTask) { let key = dependencyTask.getMapKey(); let promise = this.activeTasks[key] ? this.activeTasks[key].promise : undefined; @@ -464,6 +471,7 @@ export class TerminalTaskSystem implements ITaskSystem { return Promise.all(promises).then((summaries): Promise | ITaskSummary => { for (let summary of summaries) { if (summary.exitCode !== 0) { + this.removeInstances(task); return { exitCode: summary.exitCode }; } } @@ -545,7 +553,7 @@ export class TerminalTaskSystem implements ITaskSystem { resolveSet.process.path = envPath; } } - resolvedVariables = taskSystemInfo.resolveVariables(workspaceFolder, resolveSet).then(async (resolved) => { + resolvedVariables = taskSystemInfo.resolveVariables(workspaceFolder, resolveSet, TaskSourceKind.toConfigurationTarget(task._source.kind)).then(async (resolved) => { this.mergeMaps(alreadyResolved, resolved.variables); resolved.variables = new Map(alreadyResolved); if (isProcess) { @@ -563,7 +571,7 @@ export class TerminalTaskSystem implements ITaskSystem { unresolved.forEach(variable => variablesArray.push(variable)); return new Promise((resolve, reject) => { - this.configurationResolverService.resolveWithInteraction(workspaceFolder, variablesArray, 'tasks').then(async (resolvedVariablesMap: Map | undefined) => { + this.configurationResolverService.resolveWithInteraction(workspaceFolder, variablesArray, 'tasks', undefined, TaskSourceKind.toConfigurationTarget(task._source.kind)).then(async (resolvedVariablesMap: Map | undefined) => { if (resolvedVariablesMap) { this.mergeMaps(alreadyResolved, resolvedVariablesMap); resolvedVariablesMap = new Map(alreadyResolved); @@ -606,8 +614,7 @@ export class TerminalTaskSystem implements ITaskSystem { const resolvedVariables = this.resolveVariablesFromSet(systemInfo, workspaceFolder, task, variables, alreadyResolved); return resolvedVariables.then((resolvedVariables) => { - const isCustomExecution = (task.command.runtime === RuntimeType.CustomExecution); - if (resolvedVariables && (task.command !== undefined) && task.command.runtime && (isCustomExecution || (task.command.name !== undefined))) { + if (resolvedVariables && !this.isTaskEmpty(task)) { this.currentTask.resolvedVariables = resolvedVariables; return this.executeInTerminal(task, trigger, new VariableResolver(workspaceFolder, systemInfo, resolvedVariables.variables, this.configurationResolverService), workspaceFolder); } else { @@ -620,6 +627,11 @@ export class TerminalTaskSystem implements ITaskSystem { }); } + private isTaskEmpty(task: CustomTask | ContributedTask): boolean { + const isCustomExecution = (task.command.runtime === RuntimeType.CustomExecution); + return !((task.command !== undefined) && task.command.runtime && (isCustomExecution || (task.command.name !== undefined))); + } + private reexecuteCommand(task: CustomTask | ContributedTask, trigger: string, alreadyResolved: Map): Promise { const lastTask = this.lastTask; if (!lastTask) { diff --git a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts index 3f9ca21ede2..38ca7e709a8 100644 --- a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts +++ b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts @@ -206,7 +206,7 @@ const group: IJSONSchema = { const taskType: IJSONSchema = { type: 'string', enum: ['shell'], - default: 'shell', + default: 'process', description: nls.localize('JsonSchema.tasks.type', 'Defines whether the task is run as a process or as a command inside a shell.') }; @@ -474,7 +474,7 @@ const processTask = Objects.deepClone(taskDescription); processTask.properties!.type = { type: 'string', enum: ['process'], - default: 'shell', + default: 'process', description: nls.localize('JsonSchema.tasks.type', 'Defines whether the task is run as a process or as a command inside a shell.') }; processTask.required!.push('command'); diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index 14666552147..eb5b719dfeb 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -1242,18 +1242,20 @@ namespace GroupKind { } namespace TaskDependency { + function uriFromSource(context: ParseContext, source: TaskConfigSource): URI | string { + switch (source) { + case TaskConfigSource.User: return USER_TASKS_GROUP_KEY; + case TaskConfigSource.TasksJson: return context.workspaceFolder.uri; + default: return context.workspace && context.workspace.configuration ? context.workspace.configuration : context.workspaceFolder.uri; + } + } + export function from(this: void, external: string | TaskIdentifier, context: ParseContext, source: TaskConfigSource): Tasks.TaskDependency | undefined { if (Types.isString(external)) { - let uri: URI | string; - if (source === TaskConfigSource.User) { - uri = USER_TASKS_GROUP_KEY; - } else { - uri = context.workspace && context.workspace.configuration ? context.workspace.configuration : context.workspaceFolder.uri; - } - return { uri, task: external }; + return { uri: uriFromSource(context, source), task: external }; } else if (TaskIdentifier.is(external)) { return { - uri: context.workspace && context.workspace.configuration ? context.workspace.configuration : context.workspaceFolder.uri, + uri: uriFromSource(context, source), task: Tasks.TaskDefinition.createTaskIdentifier(external as Tasks.TaskIdentifier, context.problemReporter) }; } else { @@ -2077,12 +2079,19 @@ class ConfigurationParser { } } -let uuidMaps: Map = new Map(); -export function parse(workspaceFolder: IWorkspaceFolder, workspace: IWorkspace | undefined, platform: Platform, configuration: ExternalTaskRunnerConfiguration, logger: IProblemReporter, source: TaskConfigSource): ParseResult { - let uuidMap = uuidMaps.get(workspaceFolder.uri.toString()); +let uuidMaps: Map> = new Map(); +let recentUuidMaps: Map> = new Map(); +export function parse(workspaceFolder: IWorkspaceFolder, workspace: IWorkspace | undefined, platform: Platform, configuration: ExternalTaskRunnerConfiguration, logger: IProblemReporter, source: TaskConfigSource, isRecents: boolean = false): ParseResult { + let recentOrOtherMaps = isRecents ? recentUuidMaps : uuidMaps; + let selectedUuidMaps = recentOrOtherMaps.get(source); + if (!selectedUuidMaps) { + recentOrOtherMaps.set(source, new Map()); + selectedUuidMaps = recentOrOtherMaps.get(source)!; + } + let uuidMap = selectedUuidMaps.get(workspaceFolder.uri.toString()); if (!uuidMap) { uuidMap = new UUIDMap(); - uuidMaps.set(workspaceFolder.uri.toString(), uuidMap); + selectedUuidMaps.set(workspaceFolder.uri.toString(), uuidMap); } try { uuidMap.start(); @@ -2092,6 +2101,8 @@ export function parse(workspaceFolder: IWorkspaceFolder, workspace: IWorkspace | } } + + export function createCustomTask(contributedTask: Tasks.ContributedTask, configuredProps: Tasks.ConfiguringTask | Tasks.CustomTask): Tasks.CustomTask { return CustomTask.createCustomTask(contributedTask, configuredProps); } diff --git a/src/vs/workbench/contrib/tasks/common/taskService.ts b/src/vs/workbench/contrib/tasks/common/taskService.ts index 2342fedf370..626c7b4b6d4 100644 --- a/src/vs/workbench/contrib/tasks/common/taskService.ts +++ b/src/vs/workbench/contrib/tasks/common/taskService.ts @@ -69,19 +69,23 @@ export interface ITaskService { terminate(task: Task): Promise; terminateAll(): Promise; tasks(filter?: TaskFilter): Promise; + taskTypes(): string[]; getWorkspaceTasks(runSource?: TaskRunSource): Promise>; + readRecentTasks(): Promise<(Task | ConfiguringTask)[]>; /** * @param alias The task's name, label or defined identifier. */ getTask(workspaceFolder: IWorkspace | IWorkspaceFolder | string, alias: string | TaskIdentifier, compareId?: boolean): Promise; + tryResolveTask(configuringTask: ConfiguringTask): Promise; getTasksForGroup(group: string): Promise; getRecentlyUsedTasks(): LinkedMap; + migrateRecentTasks(tasks: Task[]): void; createSorter(): TaskSorter; - getTaskDescription(task: Task): string | undefined; + getTaskDescription(task: Task | ConfiguringTask): string | undefined; canCustomize(task: ContributedTask | CustomTask): boolean; customize(task: ContributedTask | CustomTask, properties?: {}, openConfig?: boolean): Promise; - openConfig(task: CustomTask | undefined): Promise; + openConfig(task: CustomTask | ConfiguringTask | undefined): Promise; registerTaskProvider(taskProvider: ITaskProvider, type: string): IDisposable; diff --git a/src/vs/workbench/contrib/tasks/common/taskSystem.ts b/src/vs/workbench/contrib/tasks/common/taskSystem.ts index 0ac0d5799a6..482eb62461e 100644 --- a/src/vs/workbench/contrib/tasks/common/taskSystem.ts +++ b/src/vs/workbench/contrib/tasks/common/taskSystem.ts @@ -10,6 +10,7 @@ import { Event } from 'vs/base/common/event'; import { Platform } from 'vs/base/common/platform'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { Task, TaskEvent, KeyedTaskIdentifier } from './tasks'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; export const enum TaskErrors { NotConfigured, @@ -93,7 +94,7 @@ export interface ITaskExecuteResult { } export interface ITaskResolver { - resolve(uri: URI | string, identifier: string | KeyedTaskIdentifier | undefined): Task | undefined; + resolve(uri: URI | string, identifier: string | KeyedTaskIdentifier | undefined): Promise; } export interface TaskTerminateResponse extends TerminateResponse { @@ -118,7 +119,7 @@ export interface TaskSystemInfo { platform: Platform; context: any; uriProvider: (this: void, path: string) => URI; - resolveVariables(workspaceFolder: IWorkspaceFolder, toResolve: ResolveSet): Promise; + resolveVariables(workspaceFolder: IWorkspaceFolder, toResolve: ResolveSet, target: ConfigurationTarget): Promise; getDefaultShellAndArgs(): Promise<{ shell: string, args: string[] | string | undefined }>; } diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index 2fcd3f90195..e6ebd8eef18 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -15,6 +15,8 @@ import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/works import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { USER_TASKS_GROUP_KEY } from 'vs/workbench/contrib/tasks/common/taskService'; export const TASK_RUNNING_STATE = new RawContextKey('taskRunning', false); @@ -377,6 +379,14 @@ export namespace TaskSourceKind { export const InMemory: 'inMemory' = 'inMemory'; export const WorkspaceFile: 'workspaceFile' = 'workspaceFile'; export const User: 'user' = 'user'; + + export function toConfigurationTarget(kind: string): ConfigurationTarget { + switch (kind) { + case TaskSourceKind.User: return ConfigurationTarget.USER; + case TaskSourceKind.WorkspaceFile: return ConfigurationTarget.WORKSPACE; + default: return ConfigurationTarget.WORKSPACE_FOLDER; + } + } } export interface TaskSourceConfigElement { @@ -642,6 +652,8 @@ export class CustomTask extends CommonTask { type!: '$customized'; // CUSTOMIZED_TASK_TYPE + instance: number | undefined; + /** * Indicated the source of the task (e.g. tasks.json or extension) */ @@ -713,7 +725,7 @@ export class CustomTask extends CommonTask { public getMapKey(): string { let workspaceFolder = this._source.config.workspaceFolder; - return workspaceFolder ? `${workspaceFolder.uri.toString()}|${this._id}` : this._id; + return workspaceFolder ? `${workspaceFolder.uri.toString()}|${this._id}|${this.instance}` : `${this._id}|${this.instance}`; } public getRecentlyUsedKey(): string | undefined { @@ -722,7 +734,7 @@ export class CustomTask extends CommonTask { folder: string; id: string; } - let workspaceFolder = this._source.config.workspaceFolder; + let workspaceFolder = this._source.kind === TaskSourceKind.User ? USER_TASKS_GROUP_KEY : this._source.config.workspaceFolder?.uri.toString(); if (!workspaceFolder) { return undefined; } @@ -730,7 +742,7 @@ export class CustomTask extends CommonTask { if (this._source.kind !== TaskSourceKind.Workspace) { id += this._source.kind; } - let key: CustomKey = { type: CUSTOMIZED_TASK_TYPE, folder: workspaceFolder.uri.toString(), id }; + let key: CustomKey = { type: CUSTOMIZED_TASK_TYPE, folder: workspaceFolder, id }; return JSON.stringify(key); } @@ -786,6 +798,28 @@ export class ConfiguringTask extends CommonTask { public getWorkspaceFileName(): string | undefined { return (this._source.config.workspace && this._source.config.workspace.configuration) ? resources.basename(this._source.config.workspace.configuration) : undefined; } + + public getWorkspaceFolder(): IWorkspaceFolder | undefined { + return this._source.config.workspaceFolder; + } + + public getRecentlyUsedKey(): string | undefined { + interface CustomKey { + type: string; + folder: string; + id: string; + } + let workspaceFolder = this._source.kind === TaskSourceKind.User ? USER_TASKS_GROUP_KEY : this._source.config.workspaceFolder?.uri.toString(); + if (!workspaceFolder) { + return undefined; + } + let id: string = this.configurationProperties.identifier!; + if (this._source.kind !== TaskSourceKind.Workspace) { + id += this._source.kind; + } + let key: CustomKey = { type: CUSTOMIZED_TASK_TYPE, folder: workspaceFolder, id }; + return JSON.stringify(key); + } } export class ContributedTask extends CommonTask { @@ -796,6 +830,8 @@ export class ContributedTask extends CommonTask { */ _source!: ExtensionTaskSource; + instance: number | undefined; + defines: KeyedTaskIdentifier; hasDefinedMatchers: boolean; @@ -825,8 +861,8 @@ export class ContributedTask extends CommonTask { public getMapKey(): string { let workspaceFolder = this._source.workspaceFolder; return workspaceFolder - ? `${this._source.scope.toString()}|${workspaceFolder.uri.toString()}|${this._id}` - : `${this._source.scope.toString()}|${this._id}`; + ? `${this._source.scope.toString()}|${workspaceFolder.uri.toString()}|${this._id}|${this.instance}` + : `${this._source.scope.toString()}|${this._id}|${this.instance}`; } public getRecentlyUsedKey(): string | undefined { @@ -863,6 +899,8 @@ export class InMemoryTask extends CommonTask { */ _source: InMemoryTaskSource; + instance: number | undefined; + type!: 'inMemory'; public constructor(id: string, source: InMemoryTaskSource, label: string, type: string, @@ -879,6 +917,10 @@ export class InMemoryTask extends CommonTask { return 'composite'; } + public getMapKey(): string { + return `${this._id}|${this.instance}`; + } + protected fromObject(object: InMemoryTask): InMemoryTask { return new InMemoryTask(object._id, object._source, object._label, object.type, object.runOptions, object.configurationProperties); } @@ -927,7 +969,7 @@ export class TaskSorter { } } - public compare(a: Task, b: Task): number { + public compare(a: Task | ConfiguringTask, b: Task | ConfiguringTask): number { let aw = a.getWorkspaceFolder(); let bw = b.getWorkspaceFolder(); if (aw && bw) { diff --git a/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts b/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts index 2564323b1c4..766b2115a83 100644 --- a/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts +++ b/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts @@ -514,11 +514,7 @@ function assertProblemMatcher(actual: string | ProblemMatcher, expected: string } if (typeof actual !== 'string' && typeof expected !== 'string') { if (expected.owner === ProblemMatcherBuilder.DEFAULT_UUID) { - try { - UUID.parse(actual.owner); - } catch (err) { - assert.fail(actual.owner, 'Owner must be a UUID'); - } + assert.ok(UUID.isUUID(actual.owner), 'Owner must be a UUID'); } else { assert.strictEqual(actual.owner, expected.owner); } diff --git a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts index ee6a1f86d1e..bd8c574e3d5 100644 --- a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts +++ b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts @@ -20,7 +20,7 @@ import { configurationTelemetry } from 'vs/platform/telemetry/common/telemetryUt import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ITextFileService, ITextFileSaveEvent, ITextFileLoadEvent } from 'vs/workbench/services/textfile/common/textfiles'; -import { extname, basename, isEqual, isEqualOrParent, joinPath } from 'vs/base/common/resources'; +import { extname, basename, isEqual, isEqualOrParent } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { guessMimeTypes } from 'vs/base/common/mime'; @@ -175,7 +175,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr } // Check for snippets - if (isEqualOrParent(resource, joinPath(this.environmentService.userRoamingDataHome, 'snippets'))) { + if (isEqualOrParent(resource, this.environmentService.snippetsHome)) { return 'snippets'; } diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 1a0152f4179..49e30505e46 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -155,3 +155,37 @@ .xterm.xterm-cursor-pointer { cursor: pointer!important; } + +/* Rotate icon when terminal is in the sidebar */ +.monaco-workbench .part.sidebar .title-actions .terminal-action.codicon-split-horizontal { + transform: rotate(-90deg); +} + +.monaco-workbench .part > .title > .title-actions .switch-terminal { + display: flex; + align-items: center; + font-size: 11px; + margin-right: 0.3em; + height: 20px; + flex-shrink: 1; + margin-top: 7px; +} + +.monaco-workbench.mac .part > .title > .title-actions .switch-terminal { + border-radius: 4px; +} + +.monaco-workbench .part > .title > .title-actions .switch-terminal > .monaco-select-box { + border: none !important; + display: block !important; + background-color: unset !important; +} + +.monaco-pane-view .pane > .pane-header .monaco-action-bar .switch-terminal.action-item.select-container { + border: none !important; +} + +.monaco-workbench .part > .title > .title-actions .switch-terminal > .monaco-select-box { + padding: 0 22px 0 6px; +} + diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 9ad11415ffe..1d0e690a6e1 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -15,15 +15,12 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions as ActionBarExtensions, IActionBarRegistry, Scope } from 'vs/workbench/browser/actions'; import * as panel from 'vs/workbench/browser/panel'; -import { getQuickNavigateHandler } from 'vs/workbench/browser/parts/quickopen/quickopen'; -import { Extensions as QuickOpenExtensions, IQuickOpenRegistry, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; +import { getQuickNavigateHandler } from 'vs/workbench/browser/quickaccess'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { Extensions as ViewContainerExtensions, IViewContainersRegistry, ViewContainerLocation, IViewsRegistry } from 'vs/workbench/common/views'; -import { ClearSelectionTerminalAction, ClearTerminalAction, CopyTerminalSelectionAction, CreateNewInActiveWorkspaceTerminalAction, CreateNewTerminalAction, DeleteToLineStartTerminalAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, FindNext, FindPrevious, FocusActiveTerminalAction, FocusNextPaneTerminalAction, FocusNextTerminalAction, FocusPreviousPaneTerminalAction, FocusPreviousTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, KillTerminalAction, MoveToLineEndTerminalAction, MoveToLineStartTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, RenameTerminalAction, ResizePaneDownTerminalAction, ResizePaneLeftTerminalAction, ResizePaneRightTerminalAction, ResizePaneUpTerminalAction, RunActiveFileInTerminalAction, RunSelectedTextInTerminalAction, ScrollDownPageTerminalAction, ScrollDownTerminalAction, ScrollToBottomTerminalAction, ScrollToNextCommandAction, ScrollToPreviousCommandAction, ScrollToTopTerminalAction, ScrollUpPageTerminalAction, ScrollUpTerminalAction, SelectAllTerminalAction, SelectDefaultShellWindowsTerminalAction, SelectToNextCommandAction, SelectToNextLineAction, SelectToPreviousCommandAction, SelectToPreviousLineAction, SplitInActiveWorkspaceTerminalAction, SplitTerminalAction, TerminalPasteAction, TERMINAL_PICKER_PREFIX, ToggleCaseSensitiveCommand, ToggleEscapeSequenceLoggingAction, ToggleRegexCommand, ToggleTerminalAction, ToggleWholeWordCommand, NavigationModeFocusPreviousTerminalAction, NavigationModeFocusNextTerminalAction, NavigationModeExitTerminalAction, ManageWorkspaceShellPermissionsTerminalCommand, CreateNewWithCwdTerminalAction, RenameWithArgTerminalAction, SendSequenceTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; +import { ClearSelectionTerminalAction, ClearTerminalAction, CopyTerminalSelectionAction, CreateNewInActiveWorkspaceTerminalAction, CreateNewTerminalAction, DeleteToLineStartTerminalAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, FindNext, FindPrevious, FocusActiveTerminalAction, FocusNextPaneTerminalAction, FocusNextTerminalAction, FocusPreviousPaneTerminalAction, FocusPreviousTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, KillTerminalAction, MoveToLineEndTerminalAction, MoveToLineStartTerminalAction, QuickAccessTerminalAction, RenameTerminalAction, ResizePaneDownTerminalAction, ResizePaneLeftTerminalAction, ResizePaneRightTerminalAction, ResizePaneUpTerminalAction, RunActiveFileInTerminalAction, RunSelectedTextInTerminalAction, ScrollDownPageTerminalAction, ScrollDownTerminalAction, ScrollToBottomTerminalAction, ScrollToNextCommandAction, ScrollToPreviousCommandAction, ScrollToTopTerminalAction, ScrollUpPageTerminalAction, ScrollUpTerminalAction, SelectAllTerminalAction, SelectDefaultShellWindowsTerminalAction, SelectToNextCommandAction, SelectToNextLineAction, SelectToPreviousCommandAction, SelectToPreviousLineAction, SplitInActiveWorkspaceTerminalAction, SplitTerminalAction, TerminalPasteAction, ToggleCaseSensitiveCommand, ToggleEscapeSequenceLoggingAction, ToggleRegexCommand, ToggleTerminalAction, ToggleWholeWordCommand, NavigationModeFocusPreviousTerminalAction, NavigationModeFocusNextTerminalAction, NavigationModeExitTerminalAction, ManageWorkspaceShellPermissionsTerminalCommand, CreateNewWithCwdTerminalAction, RenameWithArgTerminalAction, SendSequenceTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { TerminalViewPane } from 'vs/workbench/contrib/terminal/browser/terminalView'; -import { TerminalPickerHandler } from 'vs/workbench/contrib/terminal/browser/terminalQuickOpen'; import { KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_VIEW_ID, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, TERMINAL_ACTION_CATEGORY, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { registerColors } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { setupTerminalCommands } from 'vs/workbench/contrib/terminal/browser/terminalCommands'; @@ -39,6 +36,8 @@ import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal import { BrowserFeatures } from 'vs/base/browser/canIUse'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; +import { TerminalQuickAccessProvider } from 'vs/workbench/contrib/terminal/browser/terminalsQuickAccess'; registerSingleton(ITerminalService, TerminalService, true); @@ -46,27 +45,24 @@ if (platform.isWeb) { registerShellConfiguration(); } -const quickOpenRegistry = (Registry.as(QuickOpenExtensions.Quickopen)); - const inTerminalsPicker = 'inTerminalPicker'; +const quickAccessRegistry = (Registry.as(QuickAccessExtensions.Quickaccess)); -quickOpenRegistry.registerQuickOpenHandler( - QuickOpenHandlerDescriptor.create( - TerminalPickerHandler, - TerminalPickerHandler.ID, - TERMINAL_PICKER_PREFIX, - inTerminalsPicker, - nls.localize('quickOpen.terminal', "Show All Opened Terminals") - ) -); +quickAccessRegistry.registerQuickAccessProvider({ + ctor: TerminalQuickAccessProvider, + prefix: TerminalQuickAccessProvider.PREFIX, + contextKey: inTerminalsPicker, + placeholder: nls.localize('tasksQuickAccessPlaceholder', "Type the name of a terminal to open."), + helpEntries: [{ description: nls.localize('tasksQuickAccessHelp', "Show All Opened Terminals"), needsEditor: false }] +}); -const quickOpenNavigateNextInTerminalPickerId = 'workbench.action.quickOpenNavigateNextInTerminalPicker'; +const quickAccessNavigateNextInTerminalPickerId = 'workbench.action.quickOpenNavigateNextInTerminalPicker'; CommandsRegistry.registerCommand( - { id: quickOpenNavigateNextInTerminalPickerId, handler: getQuickNavigateHandler(quickOpenNavigateNextInTerminalPickerId, true) }); + { id: quickAccessNavigateNextInTerminalPickerId, handler: getQuickNavigateHandler(quickAccessNavigateNextInTerminalPickerId, true) }); -const quickOpenNavigatePreviousInTerminalPickerId = 'workbench.action.quickOpenNavigatePreviousInTerminalPicker'; +const quickAccessNavigatePreviousInTerminalPickerId = 'workbench.action.quickOpenNavigatePreviousInTerminalPicker'; CommandsRegistry.registerCommand( - { id: quickOpenNavigatePreviousInTerminalPickerId, handler: getQuickNavigateHandler(quickOpenNavigatePreviousInTerminalPickerId, false) }); + { id: quickAccessNavigatePreviousInTerminalPickerId, handler: getQuickNavigateHandler(quickAccessNavigatePreviousInTerminalPickerId, false) }); const configurationRegistry = Registry.as(Extensions.Configuration); @@ -282,6 +278,11 @@ configurationRegistry.registerConfiguration({ type: 'boolean', default: true }, + 'terminal.integrated.allowMnemonics': { + markdownDescription: nls.localize('terminal.integrated.allowMnemonics', "Whether to allow menubar mnemonics (eg. alt+f) to trigger the open the menubar. Note that this will cause all alt keystrokes will skip the shell when true. This does nothing on macOS."), + type: 'boolean', + default: false + }, 'terminal.integrated.inheritEnv': { markdownDescription: nls.localize('terminal.integrated.inheritEnv', "Whether new shells should inherit their environment from VS Code. This is not supported on Windows."), type: 'boolean', @@ -356,22 +357,24 @@ configurationRegistry.registerConfiguration({ }); const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenTermAction, QuickOpenTermAction.ID, QuickOpenTermAction.LABEL), 'Terminal: Switch Active Terminal', nls.localize('terminal', "Terminal")); -const actionBarRegistry = Registry.as(ActionBarExtensions.Actionbar); -actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionTermContributor); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickAccessTerminalAction, QuickAccessTerminalAction.ID, QuickAccessTerminalAction.LABEL), 'Terminal: Switch Active Terminal', nls.localize('terminal', "Terminal")); const VIEW_CONTAINER = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: TERMINAL_VIEW_ID, name: nls.localize('terminal', "Terminal"), ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [TERMINAL_VIEW_ID, TERMINAL_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), - focusCommand: { id: TERMINAL_COMMAND_ID.FOCUS } + focusCommand: { id: TERMINAL_COMMAND_ID.FOCUS }, + hideIfEmpty: true, + order: 3 }, ViewContainerLocation.Panel); Registry.as(panel.Extensions.Panels).setDefaultPanelId(TERMINAL_VIEW_ID); Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ id: TERMINAL_VIEW_ID, name: nls.localize('terminal', "Terminal"), + containerIcon: 'codicon-terminal', canToggleVisibility: false, + canMoveView: true, ctorDescriptor: new SyncDescriptor(TerminalViewPane) }], VIEW_CONTAINER); @@ -439,10 +442,7 @@ actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ManageWorkspa actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(RenameTerminalAction, RenameTerminalAction.ID, RenameTerminalAction.LABEL), 'Terminal: Rename', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusTerminalFindWidgetAction, FocusTerminalFindWidgetAction.ID, FocusTerminalFindWidgetAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_F -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Focus Find Widget', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusTerminalFindWidgetAction, FocusTerminalFindWidgetAction.ID, FocusTerminalFindWidgetAction.LABEL, { - primary: KeyMod.CtrlCmd | KeyCode.KEY_F -}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Focus Find Widget', category); +}, ContextKeyExpr.or(KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FOCUS)), 'Terminal: Focus Find Widget', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(HideTerminalFindWidgetAction, HideTerminalFindWidgetAction.ID, HideTerminalFindWidgetAction.LABEL, { primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape] @@ -530,43 +530,25 @@ actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(NavigationMod }, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED)), 'Terminal: Exit Navigation Mode', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(NavigationModeFocusPreviousTerminalAction, NavigationModeFocusPreviousTerminalAction.ID, NavigationModeFocusPreviousTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.UpArrow -}, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED)), 'Terminal: Focus Previous Line (Navigation Mode)', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(NavigationModeFocusPreviousTerminalAction, NavigationModeFocusPreviousTerminalAction.ID, NavigationModeFocusPreviousTerminalAction.LABEL, { - primary: KeyMod.CtrlCmd | KeyCode.UpArrow -}, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED)), 'Terminal: Focus Previous Line (Navigation Mode)', category); +}, ContextKeyExpr.or(ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED), ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED))), 'Terminal: Focus Previous Line (Navigation Mode)', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(NavigationModeFocusNextTerminalAction, NavigationModeFocusNextTerminalAction.ID, NavigationModeFocusNextTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.DownArrow -}, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED)), 'Terminal: Focus Next Line (Navigation Mode)', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(NavigationModeFocusNextTerminalAction, NavigationModeFocusNextTerminalAction.ID, NavigationModeFocusNextTerminalAction.LABEL, { - primary: KeyMod.CtrlCmd | KeyCode.DownArrow -}, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED)), 'Terminal: Focus Next Line (Navigation Mode)', category); +}, ContextKeyExpr.or(ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED), ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED))), 'Terminal: Focus Next Line (Navigation Mode)', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SelectToPreviousLineAction, SelectToPreviousLineAction.ID, SelectToPreviousLineAction.LABEL), 'Terminal: Select To Previous Line', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SelectToNextLineAction, SelectToNextLineAction.ID, SelectToNextLineAction.LABEL), 'Terminal: Select To Next Line', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleEscapeSequenceLoggingAction, ToggleEscapeSequenceLoggingAction.ID, ToggleEscapeSequenceLoggingAction.LABEL), 'Terminal: Toggle Escape Sequence Logging', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleRegexCommand, ToggleRegexCommand.ID, ToggleRegexCommand.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_R, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_R } -}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Toggle find using regex', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleRegexCommand, ToggleRegexCommand.ID, ToggleRegexCommand.LABEL, { - primary: KeyMod.Alt | KeyCode.KEY_R, - mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_R } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Toggle find using regex', category); +}, ContextKeyExpr.or(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED)), 'Terminal: Toggle find using regex', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleWholeWordCommand, ToggleWholeWordCommand.ID, ToggleWholeWordCommand.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_W, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_W } -}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Toggle find using whole word', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleWholeWordCommand, ToggleWholeWordCommand.ID, ToggleWholeWordCommand.LABEL, { - primary: KeyMod.Alt | KeyCode.KEY_W, - mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_W } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Toggle find using whole word', category); +}, ContextKeyExpr.or(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED)), 'Terminal: Toggle find using whole word', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleCaseSensitiveCommand, ToggleCaseSensitiveCommand.ID, ToggleCaseSensitiveCommand.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_C, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_C } -}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Toggle find using case sensitive', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleCaseSensitiveCommand, ToggleCaseSensitiveCommand.ID, ToggleCaseSensitiveCommand.LABEL, { - primary: KeyMod.Alt | KeyCode.KEY_C, - mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_C } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Toggle find using case sensitive', category); +}, ContextKeyExpr.or(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED)), 'Terminal: Toggle find using case sensitive', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FindNext, FindNext.ID, FindNext.LABEL, { primary: KeyCode.F3, mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_G, secondary: [KeyCode.F3] } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index bac468cd33a..e28e9e597e5 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -131,6 +131,14 @@ export interface ITerminalService { findNext(): void; findPrevious(): void; + /** + * Link handlers can be registered here to allow intercepting links clicked in the terminal. + * When a link is clicked, the link will be considered handled when the first interceptor + * resolves with true. It will be considered not handled when _all_ link handlers resolve with + * false, or 3 seconds have elapsed. + */ + addLinkHandler(key: string, callback: TerminalLinkHandlerCallback): IDisposable; + selectDefaultWindowsShell(): Promise; setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void; @@ -179,6 +187,18 @@ export enum WindowsShellType { } export type TerminalShellType = WindowsShellType | undefined; +export const LINK_INTERCEPT_THRESHOLD = 3000; + +export interface ITerminalBeforeHandleLinkEvent { + terminal?: ITerminalInstance; + /** The text of the link */ + link: string; + /** Call with whether the link was handled by the interceptor */ + resolve(wasHandled: boolean): void; +} + +export type TerminalLinkHandlerCallback = (e: ITerminalBeforeHandleLinkEvent) => Promise; + export interface ITerminalInstance { /** * The ID of the terminal instance, this is an arbitrary number only used to identify the @@ -240,6 +260,11 @@ export interface ITerminalInstance { */ onExit: Event; + /** + * Attach a listener to intercept and handle link clicks in the terminal. + */ + onBeforeHandleLink: Event; + readonly exitCode: number | undefined; processReady: Promise; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 7efd8924fe2..70ea819b528 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -9,22 +9,16 @@ import { EndOfLinePreference } from 'vs/editor/common/model'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { TERMINAL_VIEW_ID, ITerminalConfigHelper, TitleEventSource, TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; -import { TogglePanelAction } from 'vs/workbench/browser/panel'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; +import { attachSelectBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { IQuickInputService, IPickOptions, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { ActionBarContributor } from 'vs/workbench/browser/actions'; -import { TerminalEntry } from 'vs/workbench/contrib/terminal/browser/terminalQuickOpen'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { timeout } from 'vs/base/common/async'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import { ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; @@ -35,8 +29,12 @@ import { isWindows } from 'vs/base/common/platform'; import { withNullAsUndefined } from 'vs/base/common/types'; import { ITerminalInstance, ITerminalService, Direction } from 'vs/workbench/contrib/terminal/browser/terminal'; import { Action2 } from 'vs/platform/actions/common/actions'; - -export const TERMINAL_PICKER_PREFIX = 'term '; +import { TerminalQuickAccessProvider } from 'vs/workbench/contrib/terminal/browser/terminalsQuickAccess'; +import { ToggleViewAction } from 'vs/workbench/browser/actions/layoutActions'; +import { IViewsService, IViewDescriptorService } from 'vs/workbench/common/views'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { addClass } from 'vs/base/browser/dom'; +import { selectBorder } from 'vs/platform/theme/common/colorRegistry'; async function getCwdForSplit(configHelper: ITerminalConfigHelper, instance: ITerminalInstance, folders?: IWorkspaceFolder[], commandService?: ICommandService): Promise { switch (configHelper.config.splitCwd) { @@ -65,18 +63,20 @@ async function getCwdForSplit(configHelper: ITerminalConfigHelper, instance: ITe } } -export class ToggleTerminalAction extends TogglePanelAction { +export class ToggleTerminalAction extends ToggleViewAction { public static readonly ID = TERMINAL_COMMAND_ID.TOGGLE; public static readonly LABEL = nls.localize('workbench.action.terminal.toggleTerminal', "Toggle Integrated Terminal"); constructor( id: string, label: string, - @IPanelService panelService: IPanelService, + @IViewsService viewsService: IViewsService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IContextKeyService contextKeyService: IContextKeyService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ITerminalService private readonly terminalService: ITerminalService ) { - super(id, label, TERMINAL_VIEW_ID, panelService, layoutService); + super(id, label, TERMINAL_VIEW_ID, viewsService, viewDescriptorService, contextKeyService, layoutService); } public run(event?: any): Promise { @@ -118,29 +118,6 @@ export class KillTerminalAction extends Action { } } -export class QuickKillTerminalAction extends Action { - - public static readonly ID = TERMINAL_COMMAND_ID.QUICK_KILL; - public static readonly LABEL = nls.localize('workbench.action.terminal.quickKill', "Kill Terminal Instance"); - - constructor( - id: string, label: string, - private terminalEntry: TerminalEntry, - @IQuickOpenService private readonly quickOpenService: IQuickOpenService - ) { - super(id, label, 'terminal-action kill'); - } - - public async run(event?: any): Promise { - const instance = this.terminalEntry.instance; - if (instance) { - instance.dispose(true); - } - await timeout(50); - return this.quickOpenService.show(TERMINAL_PICKER_PREFIX, undefined); - } -} - /** * Copies the terminal selection. Note that since the command palette takes focus from the terminal, * this cannot be triggered through the command palette. @@ -745,10 +722,10 @@ export class SwitchTerminalActionViewItem extends SelectActionViewItem { constructor( action: IAction, @ITerminalService private readonly terminalService: ITerminalService, - @IThemeService themeService: IThemeService, + @IThemeService private readonly themeService: IThemeService, @IContextViewService contextViewService: IContextViewService ) { - super(null, action, terminalService.getTabLabels().map(label => { text: label }), terminalService.activeTabIndex, contextViewService, { ariaLabel: nls.localize('terminals', 'Open Terminals.') }); + super(null, action, getTerminalSelectOpenItems(terminalService), terminalService.activeTabIndex, contextViewService, { ariaLabel: nls.localize('terminals', 'Open Terminals.') }); this._register(terminalService.onInstancesChanged(this._updateItems, this)); this._register(terminalService.onActiveTabChanged(this._updateItems, this)); @@ -757,12 +734,24 @@ export class SwitchTerminalActionViewItem extends SelectActionViewItem { this._register(attachSelectBoxStyler(this.selectBox, themeService)); } - private _updateItems(): void { - const items = this.terminalService.getTabLabels().map(label => { text: label }); - items.push({ text: SwitchTerminalActionViewItem.SEPARATOR, isDisabled: true }); - items.push({ text: SelectDefaultShellWindowsTerminalAction.LABEL }); - this.setOptions(items, this.terminalService.activeTabIndex); + render(container: HTMLElement): void { + super.render(container); + addClass(container, 'switch-terminal'); + this._register(attachStylerCallback(this.themeService, { selectBorder }, colors => { + container.style.border = colors.selectBorder ? `1px solid ${colors.selectBorder}` : ''; + })); } + + private _updateItems(): void { + this.setOptions(getTerminalSelectOpenItems(this.terminalService), this.terminalService.activeTabIndex); + } +} + +function getTerminalSelectOpenItems(terminalService: ITerminalService): ISelectOptionItem[] { + const items = terminalService.getTabLabels().map(label => { text: label }); + items.push({ text: SwitchTerminalActionViewItem.SEPARATOR, isDisabled: true }); + items.push({ text: SelectDefaultShellWindowsTerminalAction.LABEL }); + return items; } export class ScrollDownTerminalAction extends Action { @@ -1022,15 +1011,14 @@ export class RenameTerminalAction extends Action { constructor( id: string, label: string, - @IQuickOpenService protected quickOpenService: IQuickOpenService, @IQuickInputService protected quickInputService: IQuickInputService, @ITerminalService protected terminalService: ITerminalService ) { super(id, label); } - public async run(entry?: TerminalEntry): Promise { - const terminalInstance = entry ? entry.instance : this.terminalService.getActiveInstance(); + public async run(): Promise { + const terminalInstance = this.terminalService.getActiveInstance(); if (!terminalInstance) { return Promise.resolve(undefined); } @@ -1103,64 +1091,21 @@ export class HideTerminalFindWidgetAction extends Action { } } -export class QuickOpenActionTermContributor extends ActionBarContributor { - - constructor( - @IInstantiationService private readonly instantiationService: IInstantiationService - ) { - super(); - } - - public getActions(context: any): ReadonlyArray { - const actions: Action[] = []; - if (context.element instanceof TerminalEntry) { - actions.push(this.instantiationService.createInstance(RenameTerminalQuickOpenAction, RenameTerminalQuickOpenAction.ID, RenameTerminalQuickOpenAction.LABEL, context.element)); - actions.push(this.instantiationService.createInstance(QuickKillTerminalAction, QuickKillTerminalAction.ID, QuickKillTerminalAction.LABEL, context.element)); - } - return actions; - } - - public hasActions(context: any): boolean { - return true; - } -} - -export class QuickOpenTermAction extends Action { +export class QuickAccessTerminalAction extends Action { public static readonly ID = TERMINAL_COMMAND_ID.QUICK_OPEN_TERM; - public static readonly LABEL = nls.localize('quickOpenTerm', "Switch Active Terminal"); + public static readonly LABEL = nls.localize('quickAccessTerminal', "Switch Active Terminal"); constructor( id: string, label: string, - @IQuickOpenService private readonly quickOpenService: IQuickOpenService + @IQuickInputService private readonly quickInputService: IQuickInputService ) { super(id, label); } - public run(): Promise { - return this.quickOpenService.show(TERMINAL_PICKER_PREFIX, undefined); - } -} - -export class RenameTerminalQuickOpenAction extends RenameTerminalAction { - - constructor( - id: string, label: string, - private terminal: TerminalEntry, - @IQuickOpenService quickOpenService: IQuickOpenService, - @IQuickInputService quickInputService: IQuickInputService, - @ITerminalService terminalService: ITerminalService - ) { - super(id, label, quickOpenService, quickInputService, terminalService); - this.class = 'codicon codicon-gear'; - } - - public async run(): Promise { - await super.run(this.terminal); - // This timeout is needed to make sure the previous quickOpen has time to close before we show the next one - await timeout(50); - await this.quickOpenService.show(TERMINAL_PICKER_PREFIX, undefined); + async run(): Promise { + this.quickInputService.quickAccess.show(TerminalQuickAccessProvider.PREFIX); } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts index d182592bf01..c149a469479 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts @@ -21,6 +21,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { InstallRecommendedExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IProductService } from 'vs/platform/product/common/productService'; import { XTermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; const MINIMUM_FONT_SIZE = 6; const MAXIMUM_FONT_SIZE = 25; @@ -47,7 +48,8 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { @IStorageService private readonly _storageService: IStorageService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IProductService private readonly productService: IProductService + @IProductService private readonly productService: IProductService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService ) { this._updateConfig(); this._configurationService.onDidChangeConfiguration(e => { @@ -55,6 +57,9 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { this._updateConfig(); } }); + + // opt-in to syncing + storageKeysSyncRegistryService.registerStorageKey({ key: 'terminalConfigHelper/launchRecommendationsIgnore', version: 1 }); } private _updateConfig(): void { @@ -119,9 +124,23 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { fontSize, letterSpacing, lineHeight, - charWidth: rect && rect.width ? rect.width : 0, - charHeight: rect && rect.height ? Math.ceil(rect.height) : 0 + charWidth: 0, + charHeight: 0 }; + + if (rect && rect.width && rect.height) { + this._lastFontMeasurement.charHeight = Math.ceil(rect.height); + // Char width is calculated differently for DOM and the other renderer types. Refer to + // how each renderer updates their dimensions in xterm.js + if (this.config.rendererType === 'dom') { + this._lastFontMeasurement.charWidth = rect.width; + } else { + const scaledCharWidth = rect.width * window.devicePixelRatio; + const scaledCellWidth = scaledCharWidth + Math.round(letterSpacing); + this._lastFontMeasurement.charWidth = Math.round(scaledCellWidth / window.devicePixelRatio); + } + } + return this._lastFontMeasurement; } @@ -162,14 +181,14 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { // Get the character dimensions from xterm if it's available if (xtermCore) { - if (xtermCore._charSizeService && xtermCore._charSizeService.width && xtermCore._charSizeService.height) { + if (xtermCore._renderService && xtermCore._renderService.dimensions?.actualCellWidth && xtermCore._renderService.dimensions?.actualCellHeight) { return { fontFamily, fontSize, letterSpacing, lineHeight, - charHeight: xtermCore._charSizeService.height, - charWidth: xtermCore._charSizeService.width + charHeight: xtermCore._renderService.dimensions.actualCellHeight, + charWidth: xtermCore._renderService.dimensions.actualCellWidth }; } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index a391706e51f..bac45fb2897 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -22,15 +22,15 @@ import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry'; -import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; +import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/terminalWidgetManager'; import { IShellLaunchConfig, ITerminalDimensions, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_VIEW_ID, IWindowsShellHelper, SHELL_PATH_INVALID_EXIT_CODE, SHELL_PATH_DIRECTORY_EXIT_CODE, SHELL_CWD_INVALID_EXIT_CODE, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, INavigationMode, TitleEventSource, TERMINAL_COMMAND_ID, LEGACY_CONSOLE_MODE_EXIT_CODE } from 'vs/workbench/contrib/terminal/common/terminal'; import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_FOREGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { TerminalLinkHandler } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { ITerminalInstanceService, ITerminalInstance, TerminalShellType } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalInstanceService, ITerminalInstance, TerminalShellType, ITerminalBeforeHandleLinkEvent } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager'; import { Terminal as XTermTerminal, IBuffer, ITerminalAddon } from 'xterm'; import { SearchAddon, ISearchOptions } from 'xterm-addon-search'; @@ -40,7 +40,7 @@ import { NavigationModeAddon } from 'vs/workbench/contrib/terminal/browser/addon import { XTermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService, IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; // How long in milliseconds should an average frame take to render for a notification to appear // which suggests the fallback DOM-based renderer @@ -272,6 +272,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { public get onMaximumDimensionsChanged(): Event { return this._onMaximumDimensionsChanged.event; } private readonly _onFocus = new Emitter(); public get onFocus(): Event { return this._onFocus.event; } + private readonly _onBeforeHandleLink = new Emitter(); + public get onBeforeHandleLink(): Event { return this._onBeforeHandleLink.event; } public constructor( private readonly _terminalFocusContextKey: IContextKey, @@ -290,6 +292,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { @ILogService private readonly _logService: ILogService, @IStorageService private readonly _storageService: IStorageService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, + @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService, @IOpenerService private readonly _openerService: IOpenerService ) { super(); @@ -523,11 +526,20 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { }); } this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, xterm, this._processManager, this._configHelper); + this._linkHandler.onBeforeHandleLink(e => { + e.terminal = this; + this._onBeforeHandleLink.fire(e); + }); }); this._commandTrackerAddon = new CommandTrackerAddon(); this._xterm.loadAddon(this._commandTrackerAddon); - this._register(this._themeService.onThemeChange(theme => this._updateTheme(xterm, theme))); + this._register(this._themeService.onDidColorThemeChange(theme => this._updateTheme(xterm, theme))); + this._register(this._viewDescriptorService.onDidChangeLocation(({ views }) => { + if (views.some(v => v.id === TERMINAL_VIEW_ID)) { + this._updateTheme(xterm); + } + })); return xterm; } @@ -595,19 +607,30 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return false; } - // Skip processing by xterm.js of keyboard events that resolve to commands described - // within commandsToSkipShell const standardKeyboardEvent = new StandardKeyboardEvent(event); const resolveResult = this._keybindingService.softDispatch(standardKeyboardEvent, standardKeyboardEvent.target); + // Respect chords if the allowChords setting is set and it's not Escape. Escape is // handled specially for Zen Mode's Escape, Escape chord, plus it's important in // terminals generally - const allowChords = resolveResult?.enterChord && this._configHelper.config.allowChords && event.key !== 'Escape'; - if (this._keybindingService.inChordMode || allowChords || resolveResult && this._skipTerminalCommands.some(k => k === resolveResult.commandId)) { + const isValidChord = resolveResult?.enterChord && this._configHelper.config.allowChords && event.key !== 'Escape'; + if (this._keybindingService.inChordMode || isValidChord) { event.preventDefault(); return false; } + // Skip processing by xterm.js of keyboard events that resolve to commands described + // within commandsToSkipShell + if (resolveResult && this._skipTerminalCommands.some(k => k === resolveResult.commandId)) { + event.preventDefault(); + return false; + } + + // Skip processing by xterm.js of keyboard events that match menu bar mnemonics + if (this._configHelper.config.allowMnemonics && !platform.isMacintosh && event.altKey) { + return false; + } + // If tab focus mode is on, tab is not passed to the terminal if (TabFocus.getTabFocusMode() && event.keyCode === 9) { return false; @@ -1431,13 +1454,14 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._shellLaunchConfig.env = shellLaunchConfig.env; } - private _getXtermTheme(theme?: ITheme): any { + private _getXtermTheme(theme?: IColorTheme): any { if (!theme) { - theme = this._themeService.getTheme(); + theme = this._themeService.getColorTheme(); } + const location = this._viewDescriptorService.getViewLocation(TERMINAL_VIEW_ID)!; const foregroundColor = theme.getColor(TERMINAL_FOREGROUND_COLOR); - const backgroundColor = theme.getColor(TERMINAL_BACKGROUND_COLOR) || theme.getColor(PANEL_BACKGROUND); + const backgroundColor = theme.getColor(TERMINAL_BACKGROUND_COLOR) || (location === ViewContainerLocation.Sidebar ? theme.getColor(SIDE_BAR_BACKGROUND) : theme.getColor(PANEL_BACKGROUND)); const cursorColor = theme.getColor(TERMINAL_CURSOR_FOREGROUND_COLOR) || foregroundColor; const cursorAccentColor = theme.getColor(TERMINAL_CURSOR_BACKGROUND_COLOR) || backgroundColor; const selectionColor = theme.getColor(TERMINAL_SELECTION_BACKGROUND_COLOR); @@ -1467,7 +1491,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { }; } - private _updateTheme(xterm: XTermTerminal, theme?: ITheme): void { + private _updateTheme(xterm: XTermTerminal, theme?: IColorTheme): void { xterm.setOption('theme', this._getXtermTheme(theme)); } @@ -1486,7 +1510,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { // Border const border = theme.getColor(activeContrastBorder); if (border) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts index df405001f97..e714542e72c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts @@ -69,7 +69,7 @@ export class TerminalInstanceService implements ITerminalInstanceService { throw new Error('Not implemented'); } - public getDefaultShellAndArgs(useAutomationShell: boolean, ): Promise<{ shell: string, args: string[] | string | undefined }> { + public getDefaultShellAndArgs(useAutomationShell: boolean,): Promise<{ shell: string, args: string[] | string | undefined }> { return new Promise(r => this._onRequestDefaultShellAndArgs.fire({ useAutomationShell, callback: (shell, args) => r({ shell, args }) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts index 18169644b84..9a78dca15f8 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts @@ -16,9 +16,11 @@ import { IFileService } from 'vs/platform/files/common/files'; import { Terminal, ILinkMatcherOptions, IViewportRange } from 'xterm'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { posix, win32 } from 'vs/base/common/path'; -import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalInstanceService, ITerminalBeforeHandleLinkEvent, LINK_INTERCEPT_THRESHOLD } from 'vs/workbench/contrib/terminal/browser/terminal'; import { OperatingSystem, isMacintosh } from 'vs/base/common/platform'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; +import { Emitter, Event } from 'vs/base/common/event'; +import { ILogService } from 'vs/platform/log/common/log'; const pathPrefix = '(\\.\\.?|\\~)'; const pathSeparatorClause = '\\/'; @@ -58,7 +60,7 @@ const CUSTOM_LINK_PRIORITY = -1; /** Lowest */ const LOCAL_LINK_PRIORITY = -2; -export type XtermLinkMatcherHandler = (event: MouseEvent, uri: string) => boolean | void; +export type XtermLinkMatcherHandler = (event: MouseEvent, link: string) => Promise; export type XtermLinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void; interface IPath { @@ -66,14 +68,28 @@ interface IPath { normalize(path: string): string; } -export class TerminalLinkHandler { - private readonly _hoverDisposables = new DisposableStore(); +export class TerminalLinkHandler extends DisposableStore { private _widgetManager: TerminalWidgetManager | undefined; private _processCwd: string | undefined; private _gitDiffPreImagePattern: RegExp; private _gitDiffPostImagePattern: RegExp; private readonly _tooltipCallback: (event: MouseEvent, uri: string, location: IViewportRange) => boolean | void; private readonly _leaveCallback: () => void; + private _hasBeforeHandleLinkListeners = false; + + protected static _LINK_INTERCEPT_THRESHOLD = LINK_INTERCEPT_THRESHOLD; + public static readonly LINK_INTERCEPT_THRESHOLD = TerminalLinkHandler._LINK_INTERCEPT_THRESHOLD; + + private readonly _onBeforeHandleLink = this.add(new Emitter({ + onFirstListenerAdd: () => this._hasBeforeHandleLinkListeners = true, + onLastListenerRemove: () => this._hasBeforeHandleLinkListeners = false + })); + /** + * Allows intercepting links and handling them outside of the default link handler. When fired + * the listener has a set amount of time to handle the link or the default handler will fire. + * This was designed to only be handled by a single listener. + */ + public get onBeforeHandleLink(): Event { return this._onBeforeHandleLink.event; } constructor( private _xterm: Terminal, @@ -83,8 +99,11 @@ export class TerminalLinkHandler { @IEditorService private readonly _editorService: IEditorService, @IConfigurationService private readonly _configurationService: IConfigurationService, @ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService, - @IFileService private readonly _fileService: IFileService + @IFileService private readonly _fileService: IFileService, + @ILogService private readonly _logService: ILogService ) { + super(); + // Matches '--- a/src/file1', capturing 'src/file1' in group 1 this._gitDiffPreImagePattern = /^--- a\/(\S*)/; // Matches '+++ b/src/file1', capturing 'src/file1' in group 1 @@ -211,19 +230,40 @@ export class TerminalLinkHandler { this._xterm.registerLinkMatcher(this._gitDiffPostImagePattern, wrappedHandler, options); } - public dispose(): void { - this._hoverDisposables.dispose(); - } - - private _wrapLinkHandler(handler: (uri: string) => boolean | void): XtermLinkMatcherHandler { - return (event: MouseEvent, uri: string) => { + protected _wrapLinkHandler(handler: (link: string) => void): XtermLinkMatcherHandler { + return async (event: MouseEvent, link: string) => { // Prevent default electron link handling so Alt+Click mode works normally event.preventDefault(); // Require correct modifier on click if (!this._isLinkActivationModifierDown(event)) { - return false; + return; } - return handler(uri); + + // Allow the link to be intercepted if there are listeners + if (this._hasBeforeHandleLinkListeners) { + const wasHandled = await new Promise(r => { + const timeoutId = setTimeout(() => { + canceled = true; + this._logService.error(`An extension intecepted a terminal link but it timed out after ${TerminalLinkHandler.LINK_INTERCEPT_THRESHOLD / 1000} seconds`); + r(false); + }, TerminalLinkHandler.LINK_INTERCEPT_THRESHOLD); + let canceled = false; + const resolve = (handled: boolean) => { + if (!canceled) { + clearTimeout(timeoutId); + r(handled); + } + }; + this._onBeforeHandleLink.fire({ link, resolve }); + }); + if (!wasHandled) { + handler(link); + } + return; + } + + // Just call the handler if there is no before listener + handler(link); }; } @@ -244,18 +284,17 @@ export class TerminalLinkHandler { return this._gitDiffPostImagePattern; } - private _handleLocalLink(link: string): PromiseLike { - return this._resolvePath(link).then(resolvedLink => { - if (!resolvedLink) { - return Promise.resolve(null); - } - const lineColumnInfo: LineColumnInfo = this.extractLineColumnInfo(link); - const selection: ITextEditorSelection = { - startLineNumber: lineColumnInfo.lineNumber, - startColumn: lineColumnInfo.columnNumber - }; - return this._editorService.openEditor({ resource: resolvedLink, options: { pinned: true, selection } }); - }); + private async _handleLocalLink(link: string): Promise { + const resolvedLink = await this._resolvePath(link); + if (!resolvedLink) { + return; + } + const lineColumnInfo: LineColumnInfo = this.extractLineColumnInfo(link); + const selection: ITextEditorSelection = { + startLineNumber: lineColumnInfo.lineNumber, + startColumn: lineColumnInfo.columnNumber + }; + await this._editorService.openEditor({ resource: resolvedLink, options: { pinned: true, selection } }); } private _validateLocalLink(link: string, callback: (isValid: boolean) => void): void { @@ -270,7 +309,7 @@ export class TerminalLinkHandler { this._openerService.open(url, { allowTunneling: !!(this._processManager && this._processManager.remoteAuthority) }); } - private _isLinkActivationModifierDown(event: MouseEvent): boolean { + protected _isLinkActivationModifierDown(event: MouseEvent): boolean { const editorConf = this._configurationService.getValue<{ multiCursorModifier: 'ctrlCmd' | 'alt' }>('editor'); if (editorConf.multiCursorModifier === 'ctrlCmd') { return !!event.altKey; @@ -346,19 +385,19 @@ export class TerminalLinkHandler { return link; } - private _resolvePath(link: string): PromiseLike { + private async _resolvePath(link: string): Promise { if (!this._processManager) { throw new Error('Process manager is required'); } const preprocessedLink = this._preprocessPath(link); if (!preprocessedLink) { - return Promise.resolve(null); + return undefined; } const linkUrl = this.extractLinkUrl(preprocessedLink); if (!linkUrl) { - return Promise.resolve(null); + return undefined; } try { @@ -373,18 +412,20 @@ export class TerminalLinkHandler { uri = URI.file(linkUrl); } - return this._fileService.resolve(uri).then(stat => { + try { + const stat = await this._fileService.resolve(uri); if (stat.isDirectory) { - return null; + return undefined; } return uri; - }).catch(() => { + } + catch (e) { // Does not exist - return null; - }); + return undefined; + } } catch { // Errors in parsing the path - return Promise.resolve(null); + return undefined; } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index e267fc21c4a..e2152fc11ee 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -23,6 +23,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { Disposable } from 'vs/base/common/lifecycle'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { IEnvironmentVariableService, IMergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; /** The amount of time to consider terminal errors to be related to the launch */ const LAUNCHING_DURATION = 500; @@ -59,6 +60,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce private _latency: number = -1; private _latencyLastMeasured: number = 0; private _initialCwd: string | undefined; + private _extEnvironmentVariableCollection: IMergedEnvironmentVariableCollection | undefined; private readonly _onProcessReady = this._register(new Emitter()); public get onProcessReady(): Event { return this._onProcessReady.event; } @@ -87,7 +89,8 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @IProductService private readonly _productService: IProductService, @ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService, - @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService + @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, + @IEnvironmentVariableService private readonly _environmentVariableService: IEnvironmentVariableService ) { super(); this.ptyProcessReady = new Promise(c => { @@ -230,6 +233,11 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce const baseEnv = this._configHelper.config.inheritEnv ? processEnv : await this._terminalInstanceService.getMainProcessParentEnv(); const env = terminalEnvironment.createTerminalEnvironment(shellLaunchConfig, lastActiveWorkspace, envFromConfigValue, this._configurationResolverService, isWorkspaceShellAllowed, this._productService.version, this._configHelper.config.detectLocale, baseEnv); + // Fetch any extension environment additions and apply them + this._extEnvironmentVariableCollection = this._environmentVariableService.mergedCollection; + this._register(this._environmentVariableService.onDidChangeCollections(newCollection => this._onEnvironmentVariableCollectionChange(newCollection))); + this._extEnvironmentVariableCollection.applyToProcessEnvironment(env); + const useConpty = this._configHelper.config.windowsEnableConpty && !isScreenReaderModeEnabled; return this._terminalInstanceService.createTerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, useConpty); } @@ -304,4 +312,37 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce this._onProcessExit.fire(exitCode); } + + private _onEnvironmentVariableCollectionChange(newCollection: IMergedEnvironmentVariableCollection): void { + // TODO: React to changes in environment variable collections + // const newAdditions = this._extEnvironmentVariableCollection!.getNewAdditions(newCollection); + // if (newAdditions === undefined) { + // return; + // } + // const promptChoices: IPromptChoice[] = [ + // { + // label: nls.localize('apply', "Apply"), + // run: () => { + // let text = ''; + // newAdditions.forEach((mutator, variable) => { + // // TODO: Support other common shells + // // TODO: Escape the new values properly + // switch (mutator.type) { + // case EnvironmentVariableMutatorType.Append: + // text += `export ${variable}="$${variable}${mutator.value}"\n`; + // break; + // case EnvironmentVariableMutatorType.Prepend: + // text += `export ${variable}="${mutator.value}$${variable}"\n`; + // break; + // case EnvironmentVariableMutatorType.Replace: + // text += `export ${variable}="${mutator.value}"\n`; + // break; + // } + // }); + // this.write(text); + // } + // } as IPromptChoice + // ]; + // this._notificationService.prompt(Severity.Info, nls.localize('environmentchange', "An extension wants to change the terminal environment, do you want to send commands to set the variables in the terminal? Note if you have an application open in the terminal this may not work."), promptChoices); + } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalQuickOpen.ts b/src/vs/workbench/contrib/terminal/browser/terminalQuickOpen.ts deleted file mode 100644 index 17c61bc9c11..00000000000 --- a/src/vs/workbench/contrib/terminal/browser/terminalQuickOpen.ts +++ /dev/null @@ -1,137 +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 nls from 'vs/nls'; -import { Mode, IEntryRunContext, IAutoFocus, IQuickNavigateConfiguration, IModel } from 'vs/base/parts/quickopen/common/quickOpen'; -import { QuickOpenModel, QuickOpenEntry } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { QuickOpenHandler } from 'vs/workbench/browser/quickopen'; -import { ITerminalService, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { ContributableActionProvider } from 'vs/workbench/browser/actions'; -import { stripWildcards } from 'vs/base/common/strings'; -import { matchesFuzzy } from 'vs/base/common/filters'; -import { ICommandService } from 'vs/platform/commands/common/commands'; -import { CancellationToken } from 'vs/base/common/cancellation'; - -export class TerminalEntry extends QuickOpenEntry { - - constructor( - public instance: ITerminalInstance, - private label: string, - private terminalService: ITerminalService - ) { - super(); - } - - public getLabel(): string { - return this.label; - } - - public getAriaLabel(): string { - return nls.localize('termEntryAriaLabel', "{0}, terminal picker", this.getLabel()); - } - - public run(mode: Mode, context: IEntryRunContext): boolean { - if (mode === Mode.OPEN) { - setTimeout(() => { - this.terminalService.setActiveInstance(this.instance); - this.terminalService.showPanel(true); - }, 0); - return true; - } - - return super.run(mode, context); - } -} - -export class CreateTerminal extends QuickOpenEntry { - - constructor( - private label: string, - private commandService: ICommandService - ) { - super(); - } - - public getLabel(): string { - return this.label; - } - - public getAriaLabel(): string { - return nls.localize('termCreateEntryAriaLabel', "{0}, create new terminal", this.getLabel()); - } - - public run(mode: Mode, context: IEntryRunContext): boolean { - if (mode === Mode.OPEN) { - setTimeout(() => this.commandService.executeCommand('workbench.action.terminal.new'), 0); - return true; - } - - return super.run(mode, context); - } -} - -export class TerminalPickerHandler extends QuickOpenHandler { - - public static readonly ID = 'workbench.picker.terminals'; - - constructor( - @ITerminalService private readonly terminalService: ITerminalService, - @ICommandService private readonly commandService: ICommandService, - ) { - super(); - } - - public getResults(searchValue: string, token: CancellationToken): Promise { - searchValue = searchValue.trim(); - const normalizedSearchValueLowercase = stripWildcards(searchValue).toLowerCase(); - - const terminalEntries: QuickOpenEntry[] = this.getTerminals(); - terminalEntries.push(new CreateTerminal('$(plus) ' + nls.localize("workbench.action.terminal.newplus", "Create New Integrated Terminal"), this.commandService)); - - const entries = terminalEntries.filter(e => { - if (!searchValue) { - return true; - } - - const label = e.getLabel(); - if (!label) { - return false; - } - const highlights = matchesFuzzy(normalizedSearchValueLowercase, label, true); - if (!highlights) { - return false; - } - - e.setHighlights(highlights); - - return true; - }); - - return Promise.resolve(new QuickOpenModel(entries, new ContributableActionProvider())); - } - - private getTerminals(): TerminalEntry[] { - return this.terminalService.terminalTabs.reduce((terminals: TerminalEntry[], tab, tabIndex) => { - const terminalsInTab = tab.terminalInstances.map((terminal, terminalIndex) => { - const label = `${tabIndex + 1}.${terminalIndex + 1}: ${terminal.title}`; - return new TerminalEntry(terminal, label, this.terminalService); - }); - return [...terminals, ...terminalsInTab]; - }, []); - } - - public getAutoFocus(searchValue: string, context: { model: IModel, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus { - return { - autoFocusFirstEntry: !!searchValue || !!context.quickNavigateConfiguration - }; - } - - public getEmptyLabel(searchString: string): string { - if (searchString.length > 0) { - return nls.localize('noTerminalsMatching', "No terminals matching"); - } - return nls.localize('noTerminalsFound', "No terminals open"); - } -} diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 2dfb87a8d90..8a963713959 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -15,7 +15,7 @@ import { TerminalTab } from 'vs/workbench/contrib/terminal/browser/terminalTab'; import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance'; -import { ITerminalService, ITerminalInstance, ITerminalTab, TerminalShellType, WindowsShellType } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalService, ITerminalInstance, ITerminalTab, TerminalShellType, WindowsShellType, TerminalLinkHandlerCallback, LINK_INTERCEPT_THRESHOLD } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { IQuickInputService, IQuickPickItem, IPickOptions } from 'vs/platform/quickinput/common/quickInput'; @@ -30,6 +30,7 @@ import { IOpenFileRequest } from 'vs/platform/windows/common/windows'; import { find } from 'vs/base/common/arrays'; import { timeout } from 'vs/base/common/async'; import { IViewsService } from 'vs/workbench/common/views'; +import { IDisposable } from 'vs/base/common/lifecycle'; interface IExtHostReadyEntry { promise: Promise; @@ -50,6 +51,7 @@ export class TerminalService implements ITerminalService { private _findState: FindReplaceState; private _extHostsReady: { [authority: string]: IExtHostReadyEntry | undefined } = {}; private _activeTabIndex: number; + private _linkHandlers: { [key: string]: TerminalLinkHandlerCallback } = {}; public get activeTabIndex(): number { return this._activeTabIndex; } public get terminalInstances(): ITerminalInstance[] { return this._terminalInstances; } @@ -109,7 +111,7 @@ export class TerminalService implements ITerminalService { this._activeTabIndex = 0; this._isShuttingDown = false; this._findState = new FindReplaceState(); - lifecycleService.onBeforeShutdown(async event => event.veto(await this._onBeforeShutdown())); + lifecycleService.onBeforeShutdown(async event => event.veto(this._onBeforeShutdown())); lifecycleService.onShutdown(() => this._onShutdown()); if (this._terminalNativeService) { this._terminalNativeService.onOpenFileRequest(e => this._onOpenFileRequest(e)); @@ -176,19 +178,14 @@ export class TerminalService implements ITerminalService { this._extHostsReady[remoteAuthority] = { promise, resolve }; } - private async _onBeforeShutdown(): Promise { + private _onBeforeShutdown(): boolean | Promise { if (this.terminalInstances.length === 0) { // No terminal instances, don't veto return false; } if (this.configHelper.config.confirmOnExit) { - // veto if configured to show confirmation and the user choosed not to exit - const veto = await this._showTerminalCloseConfirmation(); - if (!veto) { - this._isShuttingDown = true; - } - return veto; + return this._onBeforeShutdownAsync(); } this._isShuttingDown = true; @@ -196,6 +193,15 @@ export class TerminalService implements ITerminalService { return false; } + private async _onBeforeShutdownAsync(): Promise { + // veto if configured to show confirmation and the user choosed not to exit + const veto = await this._showTerminalCloseConfirmation(); + if (!veto) { + this._isShuttingDown = true; + } + return veto; + } + private _onShutdown(): void { // Dispose of all instances this.terminalInstances.forEach(instance => instance.dispose(true)); @@ -411,6 +417,50 @@ export class TerminalService implements ITerminalService { instance.addDisposable(instance.onDimensionsChanged(() => this._onInstanceDimensionsChanged.fire(instance))); instance.addDisposable(instance.onMaximumDimensionsChanged(() => this._onInstanceMaximumDimensionsChanged.fire(instance))); instance.addDisposable(instance.onFocus(this._onActiveInstanceChanged.fire, this._onActiveInstanceChanged)); + instance.addDisposable(instance.onBeforeHandleLink(async e => { + // No link handlers have been registered + const keys = Object.keys(this._linkHandlers); + if (keys.length === 0) { + e.resolve(false); + return; + } + + // Fire each link interceptor and wait for either a true, all false or the cancel time + let resolved = false; + const promises: Promise[] = []; + const timeout = setTimeout(() => { + resolved = true; + e.resolve(false); + }, LINK_INTERCEPT_THRESHOLD); + for (let i = 0; i < keys.length; i++) { + const p = this._linkHandlers[keys[i]](e); + p.then(handled => { + if (!resolved && handled) { + resolved = true; + clearTimeout(timeout); + e.resolve(true); + } + }); + promises.push(p); + } + await Promise.all(promises); + if (!resolved) { + resolved = true; + clearTimeout(timeout); + e.resolve(false); + } + })); + } + + public addLinkHandler(key: string, callback: TerminalLinkHandlerCallback): IDisposable { + this._linkHandlers[key] = callback; + return { + dispose: () => { + if (this._linkHandlers[key] === callback) { + delete this._linkHandlers[key]; + } + } + }; } private _getTabForInstance(instance: ITerminalInstance): ITerminalTab | undefined { @@ -420,7 +470,7 @@ export class TerminalService implements ITerminalService { public async showPanel(focus?: boolean): Promise { const pane = this._viewsService.getActiveViewWithId(TERMINAL_VIEW_ID) as TerminalViewPane; if (!pane) { - await this._panelService.openPanel(TERMINAL_VIEW_ID, focus); + await this._viewsService.openView(TERMINAL_VIEW_ID, focus); } if (focus) { // Do the focus call asynchronously as going through the diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTab.ts b/src/vs/workbench/contrib/terminal/browser/terminalTab.ts index 68ff40e9135..ac30e0d1eca 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTab.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTab.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as aria from 'vs/base/browser/ui/aria/aria'; import * as nls from 'vs/nls'; -import { IShellLaunchConfig, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IShellLaunchConfig, ITerminalConfigHelper, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -12,6 +12,7 @@ import { SplitView, Orientation, IView, Sizing } from 'vs/base/browser/ui/splitv import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITerminalInstance, Direction, ITerminalTab, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; const SPLIT_PANE_MIN_SIZE = 120; @@ -215,6 +216,7 @@ export class TerminalTab extends Disposable implements ITerminalTab { private _splitPaneContainer: SplitPaneContainer | undefined; private _tabElement: HTMLElement | undefined; private _panelPosition: Position = Position.BOTTOM; + private _terminalLocation: ViewContainerLocation = ViewContainerLocation.Panel; private _activeInstanceIndex: number; @@ -232,6 +234,7 @@ export class TerminalTab extends Disposable implements ITerminalTab { shellLaunchConfigOrInstance: IShellLaunchConfig | ITerminalInstance, @ITerminalService private readonly _terminalService: ITerminalService, @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, + @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService, @IInstantiationService private readonly _instantiationService: IInstantiationService ) { super(); @@ -340,12 +343,18 @@ export class TerminalTab extends Disposable implements ITerminalTab { public attachToElement(element: HTMLElement): void { this._container = element; - this._tabElement = document.createElement('div'); - this._tabElement.classList.add('terminal-tab'); + + // If we already have a tab element, we can reparent it + if (!this._tabElement) { + this._tabElement = document.createElement('div'); + this._tabElement.classList.add('terminal-tab'); + } + this._container.appendChild(this._tabElement); if (!this._splitPaneContainer) { this._panelPosition = this._layoutService.getPanelPosition(); - const orientation = this._panelPosition === Position.BOTTOM ? Orientation.HORIZONTAL : Orientation.VERTICAL; + this._terminalLocation = this._viewDescriptorService.getViewLocation(TERMINAL_VIEW_ID)!; + const orientation = this._terminalLocation === ViewContainerLocation.Panel && this._panelPosition === Position.BOTTOM ? Orientation.HORIZONTAL : Orientation.VERTICAL; const newLocal = this._instantiationService.createInstance(SplitPaneContainer, this._tabElement, orientation); this._splitPaneContainer = newLocal; this.terminalInstances.forEach(instance => this._splitPaneContainer!.split(instance)); @@ -394,11 +403,14 @@ export class TerminalTab extends Disposable implements ITerminalTab { if (this._splitPaneContainer) { // Check if the panel position changed and rotate panes if so const newPanelPosition = this._layoutService.getPanelPosition(); - const panelPositionChanged = newPanelPosition !== this._panelPosition; - if (panelPositionChanged) { - const newOrientation = newPanelPosition === Position.BOTTOM ? Orientation.HORIZONTAL : Orientation.VERTICAL; + const newTerminalLocation = this._viewDescriptorService.getViewLocation(TERMINAL_VIEW_ID)!; + const terminalPositionChanged = newPanelPosition !== this._panelPosition || newTerminalLocation !== this._terminalLocation; + + if (terminalPositionChanged) { + const newOrientation = newTerminalLocation === ViewContainerLocation.Panel && newPanelPosition === Position.BOTTOM ? Orientation.HORIZONTAL : Orientation.VERTICAL; this._splitPaneContainer.setOrientation(newOrientation); this._panelPosition = newPanelPosition; + this._terminalLocation = newTerminalLocation; } this._splitPaneContainer.layout(width, height); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index 12bc4fab7ff..98bd6412523 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -12,7 +12,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IThemeService, ITheme, registerThemingParticipant, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme, registerThemingParticipant, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { TerminalFindWidget } from 'vs/workbench/contrib/terminal/browser/terminalFindWidget'; import { editorHoverBackground, editorHoverBorder, editorHoverForeground } from 'vs/platform/theme/common/colorRegistry'; import { KillTerminalAction, SwitchTerminalAction, SwitchTerminalActionViewItem, CopyTerminalSelectionAction, TerminalPasteAction, ClearTerminalAction, SelectAllTerminalAction, CreateNewTerminalAction, SplitTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; @@ -29,6 +29,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; const FIND_FOCUS_CLASS = 'find-focused'; @@ -79,7 +80,7 @@ export class TerminalViewPane extends ViewPane { this._terminalService.setContainers(container, this._terminalContainer); - this._register(this.themeService.onThemeChange(theme => this._updateTheme(theme))); + this._register(this.themeService.onDidColorThemeChange(theme => this._updateTheme(theme))); this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('terminal.integrated') || e.affectsConfiguration('editor.fontFamily')) { this._updateFont(); @@ -305,9 +306,9 @@ export class TerminalViewPane extends ViewPane { })); } - private _updateTheme(theme?: ITheme): void { + private _updateTheme(theme?: IColorTheme): void { if (!theme) { - theme = this.themeService.getTheme(); + theme = this.themeService.getColorTheme(); } if (this._findWidget) { @@ -325,9 +326,12 @@ export class TerminalViewPane extends ViewPane { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { - const backgroundColor = theme.getColor(TERMINAL_BACKGROUND_COLOR); - collector.addRule(`.monaco-workbench .pane-body.integrated-terminal .terminal-outer-container { background-color: ${backgroundColor ? backgroundColor.toString() : ''}; }`); +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { + const panelBackgroundColor = theme.getColor(TERMINAL_BACKGROUND_COLOR) || theme.getColor(PANEL_BACKGROUND); + collector.addRule(`.monaco-workbench .part.panel .pane-body.integrated-terminal .terminal-outer-container { background-color: ${panelBackgroundColor ? panelBackgroundColor.toString() : ''}; }`); + + const sidebarBackgroundColor = theme.getColor(TERMINAL_BACKGROUND_COLOR) || theme.getColor(SIDE_BAR_BACKGROUND); + collector.addRule(`.monaco-workbench .part.sidebar .pane-body.integrated-terminal .terminal-outer-container { background-color: ${sidebarBackgroundColor ? sidebarBackgroundColor.toString() : ''}; }`); const borderColor = theme.getColor(TERMINAL_BORDER_COLOR); if (borderColor) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalsQuickAccess.ts b/src/vs/workbench/contrib/terminal/browser/terminalsQuickAccess.ts new file mode 100644 index 00000000000..6c4af635014 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/browser/terminalsQuickAccess.ts @@ -0,0 +1,85 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { IPickerQuickAccessItem, PickerQuickAccessProvider, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess'; +import { matchesFuzzy } from 'vs/base/common/filters'; +import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal'; + +export class TerminalQuickAccessProvider extends PickerQuickAccessProvider { + + static PREFIX = 'term '; + + constructor( + @ITerminalService private readonly terminalService: ITerminalService, + @ICommandService private readonly commandService: ICommandService, + ) { + super(TerminalQuickAccessProvider.PREFIX, { canAcceptInBackground: true }); + } + + protected getPicks(filter: string): Array { + const terminalPicks: Array = []; + + const terminalTabs = this.terminalService.terminalTabs; + for (let tabIndex = 0; tabIndex < terminalTabs.length; tabIndex++) { + const terminalTab = terminalTabs[tabIndex]; + for (let terminalIndex = 0; terminalIndex < terminalTab.terminalInstances.length; terminalIndex++) { + const terminal = terminalTab.terminalInstances[terminalIndex]; + const label = `${tabIndex + 1}.${terminalIndex + 1}: ${terminal.title}`; + + const highlights = matchesFuzzy(filter, label, true); + if (highlights) { + terminalPicks.push({ + label, + highlights: { label: highlights }, + buttons: [ + { + iconClass: 'codicon-gear', + tooltip: localize('renameTerminal', "Rename Terminal") + }, + { + iconClass: 'codicon-trash', + tooltip: localize('killTerminal', "Kill Terminal Instance") + } + ], + trigger: buttonIndex => { + switch (buttonIndex) { + case 0: + this.commandService.executeCommand(TERMINAL_COMMAND_ID.RENAME, terminal); + return TriggerAction.NO_ACTION; + case 1: + terminal.dispose(true); + return TriggerAction.REMOVE_ITEM; + } + + return TriggerAction.NO_ACTION; + }, + accept: (keyMod, event) => { + this.terminalService.setActiveInstance(terminal); + this.terminalService.showPanel(!event.inBackground); + } + }); + } + } + } + + if (terminalPicks.length > 0) { + terminalPicks.push({ type: 'separator' }); + } + + const createTerminalLabel = localize("workbench.action.terminal.newplus", "Create New Integrated Terminal"); + terminalPicks.push({ + label: `$(plus) ${createTerminalLabel}`, + ariaLabel: createTerminalLabel, + accept: () => this.commandService.executeCommand('workbench.action.terminal.new') + }); + + return terminalPicks; + + } +} diff --git a/src/vs/workbench/contrib/terminal/browser/xterm-private.d.ts b/src/vs/workbench/contrib/terminal/browser/xterm-private.d.ts index 9c57c636a9e..c559ff7b8db 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm-private.d.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm-private.d.ts @@ -17,6 +17,10 @@ export interface XTermCore { }; _renderService: { + dimensions: { + actualCellWidth: number; + actualCellHeight: number; + }, _renderer: { _renderLayers: any[]; }; diff --git a/src/vs/workbench/contrib/terminal/common/environmentVariable.contribution.ts b/src/vs/workbench/contrib/terminal/common/environmentVariable.contribution.ts new file mode 100644 index 00000000000..4ebe6822816 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/common/environmentVariable.contribution.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IEnvironmentVariableService } from 'vs/workbench/contrib/terminal/common/environmentVariable'; +import { EnvironmentVariableService } from 'vs/workbench/contrib/terminal/common/environmentVariableService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +registerSingleton(IEnvironmentVariableService, EnvironmentVariableService, true); diff --git a/src/vs/workbench/contrib/terminal/common/environmentVariable.ts b/src/vs/workbench/contrib/terminal/common/environmentVariable.ts new file mode 100644 index 00000000000..78fce58dc3c --- /dev/null +++ b/src/vs/workbench/contrib/terminal/common/environmentVariable.ts @@ -0,0 +1,99 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { Event } from 'vs/base/common/event'; +import { IProcessEnvironment } from 'vs/base/common/platform'; + +export const IEnvironmentVariableService = createDecorator('environmentVariableService'); + +export enum EnvironmentVariableMutatorType { + Replace = 1, + Append = 2, + Prepend = 3 +} + +export interface IEnvironmentVariableMutator { + readonly value: string; + readonly type: EnvironmentVariableMutatorType; +} + +export interface IExtensionOwnedEnvironmentVariableMutator extends IEnvironmentVariableMutator { + readonly extensionIdentifier: string; +} + +export interface IEnvironmentVariableCollection { + readonly map: ReadonlyMap; +} + +export interface IEnvironmentVariableCollectionWithPersistence extends IEnvironmentVariableCollection { + readonly persistent: boolean; +} + +export interface IMergedEnvironmentVariableCollectionDiff { + added: ReadonlyMap; + changed: ReadonlyMap; + removed: ReadonlyMap; +} + +/** + * Represents an environment variable collection that results from merging several collections + * together. + */ +export interface IMergedEnvironmentVariableCollection { + readonly map: ReadonlyMap; + + /** + * Applies this collection to a process environment. + */ + applyToProcessEnvironment(env: IProcessEnvironment): void; + + /** + * Generates a diff of this connection against another. + */ + diff(other: IMergedEnvironmentVariableCollection): IMergedEnvironmentVariableCollectionDiff; +} + +/** + * Tracks and persists environment variable collections as defined by extensions. + */ +export interface IEnvironmentVariableService { + _serviceBrand: undefined; + + /** + * Gets a single collection constructed by merging all environment variable collections into + * one. + */ + readonly collections: ReadonlyMap; + + /** + * Gets a single collection constructed by merging all environment variable collections into + * one. + */ + readonly mergedCollection: IMergedEnvironmentVariableCollection; + + /** + * An event that is fired when an extension's environment variable collection changes, the event + * provides the new merged collection. + */ + onDidChangeCollections: Event; + + /** + * Sets an extension's environment variable collection. + */ + set(extensionIdentifier: string, collection: IEnvironmentVariableCollection): void; + + /** + * Deletes an extension's environment variable collection. + */ + delete(extensionIdentifier: string): void; +} + +/** + * First: Variable + * Second: Value + * Third: Type + */ +export type ISerializableEnvironmentVariableCollection = [string, IEnvironmentVariableMutator][]; diff --git a/src/vs/workbench/contrib/terminal/common/environmentVariableCollection.ts b/src/vs/workbench/contrib/terminal/common/environmentVariableCollection.ts new file mode 100644 index 00000000000..c8ae31d5bcb --- /dev/null +++ b/src/vs/workbench/contrib/terminal/common/environmentVariableCollection.ts @@ -0,0 +1,152 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IEnvironmentVariableCollection, EnvironmentVariableMutatorType, IMergedEnvironmentVariableCollection, IMergedEnvironmentVariableCollectionDiff, IExtensionOwnedEnvironmentVariableMutator } from 'vs/workbench/contrib/terminal/common/environmentVariable'; +import { IProcessEnvironment, isWindows } from 'vs/base/common/platform'; + +export class MergedEnvironmentVariableCollection implements IMergedEnvironmentVariableCollection { + readonly map: Map = new Map(); + + constructor(collections: Map) { + collections.forEach((collection, extensionIdentifier) => { + const it = collection.map.entries(); + let next = it.next(); + while (!next.done) { + const variable = next.value[0]; + let entry = this.map.get(variable); + if (!entry) { + entry = []; + this.map.set(variable, entry); + } + + // If the first item in the entry is replace ignore any other entries as they would + // just get replaced by this one. + if (entry.length > 0 && entry[0].type === EnvironmentVariableMutatorType.Replace) { + next = it.next(); + continue; + } + + // Mutators get applied in the reverse order than they are created + const mutator = next.value[1]; + entry.unshift({ + extensionIdentifier, + value: mutator.value, + type: mutator.type + }); + + next = it.next(); + } + }); + } + + applyToProcessEnvironment(env: IProcessEnvironment): void { + let lowerToActualVariableNames: { [lowerKey: string]: string | undefined } | undefined; + if (isWindows) { + lowerToActualVariableNames = {}; + Object.keys(env).forEach(e => lowerToActualVariableNames![e.toLowerCase()] = e); + } + this.map.forEach((mutators, variable) => { + const actualVariable = isWindows ? lowerToActualVariableNames![variable.toLowerCase()] || variable : variable; + mutators.forEach(mutator => { + switch (mutator.type) { + case EnvironmentVariableMutatorType.Append: + env[actualVariable] = (env[actualVariable] || '') + mutator.value; + break; + case EnvironmentVariableMutatorType.Prepend: + env[actualVariable] = mutator.value + (env[actualVariable] || ''); + break; + case EnvironmentVariableMutatorType.Replace: + env[actualVariable] = mutator.value; + break; + } + }); + }); + } + + diff(other: IMergedEnvironmentVariableCollection): IMergedEnvironmentVariableCollectionDiff { + const added: Map = new Map(); + const changed: Map = new Map(); + const removed: Map = new Map(); + + // Find added + other.map.forEach((otherMutators, variable) => { + const currentMutators = this.map.get(variable); + const result = getMissingMutatorsFromArray(otherMutators, currentMutators); + if (result) { + added.set(variable, result); + } + }); + + // Find removed + this.map.forEach((currentMutators, variable) => { + const otherMutators = other.map.get(variable); + const result = getMissingMutatorsFromArray(currentMutators, otherMutators); + if (result) { + removed.set(variable, result); + } + }); + + // Find changed + this.map.forEach((currentMutators, variable) => { + const otherMutators = other.map.get(variable); + const result = getChangedMutatorsFromArray(currentMutators, otherMutators); + if (result) { + changed.set(variable, result); + } + }); + + return { added, changed, removed }; + } +} + +function getMissingMutatorsFromArray( + current: IExtensionOwnedEnvironmentVariableMutator[], + other: IExtensionOwnedEnvironmentVariableMutator[] | undefined +): IExtensionOwnedEnvironmentVariableMutator[] | undefined { + // If it doesn't exist, all are removed + if (!other) { + return current; + } + + // Create a map to help + const otherMutatorExtensions = new Set(); + other.forEach(m => otherMutatorExtensions.add(m.extensionIdentifier)); + + // Find entries removed from other + const result: IExtensionOwnedEnvironmentVariableMutator[] = []; + current.forEach(mutator => { + if (!otherMutatorExtensions.has(mutator.extensionIdentifier)) { + result.push(mutator); + } + }); + + return result.length === 0 ? undefined : result; +} + +function getChangedMutatorsFromArray( + current: IExtensionOwnedEnvironmentVariableMutator[], + other: IExtensionOwnedEnvironmentVariableMutator[] | undefined +): IExtensionOwnedEnvironmentVariableMutator[] | undefined { + // If it doesn't exist, none are changed (they are removed) + if (!other) { + return undefined; + } + + // Create a map to help + const otherMutatorExtensions = new Map(); + other.forEach(m => otherMutatorExtensions.set(m.extensionIdentifier, m)); + + // Find entries that exist in both but are not equal + const result: IExtensionOwnedEnvironmentVariableMutator[] = []; + current.forEach(mutator => { + const otherMutator = otherMutatorExtensions.get(mutator.extensionIdentifier); + if (otherMutator && (mutator.type !== otherMutator.type || mutator.value !== otherMutator.value)) { + // Return the new result, not the old one + result.push(otherMutator); + } + }); + + return result.length === 0 ? undefined : result; +} diff --git a/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts b/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts new file mode 100644 index 00000000000..f4cda20ee28 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts @@ -0,0 +1,120 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IEnvironmentVariableService, IMergedEnvironmentVariableCollection, ISerializableEnvironmentVariableCollection, IEnvironmentVariableCollectionWithPersistence } from 'vs/workbench/contrib/terminal/common/environmentVariable'; +import { Event, Emitter } from 'vs/base/common/event'; +import { debounce, throttle } from 'vs/base/common/decorators'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { MergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableCollection'; +import { deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared'; + +const ENVIRONMENT_VARIABLE_COLLECTIONS_KEY = 'terminal.integrated.environmentVariableCollections'; + +interface ISerializableExtensionEnvironmentVariableCollection { + extensionIdentifier: string, + collection: ISerializableEnvironmentVariableCollection +} + +/** + * Tracks and persists environment variable collections as defined by extensions. + */ +export class EnvironmentVariableService implements IEnvironmentVariableService { + _serviceBrand: undefined; + + collections: Map = new Map(); + mergedCollection: IMergedEnvironmentVariableCollection; + + private readonly _onDidChangeCollections = new Emitter(); + get onDidChangeCollections(): Event { return this._onDidChangeCollections.event; } + + constructor( + @IExtensionService private readonly _extensionService: IExtensionService, + @IStorageService private readonly _storageService: IStorageService + ) { + const serializedPersistedCollections = this._storageService.get(ENVIRONMENT_VARIABLE_COLLECTIONS_KEY, StorageScope.WORKSPACE); + if (serializedPersistedCollections) { + const collectionsJson: ISerializableExtensionEnvironmentVariableCollection[] = JSON.parse(serializedPersistedCollections); + collectionsJson.forEach(c => this.collections.set(c.extensionIdentifier, { + persistent: true, + map: deserializeEnvironmentVariableCollection(c.collection) + })); + + // Asynchronously invalidate collections where extensions have been uninstalled, this is + // async to avoid making all functions on the service synchronous and because extensions + // being uninstalled is rare. + this._invalidateExtensionCollections(); + } + this.mergedCollection = this._resolveMergedCollection(); + + // Listen for uninstalled/disabled extensions + this._extensionService.onDidChangeExtensions(() => this._invalidateExtensionCollections()); + } + + set(extensionIdentifier: string, collection: IEnvironmentVariableCollectionWithPersistence): void { + this.collections.set(extensionIdentifier, collection); + this._updateCollections(); + } + + delete(extensionIdentifier: string): void { + this.collections.delete(extensionIdentifier); + this._updateCollections(); + } + + private _updateCollections(): void { + this._persistCollectionsEventually(); + this.mergedCollection = this._resolveMergedCollection(); + this._notifyCollectionUpdatesEventually(); + } + + @throttle(1000) + private _persistCollectionsEventually(): void { + this._persistCollections(); + } + + protected _persistCollections(): void { + const collectionsJson: ISerializableExtensionEnvironmentVariableCollection[] = []; + this.collections.forEach((collection, extensionIdentifier) => { + if (collection.persistent) { + collectionsJson.push({ + extensionIdentifier, + collection: serializeEnvironmentVariableCollection(this.collections.get(extensionIdentifier)!.map) + }); + } + }); + const stringifiedJson = JSON.stringify(collectionsJson); + this._storageService.store(ENVIRONMENT_VARIABLE_COLLECTIONS_KEY, stringifiedJson, StorageScope.WORKSPACE); + } + + @debounce(1000) + private _notifyCollectionUpdatesEventually(): void { + this._notifyCollectionUpdates(); + } + + protected _notifyCollectionUpdates(): void { + this._onDidChangeCollections.fire(this.mergedCollection); + } + + private _resolveMergedCollection(): IMergedEnvironmentVariableCollection { + return new MergedEnvironmentVariableCollection(this.collections); + } + + private async _invalidateExtensionCollections(): Promise { + await this._extensionService.whenInstalledExtensionsRegistered(); + + const registeredExtensions = await this._extensionService.getExtensions(); + let changes = false; + this.collections.forEach((_, extensionIdentifier) => { + const isExtensionRegistered = registeredExtensions.some(r => r.identifier.value === extensionIdentifier); + if (!isExtensionRegistered) { + this.collections.delete(extensionIdentifier); + changes = true; + } + }); + if (changes) { + this._updateCollections(); + } + } +} diff --git a/src/vs/workbench/contrib/terminal/common/environmentVariableShared.ts b/src/vs/workbench/contrib/terminal/common/environmentVariableShared.ts new file mode 100644 index 00000000000..ed39f2e8acd --- /dev/null +++ b/src/vs/workbench/contrib/terminal/common/environmentVariableShared.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 { IEnvironmentVariableMutator, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; + +// This file is shared between the renderer and extension host + +export function serializeEnvironmentVariableCollection(collection: ReadonlyMap): ISerializableEnvironmentVariableCollection { + return [...collection.entries()]; +} + +export function deserializeEnvironmentVariableCollection( + serializedCollection: ISerializableEnvironmentVariableCollection +): Map { + return new Map(serializedCollection); +} diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index f834e5e5c90..0d2d6cc069d 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -107,6 +107,7 @@ export interface ITerminalConfiguration { scrollback: number; commandsToSkipShell: string[]; allowChords: boolean; + allowMnemonics: boolean; cwd: string; confirmOnExit: boolean; enableBell: boolean; diff --git a/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts b/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts index 61b7ee7740e..30c7c55d9e4 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { registerColor, ColorIdentifier, ColorDefaults } from 'vs/platform/theme/common/colorRegistry'; -import { PANEL_BORDER, PANEL_BACKGROUND } from 'vs/workbench/common/theme'; +import { PANEL_BORDER } from 'vs/workbench/common/theme'; /** * The color identifiers for the terminal's ansi colors. The index in the array corresponds to the index @@ -14,11 +14,7 @@ import { PANEL_BORDER, PANEL_BACKGROUND } from 'vs/workbench/common/theme'; */ export const ansiColorIdentifiers: ColorIdentifier[] = []; -export const TERMINAL_BACKGROUND_COLOR = registerColor('terminal.background', { - dark: PANEL_BACKGROUND, - light: PANEL_BACKGROUND, - hc: PANEL_BACKGROUND -}, nls.localize('terminal.background', 'The background color of the terminal, this allows coloring the terminal differently to the panel.')); +export const TERMINAL_BACKGROUND_COLOR = registerColor('terminal.background', null, nls.localize('terminal.background', 'The background color of the terminal, this allows coloring the terminal differently to the panel.')); export const TERMINAL_FOREGROUND_COLOR = registerColor('terminal.foreground', { light: '#333333', dark: '#CCCCCC', diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalConfigHelper.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalConfigHelper.test.ts index 139d6ea06d7..fad3e60d818 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalConfigHelper.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalConfigHelper.test.ts @@ -8,6 +8,7 @@ import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/term import { EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal'; +import { StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; suite('Workbench - TerminalConfigHelper', () => { let fixture: HTMLElement; @@ -29,7 +30,7 @@ suite('Workbench - TerminalConfigHelper', () => { const configurationService = new TestConfigurationService(); configurationService.setUserConfiguration('editor', { fontFamily: 'foo' }); configurationService.setUserConfiguration('terminal', { integrated: { fontFamily: null } }); - const configHelper = new TerminalConfigHelper(LinuxDistro.Fedora, configurationService, null!, null!, null!, null!, null!, null!); + const configHelper = new TerminalConfigHelper(LinuxDistro.Fedora, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().fontFamily, '\'DejaVu Sans Mono\', monospace', 'Fedora should have its font overridden when terminal.integrated.fontFamily not set'); }); @@ -38,7 +39,7 @@ suite('Workbench - TerminalConfigHelper', () => { const configurationService = new TestConfigurationService(); configurationService.setUserConfiguration('editor', { fontFamily: 'foo' }); configurationService.setUserConfiguration('terminal', { integrated: { fontFamily: null } }); - const configHelper = new TerminalConfigHelper(LinuxDistro.Ubuntu, configurationService, null!, null!, null!, null!, null!, null!); + const configHelper = new TerminalConfigHelper(LinuxDistro.Ubuntu, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().fontFamily, '\'Ubuntu Mono\', monospace', 'Ubuntu should have its font overridden when terminal.integrated.fontFamily not set'); }); @@ -47,7 +48,7 @@ suite('Workbench - TerminalConfigHelper', () => { const configurationService = new TestConfigurationService(); configurationService.setUserConfiguration('editor', { fontFamily: 'foo' }); configurationService.setUserConfiguration('terminal', { integrated: { fontFamily: null } }); - const configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!); + const configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().fontFamily, 'foo', 'editor.fontFamily should be the fallback when terminal.integrated.fontFamily not set'); }); @@ -65,7 +66,7 @@ suite('Workbench - TerminalConfigHelper', () => { fontSize: 10 } }); - let configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!); + let configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().fontSize, 10, 'terminal.integrated.fontSize should be selected over editor.fontSize'); @@ -78,11 +79,11 @@ suite('Workbench - TerminalConfigHelper', () => { fontSize: 0 } }); - configHelper = new TerminalConfigHelper(LinuxDistro.Ubuntu, configurationService, null!, null!, null!, null!, null!, null!); + configHelper = new TerminalConfigHelper(LinuxDistro.Ubuntu, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().fontSize, 8, 'The minimum terminal font size (with adjustment) should be used when terminal.integrated.fontSize less than it'); - configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!); + configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().fontSize, 6, 'The minimum terminal font size should be used when terminal.integrated.fontSize less than it'); @@ -95,7 +96,7 @@ suite('Workbench - TerminalConfigHelper', () => { fontSize: 1500 } }); - configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!); + configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().fontSize, 25, 'The maximum terminal font size should be used when terminal.integrated.fontSize more than it'); @@ -108,11 +109,11 @@ suite('Workbench - TerminalConfigHelper', () => { fontSize: null } }); - configHelper = new TerminalConfigHelper(LinuxDistro.Ubuntu, configurationService, null!, null!, null!, null!, null!, null!); + configHelper = new TerminalConfigHelper(LinuxDistro.Ubuntu, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().fontSize, EDITOR_FONT_DEFAULTS.fontSize + 2, 'The default editor font size (with adjustment) should be used when terminal.integrated.fontSize is not set'); - configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!); + configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().fontSize, EDITOR_FONT_DEFAULTS.fontSize, 'The default editor font size should be used when terminal.integrated.fontSize is not set'); }); @@ -130,7 +131,7 @@ suite('Workbench - TerminalConfigHelper', () => { lineHeight: 2 } }); - let configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!); + let configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().lineHeight, 2, 'terminal.integrated.lineHeight should be selected over editor.lineHeight'); @@ -144,7 +145,7 @@ suite('Workbench - TerminalConfigHelper', () => { lineHeight: 0 } }); - configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!); + configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().lineHeight, 1, 'editor.lineHeight should be 1 when terminal.integrated.lineHeight not set'); }); @@ -157,7 +158,7 @@ suite('Workbench - TerminalConfigHelper', () => { } }); - let configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!); + let configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); configHelper.panelContainer = fixture; assert.equal(configHelper.configFontIsMonospace(), true, 'monospace is monospaced'); }); @@ -169,7 +170,7 @@ suite('Workbench - TerminalConfigHelper', () => { fontFamily: 'sans-serif' } }); - let configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!); + let configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); configHelper.panelContainer = fixture; assert.equal(configHelper.configFontIsMonospace(), false, 'sans-serif is not monospaced'); }); @@ -181,7 +182,7 @@ suite('Workbench - TerminalConfigHelper', () => { fontFamily: 'serif' } }); - let configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!); + let configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); configHelper.panelContainer = fixture; assert.equal(configHelper.configFontIsMonospace(), false, 'serif is not monospaced'); }); @@ -197,7 +198,7 @@ suite('Workbench - TerminalConfigHelper', () => { } }); - let configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!); + let configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); configHelper.panelContainer = fixture; assert.equal(configHelper.configFontIsMonospace(), true, 'monospace is monospaced'); }); @@ -213,7 +214,7 @@ suite('Workbench - TerminalConfigHelper', () => { } }); - let configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!); + let configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); configHelper.panelContainer = fixture; assert.equal(configHelper.configFontIsMonospace(), false, 'sans-serif is not monospaced'); }); @@ -229,7 +230,7 @@ suite('Workbench - TerminalConfigHelper', () => { } }); - let configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!); + let configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService()); configHelper.panelContainer = fixture; assert.equal(configHelper.configFontIsMonospace(), false, 'serif is not monospaced'); }); diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalLinkHandler.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalLinkHandler.test.ts index dcfcf378d02..f817d6bb400 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalLinkHandler.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalLinkHandler.test.ts @@ -5,11 +5,12 @@ import * as assert from 'assert'; import { OperatingSystem } from 'vs/base/common/platform'; -import { TerminalLinkHandler, LineColumnInfo } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler'; +import { TerminalLinkHandler, LineColumnInfo, XtermLinkMatcherHandler } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler'; import * as strings from 'vs/base/common/strings'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { Event } from 'vs/base/common/event'; import { ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; class TestTerminalLinkHandler extends TerminalLinkHandler { public get localLinkRegex(): RegExp { @@ -24,6 +25,13 @@ class TestTerminalLinkHandler extends TerminalLinkHandler { public preprocessPath(link: string): string | null { return this._preprocessPath(link); } + protected _isLinkActivationModifierDown(event: MouseEvent): boolean { + return true; + } + public wrapLinkHandler(handler: (link: string) => void): XtermLinkMatcherHandler { + TerminalLinkHandler._LINK_INTERCEPT_THRESHOLD = 0; + return this._wrapLinkHandler(handler); + } } class TestXterm { @@ -81,7 +89,7 @@ suite('Workbench - TerminalLinkHandler', () => { const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Windows, userHome: '' - } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!, null!); function testLink(link: string, linkUrl: string, lineNo?: string, columnNo?: string) { assert.equal(terminalLinkHandler.extractLinkUrl(link), linkUrl); assert.equal(terminalLinkHandler.extractLinkUrl(`:${link}:`), linkUrl); @@ -157,7 +165,7 @@ suite('Workbench - TerminalLinkHandler', () => { const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Linux, userHome: '' - } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!, null!); function testLink(link: string, linkUrl: string, lineNo?: string, columnNo?: string) { assert.equal(terminalLinkHandler.extractLinkUrl(link), linkUrl); assert.equal(terminalLinkHandler.extractLinkUrl(`:${link}:`), linkUrl); @@ -225,7 +233,7 @@ suite('Workbench - TerminalLinkHandler', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Windows, userHome: 'C:\\Users\\Me' - } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!, null!); linkHandler.processCwd = 'C:\\base'; assert.equal(linkHandler.preprocessPath('./src/file1'), 'C:\\base\\src\\file1'); @@ -238,7 +246,7 @@ suite('Workbench - TerminalLinkHandler', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Windows, userHome: 'C:\\Users\\M e' - } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!, null!); linkHandler.processCwd = 'C:\\base dir'; assert.equal(linkHandler.preprocessPath('./src/file1'), 'C:\\base dir\\src\\file1'); @@ -252,7 +260,7 @@ suite('Workbench - TerminalLinkHandler', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Linux, userHome: '/home/me' - } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!, null!); linkHandler.processCwd = '/base'; assert.equal(linkHandler.preprocessPath('./src/file1'), '/base/src/file1'); @@ -265,7 +273,7 @@ suite('Workbench - TerminalLinkHandler', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Linux, userHome: '/home/me' - } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!, null!); assert.equal(linkHandler.preprocessPath('./src/file1'), null); assert.equal(linkHandler.preprocessPath('src/file2'), null); @@ -279,7 +287,7 @@ suite('Workbench - TerminalLinkHandler', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Linux, userHome: '' - } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!, null!); function assertAreGoodMatches(matches: RegExpMatchArray | null) { if (matches) { @@ -302,4 +310,35 @@ suite('Workbench - TerminalLinkHandler', () => { assert.equal(linkHandler.gitDiffLinkPostImageRegex.test('+++ /dev/null'), false); assert.equal(linkHandler.gitDiffLinkPostImageRegex.test('+++ /dev/null '), false); }); + + suite('wrapLinkHandler', () => { + const nullMouseEvent: any = Object.freeze({ preventDefault: () => { } }); + + test('should allow intercepting of links with onBeforeHandleLink', async () => { + const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { + os: OperatingSystem.Linux, + userHome: '' + } as any, testConfigHelper, null!, null!, new TestConfigurationService(), new MockTerminalInstanceService(), null!, null!); + linkHandler.onBeforeHandleLink(e => { + if (e.link === 'https://www.microsoft.com') { + intercepted = true; + e.resolve(true); + } + e.resolve(false); + }); + const wrappedHandler = linkHandler.wrapLinkHandler(() => defaultHandled = true); + + let defaultHandled = false; + let intercepted = false; + await wrappedHandler(nullMouseEvent, 'https://www.visualstudio.com'); + assert.equal(intercepted, false); + assert.equal(defaultHandled, true); + + defaultHandled = false; + intercepted = false; + await wrappedHandler(nullMouseEvent, 'https://www.microsoft.com'); + assert.equal(intercepted, true); + assert.equal(defaultHandled, false); + }); + }); }); diff --git a/src/vs/workbench/contrib/terminal/test/common/environmentVariableCollection.test.ts b/src/vs/workbench/contrib/terminal/test/common/environmentVariableCollection.test.ts new file mode 100644 index 00000000000..254ad93b769 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/test/common/environmentVariableCollection.test.ts @@ -0,0 +1,355 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { deepStrictEqual, strictEqual } from 'assert'; +import { EnvironmentVariableMutatorType } from 'vs/workbench/contrib/terminal/common/environmentVariable'; +import { IProcessEnvironment, isWindows } from 'vs/base/common/platform'; +import { MergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableCollection'; +import { deserializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared'; + +suite('EnvironmentVariable - MergedEnvironmentVariableCollection', () => { + suite('ctor', () => { + test('Should keep entries that come after a Prepend or Append type mutators', () => { + const merged = new MergedEnvironmentVariableCollection(new Map([ + ['ext1', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a1', type: EnvironmentVariableMutatorType.Prepend }] + ]) + }], + ['ext2', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a2', type: EnvironmentVariableMutatorType.Append }] + ]) + }], + ['ext3', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a3', type: EnvironmentVariableMutatorType.Prepend }] + ]) + }], + ['ext4', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a4', type: EnvironmentVariableMutatorType.Append }] + ]) + }] + ])); + deepStrictEqual([...merged.map.entries()], [ + ['A', [ + { extensionIdentifier: 'ext4', type: EnvironmentVariableMutatorType.Append, value: 'a4' }, + { extensionIdentifier: 'ext3', type: EnvironmentVariableMutatorType.Prepend, value: 'a3' }, + { extensionIdentifier: 'ext2', type: EnvironmentVariableMutatorType.Append, value: 'a2' }, + { extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Prepend, value: 'a1' } + ]] + ]); + }); + + test('Should remove entries that come after a Replace type mutator', () => { + const merged = new MergedEnvironmentVariableCollection(new Map([ + ['ext1', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a1', type: EnvironmentVariableMutatorType.Prepend }] + ]) + }], + ['ext2', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a2', type: EnvironmentVariableMutatorType.Append }] + ]) + }], + ['ext3', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a3', type: EnvironmentVariableMutatorType.Replace }] + ]) + }], + ['ext4', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a4', type: EnvironmentVariableMutatorType.Append }] + ]) + }] + ])); + deepStrictEqual([...merged.map.entries()], [ + ['A', [ + { extensionIdentifier: 'ext3', type: EnvironmentVariableMutatorType.Replace, value: 'a3' }, + { extensionIdentifier: 'ext2', type: EnvironmentVariableMutatorType.Append, value: 'a2' }, + { extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Prepend, value: 'a1' } + ]] + ], 'The ext4 entry should be removed as it comes after a Replace'); + }); + }); + + suite('applyToProcessEnvironment', () => { + test('should apply the collection to an environment', () => { + const merged = new MergedEnvironmentVariableCollection(new Map([ + ['ext', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }], + ['B', { value: 'b', type: EnvironmentVariableMutatorType.Append }], + ['C', { value: 'c', type: EnvironmentVariableMutatorType.Prepend }] + ]) + }] + ])); + const env: IProcessEnvironment = { + A: 'foo', + B: 'bar', + C: 'baz' + }; + merged.applyToProcessEnvironment(env); + deepStrictEqual(env, { + A: 'a', + B: 'barb', + C: 'cbaz' + }); + }); + + test('should apply the collection to environment entries with no values', () => { + const merged = new MergedEnvironmentVariableCollection(new Map([ + ['ext', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }], + ['B', { value: 'b', type: EnvironmentVariableMutatorType.Append }], + ['C', { value: 'c', type: EnvironmentVariableMutatorType.Prepend }] + ]) + }] + ])); + const env: IProcessEnvironment = {}; + merged.applyToProcessEnvironment(env); + deepStrictEqual(env, { + A: 'a', + B: 'b', + C: 'c' + }); + }); + + test('should apply to variable case insensitively on Windows only', () => { + const merged = new MergedEnvironmentVariableCollection(new Map([ + ['ext', { + map: deserializeEnvironmentVariableCollection([ + ['a', { value: 'a', type: EnvironmentVariableMutatorType.Replace }], + ['b', { value: 'b', type: EnvironmentVariableMutatorType.Append }], + ['c', { value: 'c', type: EnvironmentVariableMutatorType.Prepend }] + ]) + }] + ])); + const env: IProcessEnvironment = { + A: 'A', + B: 'B', + C: 'C' + }; + merged.applyToProcessEnvironment(env); + if (isWindows) { + deepStrictEqual(env, { + A: 'a', + B: 'Bb', + C: 'cC' + }); + } else { + deepStrictEqual(env, { + a: 'a', + A: 'A', + b: 'b', + B: 'B', + c: 'c', + C: 'C' + }); + } + }); + }); + + suite('diff', () => { + test('should generate added diffs from when the first entry is added', () => { + const merged1 = new MergedEnvironmentVariableCollection(new Map([])); + const merged2 = new MergedEnvironmentVariableCollection(new Map([ + ['ext1', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }] + ]) + }] + ])); + const diff = merged1.diff(merged2); + strictEqual(diff.changed.size, 0); + strictEqual(diff.removed.size, 0); + const entries = [...diff.added.entries()]; + deepStrictEqual(entries, [ + ['A', [{ extensionIdentifier: 'ext1', value: 'a', type: EnvironmentVariableMutatorType.Replace }]] + ]); + }); + + test('should generate added diffs from the same extension', () => { + const merged1 = new MergedEnvironmentVariableCollection(new Map([ + ['ext1', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }] + ]) + }] + ])); + const merged2 = new MergedEnvironmentVariableCollection(new Map([ + ['ext1', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }], + ['B', { value: 'b', type: EnvironmentVariableMutatorType.Append }] + ]) + }] + ])); + const diff = merged1.diff(merged2); + strictEqual(diff.changed.size, 0); + strictEqual(diff.removed.size, 0); + const entries = [...diff.added.entries()]; + deepStrictEqual(entries, [ + ['B', [{ extensionIdentifier: 'ext1', value: 'b', type: EnvironmentVariableMutatorType.Append }]] + ]); + }); + + test('should generate added diffs from a different extension', () => { + const merged1 = new MergedEnvironmentVariableCollection(new Map([ + ['ext1', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a1', type: EnvironmentVariableMutatorType.Prepend }] + ]) + }] + ])); + + const merged2 = new MergedEnvironmentVariableCollection(new Map([ + ['ext2', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a2', type: EnvironmentVariableMutatorType.Append }] + ]) + }], + ['ext1', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a1', type: EnvironmentVariableMutatorType.Prepend }] + ]) + }] + ])); + const diff = merged1.diff(merged2); + strictEqual(diff.changed.size, 0); + strictEqual(diff.removed.size, 0); + deepStrictEqual([...diff.added.entries()], [ + ['A', [{ extensionIdentifier: 'ext2', value: 'a2', type: EnvironmentVariableMutatorType.Append }]] + ]); + + const merged3 = new MergedEnvironmentVariableCollection(new Map([ + ['ext1', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a1', type: EnvironmentVariableMutatorType.Prepend }] + ]) + }], + // This entry should get removed + ['ext2', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a2', type: EnvironmentVariableMutatorType.Append }] + ]) + }] + ])); + const diff2 = merged1.diff(merged3); + strictEqual(diff2.changed.size, 0); + strictEqual(diff2.removed.size, 0); + deepStrictEqual([...diff.added.entries()], [...diff2.added.entries()], 'Swapping the order of the entries in the other collection should yield the same result'); + }); + + test('should remove entries in the diff that come after a Replce', () => { + const merged1 = new MergedEnvironmentVariableCollection(new Map([ + ['ext1', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a1', type: EnvironmentVariableMutatorType.Replace }] + ]) + }] + ])); + const merged4 = new MergedEnvironmentVariableCollection(new Map([ + ['ext1', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a1', type: EnvironmentVariableMutatorType.Replace }] + ]) + }], + // This entry should get removed as it comes after a replace + ['ext2', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a2', type: EnvironmentVariableMutatorType.Append }] + ]) + }] + ])); + const diff = merged1.diff(merged4); + strictEqual(diff.changed.size, 0); + strictEqual(diff.removed.size, 0); + deepStrictEqual([...diff.added.entries()], [], 'Replace should ignore any entries after it'); + }); + + test('should generate removed diffs', () => { + const merged1 = new MergedEnvironmentVariableCollection(new Map([ + ['ext1', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }], + ['B', { value: 'b', type: EnvironmentVariableMutatorType.Replace }] + ]) + }] + ])); + const merged2 = new MergedEnvironmentVariableCollection(new Map([ + ['ext1', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }] + ]) + }] + ])); + const diff = merged1.diff(merged2); + strictEqual(diff.changed.size, 0); + strictEqual(diff.added.size, 0); + deepStrictEqual([...diff.removed.entries()], [ + ['B', [{ extensionIdentifier: 'ext1', value: 'b', type: EnvironmentVariableMutatorType.Replace }]] + ]); + }); + + test('should generate changed diffs', () => { + const merged1 = new MergedEnvironmentVariableCollection(new Map([ + ['ext1', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a1', type: EnvironmentVariableMutatorType.Replace }], + ['B', { value: 'b', type: EnvironmentVariableMutatorType.Replace }] + ]) + }] + ])); + const merged2 = new MergedEnvironmentVariableCollection(new Map([ + ['ext1', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a2', type: EnvironmentVariableMutatorType.Replace }], + ['B', { value: 'b', type: EnvironmentVariableMutatorType.Append }] + ]) + }] + ])); + const diff = merged1.diff(merged2); + strictEqual(diff.added.size, 0); + strictEqual(diff.removed.size, 0); + deepStrictEqual([...diff.changed.entries()], [ + ['A', [{ extensionIdentifier: 'ext1', value: 'a2', type: EnvironmentVariableMutatorType.Replace }]], + ['B', [{ extensionIdentifier: 'ext1', value: 'b', type: EnvironmentVariableMutatorType.Append }]] + ]); + }); + + test('should generate diffs with added, changed and removed', () => { + const merged1 = new MergedEnvironmentVariableCollection(new Map([ + ['ext1', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a1', type: EnvironmentVariableMutatorType.Replace }], + ['B', { value: 'b', type: EnvironmentVariableMutatorType.Prepend }] + ]) + }] + ])); + const merged2 = new MergedEnvironmentVariableCollection(new Map([ + ['ext1', { + map: deserializeEnvironmentVariableCollection([ + ['A', { value: 'a2', type: EnvironmentVariableMutatorType.Replace }], + ['C', { value: 'c', type: EnvironmentVariableMutatorType.Append }] + ]) + }] + ])); + const diff = merged1.diff(merged2); + deepStrictEqual([...diff.added.entries()], [ + ['C', [{ extensionIdentifier: 'ext1', value: 'c', type: EnvironmentVariableMutatorType.Append }]], + ]); + deepStrictEqual([...diff.removed.entries()], [ + ['B', [{ extensionIdentifier: 'ext1', value: 'b', type: EnvironmentVariableMutatorType.Prepend }]] + ]); + deepStrictEqual([...diff.changed.entries()], [ + ['A', [{ extensionIdentifier: 'ext1', value: 'a2', type: EnvironmentVariableMutatorType.Replace }]] + ]); + }); + }); +}); diff --git a/src/vs/workbench/contrib/terminal/test/common/environmentVariableService.test.ts b/src/vs/workbench/contrib/terminal/test/common/environmentVariableService.test.ts new file mode 100644 index 00000000000..ed3269a6b83 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/test/common/environmentVariableService.test.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 { deepStrictEqual } from 'assert'; +import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; +import { EnvironmentVariableService } from 'vs/workbench/contrib/terminal/common/environmentVariableService'; +import { EnvironmentVariableMutatorType, IEnvironmentVariableMutator } from 'vs/workbench/contrib/terminal/common/environmentVariable'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { Emitter } from 'vs/base/common/event'; +import { IProcessEnvironment } from 'vs/base/common/platform'; + +class TestEnvironmentVariableService extends EnvironmentVariableService { + persistCollections(): void { this._persistCollections(); } + notifyCollectionUpdates(): void { this._notifyCollectionUpdates(); } +} + +suite('EnvironmentVariable - EnvironmentVariableService', () => { + let instantiationService: TestInstantiationService; + let environmentVariableService: TestEnvironmentVariableService; + let storageService: TestStorageService; + let changeExtensionsEvent: Emitter; + + setup(() => { + changeExtensionsEvent = new Emitter(); + + instantiationService = new TestInstantiationService(); + instantiationService.stub(IExtensionService, TestExtensionService); + storageService = new TestStorageService(); + instantiationService.stub(IStorageService, storageService); + instantiationService.stub(IExtensionService, TestExtensionService); + instantiationService.stub(IExtensionService, 'onDidChangeExtensions', changeExtensionsEvent.event); + instantiationService.stub(IExtensionService, 'getExtensions', [ + { identifier: { value: 'ext1' } }, + { identifier: { value: 'ext2' } }, + { identifier: { value: 'ext3' } } + ]); + + environmentVariableService = instantiationService.createInstance(TestEnvironmentVariableService); + }); + + test('should persist collections to the storage service and be able to restore from them', () => { + const collection = new Map(); + collection.set('A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }); + collection.set('B', { value: 'b', type: EnvironmentVariableMutatorType.Append }); + collection.set('C', { value: 'c', type: EnvironmentVariableMutatorType.Prepend }); + environmentVariableService.set('ext1', { map: collection, persistent: true }); + deepStrictEqual([...environmentVariableService.mergedCollection.map.entries()], [ + ['A', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Replace, value: 'a' }]], + ['B', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Append, value: 'b' }]], + ['C', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Prepend, value: 'c' }]] + ]); + + // Persist with old service, create a new service with the same storage service to verify restore + environmentVariableService.persistCollections(); + const service2: TestEnvironmentVariableService = instantiationService.createInstance(TestEnvironmentVariableService); + deepStrictEqual([...service2.mergedCollection.map.entries()], [ + ['A', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Replace, value: 'a' }]], + ['B', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Append, value: 'b' }]], + ['C', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Prepend, value: 'c' }]] + ]); + }); + + suite('mergedCollection', () => { + test('should overwrite any other variable with the first extension that replaces', () => { + const collection1 = new Map(); + const collection2 = new Map(); + const collection3 = new Map(); + collection1.set('A', { value: 'a1', type: EnvironmentVariableMutatorType.Append }); + collection1.set('B', { value: 'b1', type: EnvironmentVariableMutatorType.Replace }); + collection2.set('A', { value: 'a2', type: EnvironmentVariableMutatorType.Replace }); + collection2.set('B', { value: 'b2', type: EnvironmentVariableMutatorType.Append }); + collection3.set('A', { value: 'a3', type: EnvironmentVariableMutatorType.Prepend }); + collection3.set('B', { value: 'b3', type: EnvironmentVariableMutatorType.Replace }); + environmentVariableService.set('ext1', { map: collection1, persistent: true }); + environmentVariableService.set('ext2', { map: collection2, persistent: true }); + environmentVariableService.set('ext3', { map: collection3, persistent: true }); + deepStrictEqual([...environmentVariableService.mergedCollection.map.entries()], [ + ['A', [ + { extensionIdentifier: 'ext2', type: EnvironmentVariableMutatorType.Replace, value: 'a2' }, + { extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Append, value: 'a1' } + ]], + ['B', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Replace, value: 'b1' }]] + ]); + }); + + test('should correctly apply the environment values from multiple extension contributions in the correct order', () => { + const collection1 = new Map(); + const collection2 = new Map(); + const collection3 = new Map(); + collection1.set('A', { value: ':a1', type: EnvironmentVariableMutatorType.Append }); + collection2.set('A', { value: 'a2:', type: EnvironmentVariableMutatorType.Prepend }); + collection3.set('A', { value: 'a3', type: EnvironmentVariableMutatorType.Replace }); + environmentVariableService.set('ext1', { map: collection1, persistent: true }); + environmentVariableService.set('ext2', { map: collection2, persistent: true }); + environmentVariableService.set('ext3', { map: collection3, persistent: true }); + + // The entries should be ordered in the order they are applied + deepStrictEqual([...environmentVariableService.mergedCollection.map.entries()], [ + ['A', [ + { extensionIdentifier: 'ext3', type: EnvironmentVariableMutatorType.Replace, value: 'a3' }, + { extensionIdentifier: 'ext2', type: EnvironmentVariableMutatorType.Prepend, value: 'a2:' }, + { extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Append, value: ':a1' } + ]] + ]); + + // Verify the entries get applied to the environment as expected + const env: IProcessEnvironment = { A: 'foo' }; + environmentVariableService.mergedCollection.applyToProcessEnvironment(env); + deepStrictEqual(env, { A: 'a2:a3:a1' }); + }); + }); +}); diff --git a/src/vs/workbench/contrib/terminal/test/common/environmentVariableShared.test.ts b/src/vs/workbench/contrib/terminal/test/common/environmentVariableShared.test.ts new file mode 100644 index 00000000000..1f4e518c58d --- /dev/null +++ b/src/vs/workbench/contrib/terminal/test/common/environmentVariableShared.test.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { deepStrictEqual } from 'assert'; +import { deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared'; +import { EnvironmentVariableMutatorType, IEnvironmentVariableMutator } from 'vs/workbench/contrib/terminal/common/environmentVariable'; + +suite('EnvironmentVariable - deserializeEnvironmentVariableCollection', () => { + test('should construct correctly with 3 arguments', () => { + const c = deserializeEnvironmentVariableCollection([ + ['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }], + ['B', { value: 'b', type: EnvironmentVariableMutatorType.Append }], + ['C', { value: 'c', type: EnvironmentVariableMutatorType.Prepend }] + ]); + const keys = [...c.keys()]; + deepStrictEqual(keys, ['A', 'B', 'C']); + deepStrictEqual(c.get('A'), { value: 'a', type: EnvironmentVariableMutatorType.Replace }); + deepStrictEqual(c.get('B'), { value: 'b', type: EnvironmentVariableMutatorType.Append }); + deepStrictEqual(c.get('C'), { value: 'c', type: EnvironmentVariableMutatorType.Prepend }); + }); +}); + +suite('EnvironmentVariable - serializeEnvironmentVariableCollection', () => { + test('should correctly serialize the object', () => { + const collection = new Map(); + deepStrictEqual(serializeEnvironmentVariableCollection(collection), []); + collection.set('A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }); + collection.set('B', { value: 'b', type: EnvironmentVariableMutatorType.Append }); + collection.set('C', { value: 'c', type: EnvironmentVariableMutatorType.Prepend }); + deepStrictEqual(serializeEnvironmentVariableCollection(collection), [ + ['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }], + ['B', { value: 'b', type: EnvironmentVariableMutatorType.Append }], + ['C', { value: 'c', type: EnvironmentVariableMutatorType.Prepend }] + ]); + }); +}); diff --git a/src/vs/workbench/contrib/terminal/test/common/terminalColorRegistry.test.ts b/src/vs/workbench/contrib/terminal/test/common/terminalColorRegistry.test.ts index 65fcaa47fe2..48c52ffb3a8 100644 --- a/src/vs/workbench/contrib/terminal/test/common/terminalColorRegistry.test.ts +++ b/src/vs/workbench/contrib/terminal/test/common/terminalColorRegistry.test.ts @@ -7,13 +7,13 @@ import * as assert from 'assert'; import { Extensions as ThemeingExtensions, IColorRegistry, ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { ansiColorIdentifiers, registerColors } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; -import { ITheme, ThemeType } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, ThemeType } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; registerColors(); let themingRegistry = Registry.as(ThemeingExtensions.ColorContribution); -function getMockTheme(type: ThemeType): ITheme { +function getMockTheme(type: ThemeType): IColorTheme { let theme = { selector: '', label: '', @@ -21,8 +21,8 @@ function getMockTheme(type: ThemeType): ITheme { getColor: (colorId: ColorIdentifier): Color | undefined => themingRegistry.resolveDefaultColor(colorId, theme), defines: () => true, getTokenStyleMetadata: () => undefined, - tokenColorMap: [] - + tokenColorMap: [], + semanticHighlighting: false }; return theme; } diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index 821c5a920d7..587408c3248 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -10,18 +10,19 @@ import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; -import { IWorkbenchThemeService, COLOR_THEME_SETTING, ICON_THEME_SETTING, IColorTheme, IFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService, IWorkbenchTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { VIEWLET_ID, IExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/common/extensions'; import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IColorRegistry, Extensions as ColorRegistryExtensions } from 'vs/platform/theme/common/colorRegistry'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Color } from 'vs/base/common/color'; -import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { LIGHT, DARK, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; import { colorThemeSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IQuickInputService, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { DEFAULT_PRODUCT_ICON_THEME_ID } from 'vs/workbench/services/themes/browser/productIconThemeData'; export class SelectColorThemeAction extends Action { @@ -34,8 +35,7 @@ export class SelectColorThemeAction extends Action { @IQuickInputService private readonly quickInputService: IQuickInputService, @IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, - @IViewletService private readonly viewletService: IViewletService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IViewletService private readonly viewletService: IViewletService ) { super(id, label); } @@ -60,13 +60,8 @@ export class SelectColorThemeAction extends Action { selectThemeTimeout = window.setTimeout(() => { selectThemeTimeout = undefined; const themeId = theme && theme.id !== undefined ? theme.id : currentTheme.id; - let target: ConfigurationTarget | undefined = undefined; - if (applyTheme) { - const confValue = this.configurationService.inspect(COLOR_THEME_SETTING); - target = typeof confValue.workspaceValue !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; - } - this.themeService.setColorTheme(themeId, target).then(undefined, + this.themeService.setColorTheme(themeId, applyTheme ? 'auto' : undefined).then(undefined, err => { onUnexpectedError(err); this.themeService.setColorTheme(currentTheme.id, undefined); @@ -108,7 +103,83 @@ export class SelectColorThemeAction extends Action { } } -class SelectIconThemeAction extends Action { +abstract class AbstractIconThemeAction extends Action { + constructor( + id: string, + label: string, + private readonly quickInputService: IQuickInputService, + private readonly extensionGalleryService: IExtensionGalleryService, + private readonly viewletService: IViewletService + + ) { + super(id, label); + } + + protected abstract get builtInEntry(): QuickPickInput; + protected abstract get installMessage(): string | undefined; + protected abstract get placeholderMessage(): string; + protected abstract get marketplaceTag(): string; + + protected abstract setTheme(id: string, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise; + + protected pick(themes: IWorkbenchTheme[], currentTheme: IWorkbenchTheme) { + let picks: QuickPickInput[] = [this.builtInEntry]; + picks = picks.concat( + toEntries(themes), + configurationEntries(this.extensionGalleryService, this.installMessage) + ); + + let selectThemeTimeout: number | undefined; + + const selectTheme = (theme: ThemeItem, applyTheme: boolean) => { + if (selectThemeTimeout) { + clearTimeout(selectThemeTimeout); + } + selectThemeTimeout = window.setTimeout(() => { + selectThemeTimeout = undefined; + const themeId = theme && theme.id !== undefined ? theme.id : currentTheme.id; + this.setTheme(themeId, applyTheme ? 'auto' : undefined).then(undefined, + err => { + onUnexpectedError(err); + this.setTheme(currentTheme.id, undefined); + } + ); + }, applyTheme ? 0 : 200); + }; + + return new Promise((s, _) => { + let isCompleted = false; + + const autoFocusIndex = firstIndex(picks, p => isItem(p) && p.id === currentTheme.id); + const quickpick = this.quickInputService.createQuickPick(); + quickpick.items = picks; + quickpick.placeholder = this.placeholderMessage; + quickpick.activeItems = [picks[autoFocusIndex] as ThemeItem]; + quickpick.canSelectMany = false; + quickpick.onDidAccept(_ => { + const theme = quickpick.activeItems[0]; + if (!theme || typeof theme.id === 'undefined') { // 'pick in marketplace' entry + openExtensionViewlet(this.viewletService, `${this.marketplaceTag} ${quickpick.value}`); + } else { + selectTheme(theme, true); + } + isCompleted = true; + quickpick.hide(); + s(); + }); + quickpick.onDidChangeActive(themes => selectTheme(themes[0], false)); + quickpick.onDidHide(() => { + if (!isCompleted) { + selectTheme(currentTheme, true); + s(); + } + }); + quickpick.show(); + }); + } +} + +class SelectFileIconThemeAction extends AbstractIconThemeAction { static readonly ID = 'workbench.action.selectIconTheme'; static readonly LABEL = localize('selectIconTheme.label', "File Icon Theme"); @@ -116,84 +187,61 @@ class SelectIconThemeAction extends Action { constructor( id: string, label: string, - @IQuickInputService private readonly quickInputService: IQuickInputService, + @IQuickInputService quickInputService: IQuickInputService, @IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService, - @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, - @IViewletService private readonly viewletService: IViewletService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, + @IViewletService viewletService: IViewletService ) { - super(id, label); + super(id, label, quickInputService, extensionGalleryService, viewletService); } - run(): Promise { - return this.themeService.getFileIconThemes().then(themes => { - const currentTheme = this.themeService.getFileIconTheme(); + protected builtInEntry: QuickPickInput = { id: '', label: localize('noIconThemeLabel', 'None'), description: localize('noIconThemeDesc', 'Disable file icons') }; + protected installMessage = localize('installIconThemes', "Install Additional File Icon Themes..."); + protected placeholderMessage = localize('themes.selectIconTheme', "Select File Icon Theme"); + protected marketplaceTag = 'tag:icon-theme'; + protected setTheme(id: string, settingsTarget: ConfigurationTarget | undefined | 'auto') { + return this.themeService.setFileIconTheme(id, settingsTarget); + } - let picks: QuickPickInput[] = [{ id: '', label: localize('noIconThemeLabel', 'None'), description: localize('noIconThemeDesc', 'Disable file icons') }]; - picks = picks.concat( - toEntries(themes), - configurationEntries(this.extensionGalleryService, localize('installIconThemes', "Install Additional File Icon Themes...")) - ); - - let selectThemeTimeout: number | undefined; - - const selectTheme = (theme: ThemeItem, applyTheme: boolean) => { - if (selectThemeTimeout) { - clearTimeout(selectThemeTimeout); - } - selectThemeTimeout = window.setTimeout(() => { - selectThemeTimeout = undefined; - const themeId = theme && theme.id !== undefined ? theme.id : currentTheme.id; - let target: ConfigurationTarget | undefined = undefined; - if (applyTheme) { - const confValue = this.configurationService.inspect(ICON_THEME_SETTING); - target = typeof confValue.workspaceValue !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; - } - this.themeService.setFileIconTheme(themeId, target).then(undefined, - err => { - onUnexpectedError(err); - this.themeService.setFileIconTheme(currentTheme.id, undefined); - } - ); - }, applyTheme ? 0 : 200); - }; - - return new Promise((s, _) => { - let isCompleted = false; - - const autoFocusIndex = firstIndex(picks, p => isItem(p) && p.id === currentTheme.id); - const quickpick = this.quickInputService.createQuickPick(); - quickpick.items = picks; - quickpick.placeholder = localize('themes.selectIconTheme', "Select File Icon Theme"); - quickpick.activeItems = [picks[autoFocusIndex] as ThemeItem]; - quickpick.canSelectMany = false; - quickpick.onDidAccept(_ => { - const theme = quickpick.activeItems[0]; - if (!theme || typeof theme.id === 'undefined') { // 'pick in marketplace' entry - openExtensionViewlet(this.viewletService, `tag:icon-theme ${quickpick.value}`); - } else { - selectTheme(theme, true); - } - isCompleted = true; - quickpick.hide(); - s(); - }); - quickpick.onDidChangeActive(themes => selectTheme(themes[0], false)); - quickpick.onDidHide(() => { - if (!isCompleted) { - selectTheme(currentTheme, true); - s(); - } - }); - quickpick.show(); - }); - }); + async run(): Promise { + this.pick(await this.themeService.getFileIconThemes(), this.themeService.getFileIconTheme()); } } -function configurationEntries(extensionGalleryService: IExtensionGalleryService, label: string): QuickPickInput[] { - if (extensionGalleryService.isEnabled()) { + +class SelectProductIconThemeAction extends AbstractIconThemeAction { + + static readonly ID = 'workbench.action.selectProductIconTheme'; + static readonly LABEL = localize('selectProductIconTheme.label', "Product Icon Theme"); + + constructor( + id: string, + label: string, + @IQuickInputService quickInputService: IQuickInputService, + @IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService, + @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, + @IViewletService viewletService: IViewletService + + ) { + super(id, label, quickInputService, extensionGalleryService, viewletService); + } + + protected builtInEntry: QuickPickInput = { id: DEFAULT_PRODUCT_ICON_THEME_ID, label: localize('defaultProductIconThemeLabel', 'Default') }; + protected installMessage = undefined; //localize('installProductIconThemes', "Install Additional Product Icon Themes..."); + protected placeholderMessage = localize('themes.selectProductIconTheme', "Select Product Icon Theme"); + protected marketplaceTag = 'tag:product-icon-theme'; + protected setTheme(id: string, settingsTarget: ConfigurationTarget | undefined | 'auto') { + return this.themeService.setProductIconTheme(id, settingsTarget); + } + + async run(): Promise { + this.pick(await this.themeService.getProductIconThemes(), this.themeService.getProductIconTheme()); + } +} + +function configurationEntries(extensionGalleryService: IExtensionGalleryService, label: string | undefined): QuickPickInput[] { + if (extensionGalleryService.isEnabled() && label !== undefined) { return [ { type: 'separator' @@ -227,8 +275,8 @@ function isItem(i: QuickPickInput): i is ThemeItem { return (i)['type'] !== 'separator'; } -function toEntries(themes: Array, label?: string): QuickPickInput[] { - const toEntry = (theme: IColorTheme | IFileIconTheme): ThemeItem => ({ id: theme.id, label: theme.label, description: theme.description }); +function toEntries(themes: Array, label?: string): QuickPickInput[] { + const toEntry = (theme: IWorkbenchTheme): ThemeItem => ({ id: theme.id, label: theme.label, description: theme.description }); const sorter = (t1: ThemeItem, t2: ThemeItem) => t1.label.localeCompare(t2.label); let entries: QuickPickInput[] = themes.map(toEntry).sort(sorter); if (entries.length > 0 && label) { @@ -288,8 +336,11 @@ const category = localize('preferences', "Preferences"); const colorThemeDescriptor = SyncActionDescriptor.create(SelectColorThemeAction, SelectColorThemeAction.ID, SelectColorThemeAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_T) }); Registry.as(Extensions.WorkbenchActions).registerWorkbenchAction(colorThemeDescriptor, 'Preferences: Color Theme', category); -const iconThemeDescriptor = SyncActionDescriptor.create(SelectIconThemeAction, SelectIconThemeAction.ID, SelectIconThemeAction.LABEL); -Registry.as(Extensions.WorkbenchActions).registerWorkbenchAction(iconThemeDescriptor, 'Preferences: File Icon Theme', category); +const fileIconThemeDescriptor = SyncActionDescriptor.create(SelectFileIconThemeAction, SelectFileIconThemeAction.ID, SelectFileIconThemeAction.LABEL); +Registry.as(Extensions.WorkbenchActions).registerWorkbenchAction(fileIconThemeDescriptor, 'Preferences: File Icon Theme', category); + +const productIconThemeDescriptor = SyncActionDescriptor.create(SelectProductIconThemeAction, SelectProductIconThemeAction.ID, SelectProductIconThemeAction.LABEL); +Registry.as(Extensions.WorkbenchActions).registerWorkbenchAction(productIconThemeDescriptor, 'Preferences: Product Icon Theme', category); const developerCategory = localize('developer', "Developer"); @@ -309,7 +360,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { group: '4_themes', command: { - id: SelectIconThemeAction.ID, + id: SelectFileIconThemeAction.ID, title: localize({ key: 'miSelectIconTheme', comment: ['&& denotes a mnemonic'] }, "File &&Icon Theme") }, order: 2 @@ -327,7 +378,7 @@ MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '4_themes', command: { - id: SelectIconThemeAction.ID, + id: SelectFileIconThemeAction.ID, title: localize('themes.selectIconTheme.label', "File Icon Theme") }, order: 2 diff --git a/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts b/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts index d9e0970f7ca..56d449979f5 100644 --- a/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts +++ b/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts @@ -7,7 +7,7 @@ import { URI } from 'vs/base/common/uri'; import { IModeService } from 'vs/editor/common/services/modeService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService, IWorkbenchColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { toResource } from 'vs/workbench/common/editor'; import { ITextMateService } from 'vs/workbench/services/textMate/common/textMateService'; @@ -37,11 +37,11 @@ interface IThemesResult { } class ThemeDocument { - private readonly _theme: IColorTheme; + private readonly _theme: IWorkbenchColorTheme; private readonly _cache: { [scopes: string]: ThemeRule; }; private readonly _defaultColor: string; - constructor(theme: IColorTheme) { + constructor(theme: IWorkbenchColorTheme) { this._theme = theme; this._cache = Object.create(null); this._defaultColor = '#000000'; diff --git a/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css b/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css index 3b036147cfc..8deb44d640b 100644 --- a/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css +++ b/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css @@ -7,9 +7,25 @@ position: relative; } +.monaco-workbench .timeline-view.pane-header .description { + margin-left: 10px; + opacity: 0.6; + text-transform: none; + font-weight: normal; +} + +.monaco-workbench .timeline-view.pane-header:not(.expanded) .description { + display: none; +} + +.monaco-workbench .timeline-view.pane-header .description span.codicon { + font-size: 9px; + margin-left: 2px; +} + .monaco-workbench .timeline-tree-view .message.timeline-subtle { - padding: 10px 22px 0 22px; opacity: 0.5; + padding: 10px 22px 0 22px; position: absolute; pointer-events: none; z-index: 1; @@ -24,10 +40,34 @@ .timeline-tree-view .monaco-list .monaco-list-row .custom-view-tree-node-item .timeline-timestamp-container { margin-left: 2px; margin-right: 4px; - text-overflow: ellipsis; + opacity: 0.5; overflow: hidden; + text-overflow: ellipsis; } -.timeline-tree-view .monaco-list .monaco-list-row .custom-view-tree-node-item .timeline-timestamp-container .timeline-timestamp { - opacity: 0.5; +.timeline-tree-view .monaco-list .monaco-list-row .custom-view-tree-node-item .timeline-timestamp-container.timeline-timestamp--duplicate::before { + content: ' '; + position: absolute; + right: 10px; + border-right: 1px solid currentColor; + display: block; + height: 100%; + width: 1px; + opacity: 0.25; +} + +.timeline-tree-view .monaco-list .monaco-list-row:hover .custom-view-tree-node-item .timeline-timestamp-container.timeline-timestamp--duplicate::before, +.timeline-tree-view .monaco-list .monaco-list-row.selected .custom-view-tree-node-item .timeline-timestamp-container.timeline-timestamp--duplicate::before, +.timeline-tree-view .monaco-list .monaco-list-row.focused .custom-view-tree-node-item .timeline-timestamp-container.timeline-timestamp--duplicate::before { + display: none; +} + +.timeline-tree-view .monaco-list .monaco-list-row .custom-view-tree-node-item .timeline-timestamp-container.timeline-timestamp--duplicate .timeline-timestamp { + visibility: hidden; +} + +.timeline-tree-view .monaco-list .monaco-list-row:hover .custom-view-tree-node-item .timeline-timestamp-container.timeline-timestamp--duplicate .timeline-timestamp, +.timeline-tree-view .monaco-list .monaco-list-row.selected .custom-view-tree-node-item .timeline-timestamp-container.timeline-timestamp--duplicate .timeline-timestamp, +.timeline-tree-view .monaco-list .monaco-list-row.focused .custom-view-tree-node-item .timeline-timestamp-container.timeline-timestamp--duplicate .timeline-timestamp { + visibility: visible !important; } diff --git a/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts b/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts index b3958cb47c7..a50e68f8c77 100644 --- a/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts +++ b/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts @@ -9,20 +9,20 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; import { IViewsRegistry, IViewDescriptor, Extensions as ViewExtensions } from 'vs/workbench/common/views'; import { VIEW_CONTAINER } from 'vs/workbench/contrib/files/browser/explorerViewlet'; -import { ITimelineService } from 'vs/workbench/contrib/timeline/common/timeline'; +import { ITimelineService, TimelinePaneId } from 'vs/workbench/contrib/timeline/common/timeline'; import { TimelineService } from 'vs/workbench/contrib/timeline/common/timelineService'; import { TimelinePane } from './timelinePane'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands'; -import product from 'vs/platform/product/common/product'; +import { ExplorerFolderContext } from 'vs/workbench/contrib/files/common/files'; +import { ResourceContextKey } from 'vs/workbench/common/resources'; export class TimelinePaneDescriptor implements IViewDescriptor { - readonly id = TimelinePane.ID; + readonly id = TimelinePaneId; readonly name = TimelinePane.TITLE; readonly ctorDescriptor = new SyncDescriptor(TimelinePane); - readonly when = ContextKeyExpr.equals('config.timeline.showView', true); readonly order = 2; readonly weight = 30; readonly collapsed = true; @@ -41,11 +41,6 @@ configurationRegistry.registerConfiguration({ title: localize('timelineConfigurationTitle', "Timeline"), type: 'object', properties: { - 'timeline.showView': { - type: 'boolean', - description: localize('timeline.showView', "Experimental: When enabled, shows a Timeline view in the Explorer sidebar."), - default: product.quality !== 'stable' - }, 'timeline.excludeSources': { type: 'array', description: localize('timeline.excludeSources', "Experimental: An array of Timeline sources that should be excluded from the Timeline view"), @@ -56,90 +51,30 @@ configurationRegistry.registerConfiguration({ Registry.as(ViewExtensions.ViewsRegistry).registerViews([new TimelinePaneDescriptor()], VIEW_CONTAINER); -namespace TimelineViewRefreshAction { +namespace OpenTimelineAction { - export const ID = 'timeline.refresh'; - export const LABEL = localize('timeline.refreshView', "Refresh"); + export const ID = 'files.openTimeline'; + export const LABEL = localize('files.openTimeline', "Open Timeline"); export function handler(): ICommandHandler { return (accessor, arg) => { const service = accessor.get(ITimelineService); - return service.reset(); + return service.setUri(arg); }; } } -CommandsRegistry.registerCommand(TimelineViewRefreshAction.ID, TimelineViewRefreshAction.handler()); +CommandsRegistry.registerCommand(OpenTimelineAction.ID, OpenTimelineAction.handler()); -// namespace TimelineViewRefreshHardAction { - -// export const ID = 'timeline.refreshHard'; -// export const LABEL = localize('timeline.refreshHard', "Refresh (Hard)"); - -// export function handler(fetch?: 'all' | 'more'): ICommandHandler { -// return (accessor, arg) => { -// const service = accessor.get(ITimelineService); -// return service.refresh(fetch); -// }; -// } -// } - -// CommandsRegistry.registerCommand(TimelineViewRefreshAction.ID, TimelineViewRefreshAction.handler()); - -// namespace TimelineViewLoadMoreAction { - -// export const ID = 'timeline.loadMore'; -// export const LABEL = localize('timeline.loadMoreInView', "Load More"); - -// export function handler(): ICommandHandler { -// return (accessor, arg) => { -// const service = accessor.get(ITimelineService); -// return service.refresh('more'); -// }; -// } -// } - -// CommandsRegistry.registerCommand(TimelineViewLoadMoreAction.ID, TimelineViewLoadMoreAction.handler()); - -// namespace TimelineViewLoadAllAction { - -// export const ID = 'timeline.loadAll'; -// export const LABEL = localize('timeline.loadAllInView', "Load All"); - -// export function handler(): ICommandHandler { -// return (accessor, arg) => { -// const service = accessor.get(ITimelineService); -// return service.refresh('all'); -// }; -// } -// } - -// CommandsRegistry.registerCommand(TimelineViewLoadAllAction.ID, TimelineViewLoadAllAction.handler()); - -MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({ - group: 'navigation', +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, ({ + group: '4_timeline', order: 1, command: { - id: TimelineViewRefreshAction.ID, - title: TimelineViewRefreshAction.LABEL, - icon: { id: 'codicon/refresh' } - } + id: OpenTimelineAction.ID, + title: OpenTimelineAction.LABEL, + icon: { id: 'codicon/history' } + }, + when: ContextKeyExpr.and(ExplorerFolderContext.toNegated(), ResourceContextKey.HasResource) })); -// MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({ -// group: 'navigation', -// order: 2, -// command: { -// id: TimelineViewLoadMoreAction.ID, -// title: TimelineViewLoadMoreAction.LABEL, -// icon: { id: 'codicon/unfold' } -// }, -// alt: { -// id: TimelineViewLoadAllAction.ID, -// title: TimelineViewLoadAllAction.LABEL, -// icon: { id: 'codicon/unfold' } - -// } -// })); - registerSingleton(ITimelineService, TimelineService, true); diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 29dceaaca3d..0bfa4fe3f84 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -8,23 +8,22 @@ import { localize } from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; -import { Iterator } from 'vs/base/common/iterator'; import { DisposableStore, IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { IListVirtualDelegate, IIdentityProvider, IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list'; -import { ITreeNode, ITreeRenderer, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; +import { ITreeNode, ITreeRenderer, ITreeContextMenuEvent, ITreeElement } from 'vs/base/browser/ui/tree/tree'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { TreeResourceNavigator, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; +import { ResourceNavigator, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ITimelineService, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvidersChangeEvent, TimelineRequest, Timeline } from 'vs/workbench/contrib/timeline/common/timeline'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { SideBySideEditor, toResource } from 'vs/workbench/common/editor'; -import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IThemeService, LIGHT, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { basename } from 'vs/base/common/path'; @@ -34,12 +33,14 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IActionViewItemProvider, ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAction, ActionRunner } from 'vs/base/common/actions'; import { ContextAwareMenuEntryActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { MenuItemAction, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { MenuItemAction, IMenuService, MenuId, registerAction2, Action2, MenuRegistry } from 'vs/platform/actions/common/actions'; import { fromNow } from 'vs/base/common/date'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { escapeRegExpCharacters } from 'vs/base/common/strings'; +import { Iterable } from 'vs/base/common/iterator'; +import { Schemas } from 'vs/base/common/network'; -const InitialPageSize = 20; -const SubsequentPageSize = 40; +const PageSize = 20; interface CommandItem { handle: 'vscode-command:loadMore'; @@ -55,6 +56,8 @@ interface CommandItem { icon: undefined; iconDark: undefined; source: undefined; + relativeTime: undefined; + hideRelativeTime: undefined; } type TreeElement = TimelineItem | CommandItem; @@ -73,36 +76,144 @@ function isTimelineItem(item: TreeElement | undefined): item is TimelineItem { return !item?.handle.startsWith('vscode-command:') ?? false; } +function updateRelativeTime(item: TimelineItem, lastRelativeTime: string | undefined): string | undefined { + item.relativeTime = isTimelineItem(item) ? fromNow(item.timestamp) : undefined; + if (lastRelativeTime === undefined || item.relativeTime !== lastRelativeTime) { + lastRelativeTime = item.relativeTime; + item.hideRelativeTime = false; + } else { + item.hideRelativeTime = true; + } + + return lastRelativeTime; +} interface TimelineActionContext { uri: URI | undefined; item: TreeElement; } -interface TimelineCursors { - startCursors?: { before: any; after?: any }; - endCursors?: { before: any; after?: any }; - more: boolean; +class TimelineAggregate { + readonly items: TimelineItem[]; + readonly source: string; + + lastRenderedIndex: number; + + constructor(timeline: Timeline) { + this.source = timeline.source; + this.items = timeline.items; + this._cursor = timeline.paging?.cursor; + this.lastRenderedIndex = -1; + } + + private _cursor?: string; + get cursor(): string | undefined { + return this._cursor; + } + + get more(): boolean { + return this._cursor !== undefined; + } + + get newest(): TimelineItem | undefined { + return this.items[0]; + } + + get oldest(): TimelineItem | undefined { + return this.items[this.items.length - 1]; + } + + add(timeline: Timeline) { + let updated = false; + + if (timeline.items.length !== 0 && this.items.length !== 0) { + updated = true; + + const ids = new Set(); + const timestamps = new Set(); + + for (const item of timeline.items) { + if (item.id === undefined) { + timestamps.add(item.timestamp); + } + else { + ids.add(item.id); + } + } + + // Remove any duplicate items + let i = this.items.length; + let item; + while (i--) { + item = this.items[i]; + if ((item.id !== undefined && ids.has(item.id)) || timestamps.has(item.timestamp)) { + this.items.splice(i, 1); + } + } + + if ((timeline.items[timeline.items.length - 1]?.timestamp ?? 0) >= (this.newest?.timestamp ?? 0)) { + this.items.splice(0, 0, ...timeline.items); + } else { + this.items.push(...timeline.items); + } + } else if (timeline.items.length !== 0) { + updated = true; + + this.items.push(...timeline.items); + } + + this._cursor = timeline.paging?.cursor; + + if (updated) { + this.items.sort( + (a, b) => + (b.timestamp - a.timestamp) || + (a.source === undefined + ? b.source === undefined ? 0 : 1 + : b.source === undefined ? -1 : b.source.localeCompare(a.source, undefined, { numeric: true, sensitivity: 'base' })) + ); + } + + return updated; + } + + private _stale = false; + get stale() { + return this._stale; + } + + private _requiresReset = false; + get requiresReset(): boolean { + return this._requiresReset; + } + + invalidate(requiresReset: boolean) { + this._stale = true; + this._requiresReset = requiresReset; + } } +export const TimelineFollowActiveEditorContext = new RawContextKey('timelineFollowActiveEditor', true); + export class TimelinePane extends ViewPane { - static readonly ID = 'timeline'; - static readonly TITLE = localize('timeline', 'Timeline'); + static readonly TITLE = localize('timeline', "Timeline"); - private _container!: HTMLElement; - private _messageElement!: HTMLDivElement; - private _treeElement!: HTMLDivElement; - private _tree!: WorkbenchObjectTree; - private _treeRenderer: TimelineTreeRenderer | undefined; - private _menus: TimelineMenus; - private _visibilityDisposables: DisposableStore | undefined; + private $container!: HTMLElement; + private $message!: HTMLDivElement; + private $titleDescription!: HTMLSpanElement; + private $tree!: HTMLDivElement; + private tree!: WorkbenchObjectTree; + private treeRenderer: TimelineTreeRenderer | undefined; + private commands: TimelinePaneCommands; + private visibilityDisposables: DisposableStore | undefined; - private _excludedSources: Set; - private _cursorsByProvider: Map = new Map(); - private _items: { element: TreeElement }[] = []; - private _loadingMessageTimer: any | undefined; - private _pendingRequests = new Map(); - private _uri: URI | undefined; + private followActiveEditorContext: IContextKey; + + private excludedSources: Set; + private pendingRequests = new Map(); + private timelinesBySource = new Map(); + + private uri: URI | undefined; constructor( options: IViewPaneOptions, @@ -122,13 +233,52 @@ export class TimelinePane extends ViewPane { ) { super({ ...options, titleMenuId: MenuId.TimelineTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); - this._menus = this._register(this.instantiationService.createInstance(TimelineMenus, this.id)); + this.commands = this._register(this.instantiationService.createInstance(TimelinePaneCommands, this)); - const scopedContextKeyService = this._register(this.contextKeyService.createScoped()); - scopedContextKeyService.createKey('view', TimelinePane.ID); + this.followActiveEditorContext = TimelineFollowActiveEditorContext.bindTo(this.contextKeyService); - this._excludedSources = new Set(configurationService.getValue('timeline.excludeSources')); + this.excludedSources = new Set(configurationService.getValue('timeline.excludeSources')); configurationService.onDidChangeConfiguration(this.onConfigurationChanged, this); + + this._register(timelineService.onDidChangeProviders(this.onProvidersChanged, this)); + this._register(timelineService.onDidChangeTimeline(this.onTimelineChanged, this)); + this._register(timelineService.onDidChangeUri(uri => this.setUri(uri), this)); + } + + private _followActiveEditor: boolean = true; + get followActiveEditor(): boolean { + return this._followActiveEditor; + } + set followActiveEditor(value: boolean) { + if (this._followActiveEditor === value) { + return; + } + + this._followActiveEditor = value; + this.followActiveEditorContext.set(value); + + if (value) { + this.onActiveEditorChanged(); + } + } + + reset() { + this.loadTimeline(true); + } + + setUri(uri: URI) { + this.setUriCore(uri, true); + } + + private setUriCore(uri: URI | undefined, disableFollowing: boolean) { + if (disableFollowing) { + this.followActiveEditor = false; + } + + this.uri = uri; + this.titleDescription = uri ? basename(uri.fsPath) : ''; + this.treeRenderer?.setUri(uri); + this.loadTimeline(true); } private onConfigurationChanged(e: IConfigurationChangeEvent) { @@ -136,11 +286,22 @@ export class TimelinePane extends ViewPane { return; } - this._excludedSources = new Set(this.configurationService.getValue('timeline.excludeSources')); - this.loadTimeline(true); + this.excludedSources = new Set(this.configurationService.getValue('timeline.excludeSources')); + + const missing = this.timelineService.getSources() + .filter(({ id }) => !this.excludedSources.has(id) && !this.timelinesBySource.has(id)); + if (missing.length !== 0) { + this.loadTimeline(true, missing.map(({ id }) => id)); + } else { + this.refresh(); + } } private onActiveEditorChanged() { + if (!this.followActiveEditor) { + return; + } + let uri; const editor = this.editorService.activeEditor; @@ -148,22 +309,41 @@ export class TimelinePane extends ViewPane { uri = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); } - if ((uri?.toString(true) === this._uri?.toString(true) && uri !== undefined) || + if ((uri?.toString(true) === this.uri?.toString(true) && uri !== undefined) || // Fallback to match on fsPath if we are dealing with files or git schemes - (uri?.fsPath === this._uri?.fsPath && (uri?.scheme === 'file' || uri?.scheme === 'git') && (this._uri?.scheme === 'file' || this._uri?.scheme === 'git'))) { + (uri?.fsPath === this.uri?.fsPath && (uri?.scheme === 'file' || uri?.scheme === 'git') && (this.uri?.scheme === 'file' || this.uri?.scheme === 'git'))) { + + // If the uri hasn't changed, make sure we have valid caches + for (const source of this.timelineService.getSources()) { + if (this.excludedSources.has(source.id)) { + continue; + } + + const timeline = this.timelinesBySource.get(source.id); + if (timeline !== undefined && !timeline.stale) { + continue; + } + + if (timeline !== undefined) { + this.updateTimeline(timeline, timeline.requiresReset); + } else { + this.loadTimelineForSource(source.id, uri, true); + } + } + return; } - this._uri = uri; - this._treeRenderer?.setUri(uri); - this.loadTimeline(true); + this.setUriCore(uri, false); } private onProvidersChanged(e: TimelineProvidersChangeEvent) { if (e.removed) { for (const source of e.removed) { - this.replaceItems(source); + this.timelinesBySource.delete(source); } + + this.refresh(); } if (e.added) { @@ -172,13 +352,28 @@ export class TimelinePane extends ViewPane { } private onTimelineChanged(e: TimelineChangeEvent) { - if (e?.uri === undefined || e.uri.toString(true) !== this._uri?.toString(true)) { - this.loadTimeline(e.reset ?? false, e?.id === undefined ? undefined : [e.id], { before: !e.reset }); + if (e?.uri === undefined || e.uri.toString(true) !== this.uri?.toString(true)) { + const timeline = this.timelinesBySource.get(e.id); + if (timeline === undefined) { + return; + } + + if (this.isBodyVisible()) { + this.updateTimeline(timeline, e.reset ?? false); + } else { + timeline.invalidate(e.reset ?? false); + } } } - private onReset() { - this.loadTimeline(true); + private _titleDescription: string | undefined; + get titleDescription(): string | undefined { + return this._titleDescription; + } + + set titleDescription(description: string | undefined) { + this._titleDescription = description; + this.$titleDescription.textContent = description ?? ''; } private _message: string | undefined; @@ -192,7 +387,7 @@ export class TimelinePane extends ViewPane { } private updateMessage(): void { - if (this._message) { + if (this._message !== undefined) { this.showMessage(this._message); } else { this.hideMessage(); @@ -200,330 +395,357 @@ export class TimelinePane extends ViewPane { } private showMessage(message: string): void { - DOM.removeClass(this._messageElement, 'hide'); + DOM.removeClass(this.$message, 'hide'); this.resetMessageElement(); - this._messageElement.textContent = message; + this.$message.textContent = message; } private hideMessage(): void { this.resetMessageElement(); - DOM.addClass(this._messageElement, 'hide'); + DOM.addClass(this.$message, 'hide'); } private resetMessageElement(): void { - DOM.clearNode(this._messageElement); + DOM.clearNode(this.$message); } - private async loadTimeline(reset: boolean, sources?: string[], options: TimelineOptions = {}) { - const defaultPageSize = reset ? InitialPageSize : SubsequentPageSize; + private _isEmpty = true; + private _maxItemCount = 0; + private _visibleItemCount = 0; + private get hasVisibleItems() { + return this._visibleItemCount > 0; + } + + private clear(cancelPending: boolean) { + this._visibleItemCount = 0; + this._maxItemCount = PageSize; + this.timelinesBySource.clear(); + + if (cancelPending) { + for (const { tokenSource } of this.pendingRequests.values()) { + tokenSource.dispose(true); + } + + this.pendingRequests.clear(); + + if (!this.isBodyVisible()) { + this.tree.setChildren(null, undefined); + this._isEmpty = true; + } + } + } + + private async loadTimeline(reset: boolean, sources?: string[]) { // If we have no source, we are reseting all sources, so cancel everything in flight and reset caches if (sources === undefined) { if (reset) { - this._items.length = 0; - this._cursorsByProvider.clear(); - - if (this._loadingMessageTimer) { - clearTimeout(this._loadingMessageTimer); - this._loadingMessageTimer = undefined; - } - - for (const { tokenSource } of this._pendingRequests.values()) { - tokenSource.dispose(true); - } - - this._pendingRequests.clear(); + this.clear(true); } - // TODO[ECA]: Are these the right the list of schemes to exclude? Is there a better way? - if (this._uri && (this._uri.scheme === 'vscode-settings' || this._uri.scheme === 'webview-panel' || this._uri.scheme === 'walkThrough')) { - this.message = localize('timeline.editorCannotProvideTimeline', 'The active editor cannot provide timeline information.'); - this._tree.setChildren(null, undefined); + // TODO@eamodio: Are these the right the list of schemes to exclude? Is there a better way? + if (this.uri?.scheme === Schemas.vscodeSettings || this.uri?.scheme === Schemas.webviewPanel || this.uri?.scheme === Schemas.walkThrough) { + this.uri = undefined; + + this.clear(false); + this.refresh(); return; } - if (reset && this._uri !== undefined) { - this._loadingMessageTimer = setTimeout((uri: URI) => { - if (uri !== this._uri) { - return; - } - - this._tree.setChildren(null, undefined); - this.message = localize('timeline.loading', 'Loading timeline for {0}...', basename(uri.fsPath)); - }, 500, this._uri); + if (this._isEmpty && this.uri !== undefined) { + this.setLoadingUriMessage(); } } - if (this._uri === undefined) { - return; - } - - const filteredSources = (sources ?? this.timelineService.getSources()).filter(s => !this._excludedSources.has(s)); - if (filteredSources.length === 0) { - if (reset) { - this.refresh(); - } + if (this.uri === undefined) { + this.clear(false); + this.refresh(); return; } - let lastIndex = this._items.length - 1; - let lastItem = this._items[lastIndex]?.element; - if (isLoadMoreCommandItem(lastItem)) { - lastItem.themeIcon = { id: 'sync~spin' }; - // this._items.splice(lastIndex, 1); - lastIndex--; + if (!this.isBodyVisible()) { + return; + } - if (!reset && !options.before) { - lastItem = this._items[lastIndex]?.element; - const selection = [lastItem]; - this._tree.setSelection(selection); - this._tree.setFocus(selection); + let hasPendingRequests = false; + + for (const source of sources ?? this.timelineService.getSources().map(s => s.id)) { + const requested = this.loadTimelineForSource(source, this.uri, reset); + if (requested) { + hasPendingRequests = true; } } - for (const source of filteredSources) { - let request = this._pendingRequests.get(source); - - const cursors = this._cursorsByProvider.get(source); - if (!reset) { - // TODO: Handle pending request - - if (cursors?.more !== true) { - continue; - } - - const reusingToken = request?.tokenSource !== undefined; - request = this.timelineService.getTimeline( - source, this._uri, - { - cursor: options.before ? cursors?.startCursors?.before : (cursors?.endCursors ?? cursors?.startCursors)?.after, - ...options, - limit: options.limit === 0 ? undefined : options.limit ?? defaultPageSize - }, - request?.tokenSource ?? new CancellationTokenSource(), { cacheResults: true } - )!; - - if (request === undefined) { - continue; - } - - this._pendingRequests.set(source, request); - if (!reusingToken) { - request.tokenSource.token.onCancellationRequested(() => this._pendingRequests.delete(source)); - } - } else { - request?.tokenSource.dispose(true); - - request = this.timelineService.getTimeline( - source, this._uri, - { - ...options, - limit: options.limit === 0 ? undefined : (reset ? cursors?.endCursors?.after : undefined) ?? options.limit ?? defaultPageSize - }, - new CancellationTokenSource(), { cacheResults: true } - )!; - - if (request === undefined) { - continue; - } - - this._pendingRequests.set(source, request); - request.tokenSource.token.onCancellationRequested(() => this._pendingRequests.delete(source)); - } - - this.handleRequest(request); + if (!hasPendingRequests) { + this.refresh(); + } else if (this._isEmpty) { + this.setLoadingUriMessage(); } } + private loadTimelineForSource(source: string, uri: URI, reset: boolean, options?: TimelineOptions) { + if (this.excludedSources.has(source)) { + return false; + } + + const timeline = this.timelinesBySource.get(source); + + // If we are paging, and there are no more items or we have enough cached items to cover the next page, + // don't bother querying for more + if ( + !reset && + timeline !== undefined && + (!timeline?.more || timeline.items.length > timeline.lastRenderedIndex + PageSize) + ) { + return false; + } + + if (options === undefined) { + options = { cursor: reset ? undefined : timeline?.cursor, limit: PageSize }; + } + + let request = this.pendingRequests.get(source); + if (request !== undefined) { + options.cursor = request.options.cursor; + + // TODO@eamodio deal with concurrent requests better + if (typeof options.limit === 'number') { + if (typeof request.options.limit === 'number') { + options.limit += request.options.limit; + } else { + options.limit = request.options.limit; + } + } + } + request?.tokenSource.dispose(true); + + request = this.timelineService.getTimeline( + source, uri, options, new CancellationTokenSource(), { cacheResults: true, resetCache: reset } + ); + + if (request === undefined) { + return false; + } + + this.pendingRequests.set(source, request); + request.tokenSource.token.onCancellationRequested(() => this.pendingRequests.delete(source)); + + this.handleRequest(request); + + return true; + } + + private updateTimeline(timeline: TimelineAggregate, reset: boolean) { + if (reset) { + this.timelinesBySource.delete(timeline.source); + // Override the limit, to re-query for all our existing cached (possibly visible) items to keep visual continuity + const { oldest } = timeline; + this.loadTimelineForSource(timeline.source, this.uri!, true, oldest !== undefined ? { limit: { timestamp: oldest.timestamp, id: oldest.id } } : undefined); + } else { + // Override the limit, to query for any newer items + const { newest } = timeline; + this.loadTimelineForSource(timeline.source, this.uri!, false, newest !== undefined ? { limit: { timestamp: newest.timestamp, id: newest.id } } : { limit: PageSize }); + } + } + + private _pendingRefresh = false; + private async handleRequest(request: TimelineRequest) { - let timeline: Timeline | undefined; + let response: Timeline | undefined; try { - timeline = await this.progressService.withProgress({ location: this.getProgressLocation() }, () => request.result); + response = await this.progressService.withProgress({ location: this.id }, () => request.result); } finally { - this._pendingRequests.delete(request.source); + this.pendingRequests.delete(request.source); } if ( - timeline === undefined || + response === undefined || request.tokenSource.token.isCancellationRequested || - request.uri !== this._uri + request.uri !== this.uri ) { - return; - } - - let items: TreeElement[]; - - const source = request.source; - - if (timeline !== undefined) { - if (timeline.paging !== undefined) { - let cursors = this._cursorsByProvider.get(timeline.source ?? source); - if (cursors === undefined) { - cursors = { startCursors: timeline.paging.cursors, more: timeline.paging.more ?? false }; - this._cursorsByProvider.set(timeline.source, cursors); - } else { - if (request.options.before) { - if (cursors.endCursors === undefined) { - cursors.endCursors = cursors.startCursors; - } - cursors.startCursors = timeline.paging.cursors; - } - else { - if (cursors.startCursors === undefined) { - cursors.startCursors = timeline.paging.cursors; - } - cursors.endCursors = timeline.paging.cursors; - } - cursors.more = timeline.paging.more ?? true; - } - } - } else { - this._cursorsByProvider.delete(source); - } - items = (timeline.items as TreeElement[]) ?? []; - - const alreadyHadItems = this._items.length !== 0; - - let changed; - if (request.options.cursor) { - changed = this.mergeItems(request.source, items, request.options); - } else { - changed = this.replaceItems(request.source, items); - } - - if (!changed) { - // If there are no items at all and no pending requests, make sure to refresh (to show the no timeline info message) - if (this._items.length === 0 && this._pendingRequests.size === 0) { + if (this.pendingRequests.size === 0 && this._pendingRefresh) { this.refresh(); } return; } - if (this._pendingRequests.size === 0 && this._items.length !== 0) { - const lastIndex = this._items.length - 1; - const lastItem = this._items[lastIndex]?.element; + const source = request.source; - if (timeline.paging?.more || Iterator.some(this._cursorsByProvider.values(), cursors => cursors.more)) { - if (isLoadMoreCommandItem(lastItem)) { - lastItem.themeIcon = undefined; - } - else { - this._items.push({ - element: { - handle: 'vscode-command:loadMore', - label: localize('timeline.loadMore', 'Load more'), - timestamp: 0 - } as CommandItem - }); - } - } - else { - if (isLoadMoreCommandItem(lastItem)) { - this._items.splice(lastIndex, 1); - } - } - } - - // If we have items already and there are other pending requests, debounce for a bit to wait for other requests - if (alreadyHadItems && this._pendingRequests.size !== 0) { - this.refreshDebounced(); + let updated = false; + const timeline = this.timelinesBySource.get(source); + if (timeline === undefined) { + this.timelinesBySource.set(source, new TimelineAggregate(response)); + updated = true; } else { + updated = timeline.add(response); + } + + if (updated) { + this._pendingRefresh = true; + + // If we have visible items already and there are other pending requests, debounce for a bit to wait for other requests + if (this.hasVisibleItems && this.pendingRequests.size !== 0) { + this.refreshDebounced(); + } else { + this.refresh(); + } + } else if (this.pendingRequests.size === 0 && this._pendingRefresh) { this.refresh(); } } - private mergeItems(source: string, items: TreeElement[] | undefined, options: TimelineOptions): boolean { - if (items?.length === undefined || items.length === 0) { - return false; + private *getItems(): Generator, any, any> { + let more = false; + + if (this.uri === undefined || this.timelinesBySource.size === 0) { + this._visibleItemCount = 0; + + return; } - if (options.before) { - const ids = new Set(); - const timestamps = new Set(); + const maxCount = this._maxItemCount; + let count = 0; - for (const item of items) { - if (item.id === undefined) { - timestamps.add(item.timestamp); - } - else { - ids.add(item.id); - } + if (this.timelinesBySource.size === 1) { + const [source, timeline] = Iterable.first(this.timelinesBySource); + + timeline.lastRenderedIndex = -1; + + if (this.excludedSources.has(source)) { + this._visibleItemCount = 0; + + return; } - // Remove any duplicate items - // I don't think we need to check all the items, just the most recent page - let i = Math.min(SubsequentPageSize, this._items.length); - let item; - while (i--) { - item = this._items[i].element; - if ( - (item.id === undefined && ids.has(item.id)) || - (item.timestamp === undefined && timestamps.has(item.timestamp)) - ) { - this._items.splice(i, 1); - } + if (timeline.items.length !== 0) { + // If we have any items, just say we have one for now -- the real count will be updated below + this._visibleItemCount = 1; } - this._items.splice(0, 0, ...items.map(item => ({ element: item }))); - } else { - this._items.push(...items.map(item => ({ element: item }))); + more = timeline.more; + + let lastRelativeTime: string | undefined; + for (const item of timeline.items) { + item.relativeTime = undefined; + item.hideRelativeTime = undefined; + + count++; + if (count > maxCount) { + more = true; + break; + } + + lastRelativeTime = updateRelativeTime(item, lastRelativeTime); + yield { element: item }; + } + + timeline.lastRenderedIndex = count - 1; + } + else { + const sources: { timeline: TimelineAggregate; iterator: IterableIterator; nextItem: IteratorResult }[] = []; + + let hasAnyItems = false; + let mostRecentEnd = 0; + + for (const [source, timeline] of this.timelinesBySource) { + timeline.lastRenderedIndex = -1; + + if (this.excludedSources.has(source) || timeline.stale) { + continue; + } + + if (timeline.items.length !== 0) { + hasAnyItems = true; + } + + if (timeline.more) { + more = true; + + const last = timeline.items[Math.min(maxCount, timeline.items.length - 1)]; + if (last.timestamp > mostRecentEnd) { + mostRecentEnd = last.timestamp; + } + } + + const iterator = timeline.items[Symbol.iterator](); + sources.push({ timeline: timeline, iterator: iterator, nextItem: iterator.next() }); + } + + this._visibleItemCount = hasAnyItems ? 1 : 0; + + function getNextMostRecentSource() { + return sources + .filter(source => !source.nextItem!.done) + .reduce((previous, current) => (previous === undefined || current.nextItem!.value.timestamp >= previous.nextItem!.value.timestamp) ? current : previous, undefined!); + } + + let lastRelativeTime: string | undefined; + let nextSource; + while (nextSource = getNextMostRecentSource()) { + nextSource.timeline.lastRenderedIndex++; + + const item = nextSource.nextItem.value; + item.relativeTime = undefined; + item.hideRelativeTime = undefined; + + if (item.timestamp >= mostRecentEnd) { + count++; + if (count > maxCount) { + more = true; + break; + } + + lastRelativeTime = updateRelativeTime(item, lastRelativeTime); + yield { element: item }; + } + + nextSource.nextItem = nextSource.iterator.next(); + } } - this.sortItems(); - return true; - } + this._visibleItemCount = count; - private replaceItems(source: string, items?: TreeElement[]): boolean { - if (items?.length) { - this._items.splice( - 0, this._items.length, - ...this._items.filter(item => item.element.source !== source), - ...items.map(item => ({ element: item })) - ); - this.sortItems(); - - return true; + if (more) { + yield { + element: { + handle: 'vscode-command:loadMore', + label: localize('timeline.loadMore', 'Load more'), + timestamp: 0 + } as CommandItem + }; } - - if (this._items.length && this._items.some(item => item.element.source === source)) { - this._items = this._items.filter(item => item.element.source !== source); - - return true; - } - - return false; - } - - private sortItems() { - this._items.sort( - (a, b) => - (b.element.timestamp - a.element.timestamp) || - (a.element.source === undefined - ? b.element.source === undefined ? 0 : 1 - : b.element.source === undefined ? -1 : b.element.source.localeCompare(a.element.source, undefined, { numeric: true, sensitivity: 'base' })) - ); - } private refresh() { - if (this._loadingMessageTimer) { - clearTimeout(this._loadingMessageTimer); - this._loadingMessageTimer = undefined; + if (!this.isBodyVisible()) { + return; } - if (this._items.length === 0) { - this.message = localize('timeline.noTimelineInfo', 'No timeline information was provided.'); + this.tree.setChildren(null, this.getItems() as any); + this._isEmpty = !this.hasVisibleItems; + + if (this.uri === undefined) { + this.titleDescription = undefined; + this.message = localize('timeline.editorCannotProvideTimeline', 'The active editor cannot provide timeline information.'); + } else if (this._isEmpty) { + if (this.pendingRequests.size !== 0) { + this.setLoadingUriMessage(); + } else { + this.titleDescription = basename(this.uri.fsPath); + this.message = localize('timeline.noTimelineInfo', 'No timeline information was provided.'); + } } else { + this.titleDescription = basename(this.uri.fsPath); this.message = undefined; } - this._tree.setChildren(null, this._items); + this._pendingRefresh = false; } @debounce(500) @@ -533,45 +755,71 @@ export class TimelinePane extends ViewPane { focus(): void { super.focus(); - this._tree.domFocus(); + this.tree.domFocus(); + } + + setExpanded(expanded: boolean): boolean { + const changed = super.setExpanded(expanded); + + if (changed && this.isBodyVisible()) { + if (!this.followActiveEditor) { + this.setUriCore(this.uri, true); + } else { + this.onActiveEditorChanged(); + } + } + + return changed; } setVisible(visible: boolean): void { if (visible) { - this._visibilityDisposables = new DisposableStore(); + this.visibilityDisposables = new DisposableStore(); - this.timelineService.onDidChangeProviders(this.onProvidersChanged, this, this._visibilityDisposables); - this.timelineService.onDidChangeTimeline(this.onTimelineChanged, this, this._visibilityDisposables); - this.timelineService.onDidReset(this.onReset, this, this._visibilityDisposables); - this.editorService.onDidActiveEditorChange(this.onActiveEditorChanged, this, this._visibilityDisposables); + this.editorService.onDidActiveEditorChange(this.onActiveEditorChanged, this, this.visibilityDisposables); + // Refresh the view on focus to update the relative timestamps + this.onDidFocus(() => this.refreshDebounced(), this, this.visibilityDisposables); + + super.setVisible(visible); this.onActiveEditorChanged(); } else { - this._visibilityDisposables?.dispose(); + this.visibilityDisposables?.dispose(); + + super.setVisible(visible); } } protected layoutBody(height: number, width: number): void { - this._tree.layout(height, width); + this.tree.layout(height, width); + } + + protected renderHeaderTitle(container: HTMLElement): void { + super.renderHeaderTitle(container, this.title); + + DOM.addClass(container, 'timeline-view'); + this.$titleDescription = DOM.append(container, DOM.$('span.description', undefined, this.titleDescription ?? '')); } protected renderBody(container: HTMLElement): void { - this._container = container; + super.renderBody(container); + + this.$container = container; DOM.addClasses(container, 'tree-explorer-viewlet-tree-view', 'timeline-tree-view'); - this._messageElement = DOM.append(this._container, DOM.$('.message')); - DOM.addClass(this._messageElement, 'timeline-subtle'); + this.$message = DOM.append(this.$container, DOM.$('.message')); + DOM.addClass(this.$message, 'timeline-subtle'); this.message = localize('timeline.editorCannotProvideTimeline', 'The active editor cannot provide timeline information.'); - this._treeElement = document.createElement('div'); - DOM.addClasses(this._treeElement, 'customview-tree', 'file-icon-themable-tree', 'hide-arrows'); - // DOM.addClass(this._treeElement, 'show-file-icons'); - container.appendChild(this._treeElement); + this.$tree = document.createElement('div'); + DOM.addClasses(this.$tree, 'customview-tree', 'file-icon-themable-tree', 'hide-arrows'); + // DOM.addClass(this.treeElement, 'show-file-icons'); + container.appendChild(this.$tree); - this._treeRenderer = this.instantiationService.createInstance(TimelineTreeRenderer, this._menus); - this._tree = >this.instantiationService.createInstance(WorkbenchObjectTree, 'TimelinePane', - this._treeElement, new TimelineListVirtualDelegate(), [this._treeRenderer], { + this.treeRenderer = this.instantiationService.createInstance(TimelineTreeRenderer, this.commands); + this.tree = >this.instantiationService.createInstance(WorkbenchObjectTree, 'TimelinePane', + this.$tree, new TimelineListVirtualDelegate(), [this.treeRenderer], { identityProvider: new TimelineIdentityProvider(), keyboardNavigationLabelProvider: new TimelineKeyboardNavigationLabelProvider(), overrideStyles: { @@ -580,16 +828,17 @@ export class TimelinePane extends ViewPane { } }); - const customTreeNavigator = new TreeResourceNavigator(this._tree, { openOnFocus: false, openOnSelection: false }); + const customTreeNavigator = ResourceNavigator.createTreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false }); this._register(customTreeNavigator); - this._register(this._tree.onContextMenu(e => this.onContextMenu(this._menus, e))); + this._register(this.tree.onContextMenu(e => this.onContextMenu(this.commands, e))); + this._register(this.tree.onDidChangeSelection(e => this.ensureValidItems())); this._register( customTreeNavigator.onDidOpenResource(e => { - if (!e.browserEvent) { + if (!e.browserEvent || !this.ensureValidItems()) { return; } - const selection = this._tree.getSelection(); + const selection = this.tree.getSelection(); const item = selection.length === 1 ? selection[0] : undefined; // eslint-disable-next-line eqeqeq if (item == null) { @@ -602,18 +851,37 @@ export class TimelinePane extends ViewPane { } } else if (isLoadMoreCommandItem(item)) { - // TODO: Change this, but right now this is the pending signal - if (item.themeIcon !== undefined) { + if (this.pendingRequests.size !== 0) { return; } + this._maxItemCount = this._visibleItemCount + PageSize; this.loadTimeline(false); } }) ); } + ensureValidItems() { + // If we don't have any non-excluded timelines, clear the tree and show the loading message + if (!this.hasVisibleItems || !this.timelineService.getSources().some(({ id }) => !this.excludedSources.has(id) && this.timelinesBySource.has(id))) { + this.tree.setChildren(null, undefined); + this._isEmpty = true; - private onContextMenu(menus: TimelineMenus, treeEvent: ITreeContextMenuEvent): void { + this.setLoadingUriMessage(); + + return false; + } + + return true; + } + + setLoadingUriMessage() { + const file = this.uri && basename(this.uri.fsPath); + this.titleDescription = file ?? ''; + this.message = file ? localize('timeline.loading', 'Loading timeline for {0}...', file) : ''; + } + + private onContextMenu(commands: TimelinePaneCommands, treeEvent: ITreeContextMenuEvent): void { const item = treeEvent.element; if (item === null) { return; @@ -623,8 +891,12 @@ export class TimelinePane extends ViewPane { event.preventDefault(); event.stopPropagation(); - this._tree.setFocus([item]); - const actions = menus.getResourceContextActions(item); + if (!this.ensureValidItems()) { + return; + } + + this.tree.setFocus([item]); + const actions = commands.getItemContextActions(item); if (!actions.length) { return; } @@ -641,10 +913,10 @@ export class TimelinePane extends ViewPane { }, onHide: (wasCancelled?: boolean) => { if (wasCancelled) { - this._tree.domFocus(); + this.tree.domFocus(); } }, - getActionsContext: (): TimelineActionContext => ({ uri: this._uri, item: item }), + getActionsContext: (): TimelineActionContext => ({ uri: this.uri, item: item }), actionRunner: new TimelineActionRunner() }); } @@ -694,7 +966,7 @@ class TimelineActionRunner extends ActionRunner { runAction(action: IAction, { uri, item }: TimelineActionContext): Promise { if (!isTimelineItem(item)) { - // TODO + // TODO@eamodio do we need to do anything else? return action.run(); } @@ -730,25 +1002,25 @@ export class TimelineListVirtualDelegate implements IListVirtualDelegate { readonly templateId: string = TimelineElementTemplate.id; - private _actionViewItemProvider: IActionViewItemProvider; + private actionViewItemProvider: IActionViewItemProvider; constructor( - private readonly _menus: TimelineMenus, + private readonly commands: TimelinePaneCommands, @IInstantiationService protected readonly instantiationService: IInstantiationService, - @IThemeService private _themeService: IThemeService + @IThemeService private themeService: IThemeService ) { - this._actionViewItemProvider = (action: IAction) => action instanceof MenuItemAction + this.actionViewItemProvider = (action: IAction) => action instanceof MenuItemAction ? this.instantiationService.createInstance(ContextAwareMenuEntryActionViewItem, action) : undefined; } - private _uri: URI | undefined; + private uri: URI | undefined; setUri(uri: URI | undefined) { - this._uri = uri; + this.uri = uri; } renderTemplate(container: HTMLElement): TimelineElementTemplate { - return new TimelineElementTemplate(container, this._actionViewItemProvider); + return new TimelineElementTemplate(container, this.actionViewItemProvider); } renderElement( @@ -761,7 +1033,7 @@ class TimelineTreeRenderer implements ITreeRenderer pane.followActiveEditor = !pane.followActiveEditor + )); + + this._register(MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({ + command: { + id: 'timeline.toggleFollowActiveEditor', + title: { value: localize('timeline.toggleFollowActiveEditorCommand.follow', "Automatically Follows the Active Editor"), original: 'Automatically Follows the Active Editor' }, + icon: { id: 'codicon/eye' }, + category: { value: localize('timeline', "Timeline"), original: 'Timeline' }, + }, + group: 'navigation', + order: 98, + when: TimelineFollowActiveEditorContext + }))); + + this._register(MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({ + command: { + id: 'timeline.toggleFollowActiveEditor', + title: { value: localize('timeline.toggleFollowActiveEditorCommand.unfollow', "Not Following Active Editor"), original: 'Not Following Active Editor' }, + icon: { id: 'codicon/eye-closed' }, + category: { value: localize('timeline', "Timeline"), original: 'Timeline' }, + }, + group: 'navigation', + order: 98, + when: TimelineFollowActiveEditorContext.toNegated() + }))); + + this._register(timelineService.onDidChangeProviders(() => this.updateTimelineSourceFilters())); + this.updateTimelineSourceFilters(); } - getResourceActions(element: TreeElement): IAction[] { + getItemActions(element: TreeElement): IAction[] { return this.getActions(MenuId.TimelineItemContext, { key: 'timelineItem', value: element.contextValue }).primary; } - getResourceContextActions(element: TreeElement): IAction[] { + getItemContextActions(element: TreeElement): IAction[] { return this.getActions(MenuId.TimelineItemContext, { key: 'timelineItem', value: element.contextValue }).secondary; } private getActions(menuId: MenuId, context: { key: string, value?: string }): { primary: IAction[]; secondary: IAction[]; } { - const contextKeyService = this.contextKeyService.createScoped(); - contextKeyService.createKey('view', this.id); - contextKeyService.createKey(context.key, context.value); + const scoped = this.contextKeyService.createScoped(); + scoped.createKey('view', this.pane.id); + scoped.createKey(context.key, context.value); - const menu = this.menuService.createMenu(menuId, contextKeyService); + const menu = this.menuService.createMenu(menuId, scoped); const primary: IAction[] = []; const secondary: IAction[] = []; const result = { primary, secondary }; createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, this.contextMenuService, g => /^inline/.test(g)); menu.dispose(); - contextKeyService.dispose(); + scoped.dispose(); return result; } + + private updateTimelineSourceFilters() { + this.sourceDisposables.clear(); + + const excluded = new Set(this.configurationService.getValue('timeline.excludeSources') ?? []); + + for (const source of this.timelineService.getSources()) { + this.sourceDisposables.add(registerAction2(class extends Action2 { + constructor() { + super({ + id: `timeline.toggleExcludeSource:${source.id}`, + title: { value: localize('timeline.filterSource', "Include: {0}", source.label), original: `Include: ${source.label}` }, + category: { value: localize('timeline', "Timeline"), original: 'Timeline' }, + menu: { + id: MenuId.TimelineTitle, + group: '2_sources', + }, + toggled: ContextKeyExpr.regex(`config.timeline.excludeSources`, new RegExp(`\\b${escapeRegExpCharacters(source.id)}\\b`)).negate() + }); + } + run(accessor: ServicesAccessor, ...args: any[]) { + if (excluded.has(source.id)) { + excluded.delete(source.id); + } else { + excluded.add(source.id); + } + + const configurationService = accessor.get(IConfigurationService); + configurationService.updateValue('timeline.excludeSources', [...excluded.keys()]); + } + })); + } + } } diff --git a/src/vs/workbench/contrib/timeline/common/timeline.ts b/src/vs/workbench/contrib/timeline/common/timeline.ts index 3c5597d21fc..84877649f50 100644 --- a/src/vs/workbench/contrib/timeline/common/timeline.ts +++ b/src/vs/workbench/contrib/timeline/common/timeline.ts @@ -15,6 +15,8 @@ export function toKey(extension: ExtensionIdentifier | string, source: string) { return `${typeof extension === 'string' ? extension : ExtensionIdentifier.toKey(extension)}|${source}`; } +export const TimelinePaneId = 'timeline'; + export interface TimelineItem { handle: string; source: string; @@ -29,18 +31,25 @@ export interface TimelineItem { detail?: string; command?: Command; contextValue?: string; + + relativeTime?: string; + hideRelativeTime?: boolean; } export interface TimelineChangeEvent { - id?: string; + id: string; uri?: URI; reset?: boolean } export interface TimelineOptions { cursor?: string; - before?: boolean; - limit?: number | string; + limit?: number | { timestamp: number; id?: string }; +} + +export interface InternalTimelineOptions { + cacheResults: boolean; + resetCache: boolean; } export interface Timeline { @@ -48,18 +57,19 @@ export interface Timeline { items: TimelineItem[]; paging?: { - cursors: { - before: string; - after?: string - }; - more?: boolean; + cursor: string | undefined; } } export interface TimelineProvider extends TimelineProviderDescriptor, IDisposable { onDidChange?: Event; - provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean }): Promise; + provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: InternalTimelineOptions): Promise; +} + +export interface TimelineSource { + id: string; + label: string; } export interface TimelineProviderDescriptor { @@ -86,17 +96,16 @@ export interface ITimelineService { onDidChangeProviders: Event; onDidChangeTimeline: Event; - onDidReset: Event; + onDidChangeUri: Event; registerTimelineProvider(provider: TimelineProvider): IDisposable; unregisterTimelineProvider(id: string): void; - getSources(): string[]; + getSources(): TimelineSource[]; - getTimeline(id: string, uri: URI, options: TimelineOptions, tokenSource: CancellationTokenSource, internalOptions?: { cacheResults?: boolean }): TimelineRequest | undefined; + getTimeline(id: string, uri: URI, options: TimelineOptions, tokenSource: CancellationTokenSource, internalOptions?: InternalTimelineOptions): TimelineRequest | undefined; - // refresh(fetch?: 'all' | 'more'): void; - reset(): void; + setUri(uri: URI): void; } const TIMELINE_SERVICE_ID = 'timeline'; diff --git a/src/vs/workbench/contrib/timeline/common/timelineService.ts b/src/vs/workbench/contrib/timeline/common/timelineService.ts index d017b15245d..4fd45ce01ac 100644 --- a/src/vs/workbench/contrib/timeline/common/timelineService.ts +++ b/src/vs/workbench/contrib/timeline/common/timelineService.ts @@ -9,7 +9,8 @@ import { IDisposable } from 'vs/base/common/lifecycle'; // import { basename } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; -import { ITimelineService, TimelineChangeEvent, TimelineOptions, TimelineProvidersChangeEvent, TimelineProvider } from './timeline'; +import { ITimelineService, TimelineChangeEvent, TimelineOptions, TimelineProvidersChangeEvent, TimelineProvider, InternalTimelineOptions, TimelinePaneId } from './timeline'; +import { IViewsService } from 'vs/workbench/common/views'; export class TimelineService implements ITimelineService { _serviceBrand: undefined; @@ -19,75 +20,151 @@ export class TimelineService implements ITimelineService { private readonly _onDidChangeTimeline = new Emitter(); readonly onDidChangeTimeline: Event = this._onDidChangeTimeline.event; + private readonly _onDidChangeUri = new Emitter(); + readonly onDidChangeUri: Event = this._onDidChangeUri.event; - private readonly _onDidReset = new Emitter(); - readonly onDidReset: Event = this._onDidReset.event; + private readonly providers = new Map(); + private readonly providerSubscriptions = new Map(); - private readonly _providers = new Map(); - private readonly _providerSubscriptions = new Map(); - - constructor(@ILogService private readonly logService: ILogService) { + constructor( + @ILogService private readonly logService: ILogService, + @IViewsService protected viewsService: IViewsService, + ) { + // let source = 'fast-source'; // this.registerTimelineProvider({ - // id: 'local-history', - // label: 'Local History', - // provideTimeline(uri: URI, token: CancellationToken) { - // return new Promise(resolve => setTimeout(() => { - // resolve([ + // scheme: '*', + // id: source, + // label: 'Fast Source', + // provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean | undefined; }) { + // if (options.cursor === undefined) { + // return Promise.resolve({ + // source: source, + // items: [ + // { + // handle: `${source}|1`, + // id: '1', + // label: 'Fast Timeline1', + // description: '', + // timestamp: Date.now(), + // source: source + // }, + // { + // handle: `${source}|2`, + // id: '2', + // label: 'Fast Timeline2', + // description: '', + // timestamp: Date.now() - 3000000000, + // source: source + // } + // ], + // paging: { + // cursor: 'next' + // } + // }); + // } + // return Promise.resolve({ + // source: source, + // items: [ // { - // id: '1', - // label: 'Slow Timeline1', - // description: basename(uri.fsPath), - // timestamp: Date.now(), - // source: 'local-history' + // handle: `${source}|3`, + // id: '3', + // label: 'Fast Timeline3', + // description: '', + // timestamp: Date.now() - 4000000000, + // source: source // }, // { - // id: '2', - // label: 'Slow Timeline2', - // description: basename(uri.fsPath), - // timestamp: new Date(0).getTime(), - // source: 'local-history' + // handle: `${source}|4`, + // id: '4', + // label: 'Fast Timeline4', + // description: '', + // timestamp: Date.now() - 300000000000, + // source: source // } - // ]); - // }, 3000)); + // ], + // paging: { + // cursor: undefined + // } + // }); // }, // dispose() { } // }); + // let source = 'slow-source'; // this.registerTimelineProvider({ - // id: 'slow-history', - // label: 'Slow History', - // provideTimeline(uri: URI, token: CancellationToken) { - // return new Promise(resolve => setTimeout(() => { - // resolve([ - // { - // id: '1', - // label: 'VERY Slow Timeline1', - // description: basename(uri.fsPath), - // timestamp: Date.now(), - // source: 'slow-history' - // }, - // { - // id: '2', - // label: 'VERY Slow Timeline2', - // description: basename(uri.fsPath), - // timestamp: new Date(0).getTime(), - // source: 'slow-history' - // } - // ]); - // }, 6000)); + // scheme: '*', + // id: source, + // label: 'Slow Source', + // provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean | undefined; }) { + // return new Promise(resolve => setTimeout(() => { + // resolve({ + // source: source, + // items: [ + // { + // handle: `${source}|1`, + // id: '1', + // label: 'Slow Timeline1', + // description: basename(uri.fsPath), + // timestamp: Date.now(), + // source: source + // }, + // { + // handle: `${source}|2`, + // id: '2', + // label: 'Slow Timeline2', + // description: basename(uri.fsPath), + // timestamp: new Date(0).getTime(), + // source: source + // } + // ] + // }); + // }, 5000)); + // }, + // dispose() { } + // }); + + // source = 'very-slow-source'; + // this.registerTimelineProvider({ + // scheme: '*', + // id: source, + // label: 'Very Slow Source', + // provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean | undefined; }) { + // return new Promise(resolve => setTimeout(() => { + // resolve({ + // source: source, + // items: [ + // { + // handle: `${source}|1`, + // id: '1', + // label: 'VERY Slow Timeline1', + // description: basename(uri.fsPath), + // timestamp: Date.now(), + // source: source + // }, + // { + // handle: `${source}|2`, + // id: '2', + // label: 'VERY Slow Timeline2', + // description: basename(uri.fsPath), + // timestamp: new Date(0).getTime(), + // source: source + // } + // ] + // }); + // }, 10000)); // }, // dispose() { } // }); } getSources() { - return [...this._providers.keys()]; + return [...this.providers.values()].map(p => ({ id: p.id, label: p.label })); } - getTimeline(id: string, uri: URI, options: TimelineOptions, tokenSource: CancellationTokenSource, internalOptions?: { cacheResults?: boolean }) { + getTimeline(id: string, uri: URI, options: TimelineOptions, tokenSource: CancellationTokenSource, internalOptions?: InternalTimelineOptions) { this.logService.trace(`TimelineService#getTimeline(${id}): uri=${uri.toString(true)}`); - const provider = this._providers.get(id); + const provider = this.providers.get(id); if (provider === undefined) { return undefined; } @@ -124,10 +201,10 @@ export class TimelineService implements ITimelineService { const id = provider.id; - const existing = this._providers.get(id); + const existing = this.providers.get(id); if (existing) { // For now to deal with https://github.com/microsoft/vscode/issues/89553 allow any overwritting here (still will be blocked in the Extension Host) - // TODO[ECA]: Ultimately will need to figure out a way to unregister providers when the Extension Host restarts/crashes + // TODO@eamodio: Ultimately will need to figure out a way to unregister providers when the Extension Host restarts/crashes // throw new Error(`Timeline Provider ${id} already exists.`); try { existing?.dispose(); @@ -135,15 +212,15 @@ export class TimelineService implements ITimelineService { catch { } } - this._providers.set(id, provider); + this.providers.set(id, provider); if (provider.onDidChange) { - this._providerSubscriptions.set(id, provider.onDidChange(e => this._onDidChangeTimeline.fire(e))); + this.providerSubscriptions.set(id, provider.onDidChange(e => this._onDidChangeTimeline.fire(e))); } this._onDidChangeProviders.fire({ added: [id] }); return { dispose: () => { - this._providers.delete(id); + this.providers.delete(id); this._onDidChangeProviders.fire({ removed: [id] }); } }; @@ -152,20 +229,17 @@ export class TimelineService implements ITimelineService { unregisterTimelineProvider(id: string): void { this.logService.trace(`TimelineService#unregisterTimelineProvider: id=${id}`); - if (!this._providers.has(id)) { + if (!this.providers.has(id)) { return; } - this._providers.delete(id); - this._providerSubscriptions.delete(id); + this.providers.delete(id); + this.providerSubscriptions.delete(id); this._onDidChangeProviders.fire({ removed: [id] }); } - // refresh(fetch?: 'all' | 'more') { - // this._onDidChangeTimeline.fire({ fetch: fetch }); - // } - - reset() { - this._onDidReset.fire(); + setUri(uri: URI) { + this.viewsService.openView(TimelinePaneId, true); + this._onDidChangeUri.fire(uri); } } diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index f0150abdd16..33503865093 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -67,11 +67,11 @@ export class ReleaseNotesManager { const html = await this.renderBody(releaseNoteText); const title = nls.localize('releaseNotesInputName', "Release Notes: {0}", version); - const activeControl = this._editorService.activeControl; + const activeEditorPane = this._editorService.activeEditorPane; if (this._currentReleaseNotes) { this._currentReleaseNotes.setName(title); this._currentReleaseNotes.webview.html = html; - this._webviewWorkbenchService.revealWebview(this._currentReleaseNotes, activeControl ? activeControl.group : this._editorGroupService.activeGroup, false); + this._webviewWorkbenchService.revealWebview(this._currentReleaseNotes, activeEditorPane ? activeEditorPane.group : this._editorGroupService.activeGroup, false); } else { this._currentReleaseNotes = this._webviewWorkbenchService.createWebview( generateUuid(), @@ -190,7 +190,7 @@ export class ReleaseNotesManager { body { padding: 10px 20px; line-height: 22px; - max-width: 780px; + max-width: 882px; margin: 0 auto; } diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index 786014463b6..2e7fe01da59 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -22,14 +22,14 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { ReleaseNotesManager } from './releaseNotesEditor'; import { isWindows } from 'vs/base/common/platform'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { RawContextKey, IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { FalseContext } from 'vs/platform/contextkey/common/contextkeys'; import { ShowCurrentReleaseNotesActionId, CheckForVSCodeUpdateActionId } from 'vs/workbench/contrib/update/common/update'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IProductService } from 'vs/platform/product/common/productService'; import product from 'vs/platform/product/common/product'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; export const CONTEXT_UPDATE_STATE = new RawContextKey('updateState', StateType.Idle); @@ -180,12 +180,16 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu @IActivityService private readonly activityService: IActivityService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IProductService private readonly productService: IProductService, - @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService ) { super(); this.state = updateService.state; this.updateStateContextKey = CONTEXT_UPDATE_STATE.bindTo(this.contextKeyService); + // opt-in to syncing + storageKeysSyncRegistryService.registerStorageKey({ key: 'neverShowAgain:update/win32-fast-updates', version: 1 }); + this._register(updateService.onStateChange(this.onUpdateStateChange, this)); this.onUpdateStateChange(this.updateService.state); @@ -417,7 +421,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu command: { id: 'update.checking', title: nls.localize('checkingForUpdates', "Checking for Updates..."), - precondition: FalseContext + precondition: ContextKeyExpr.false() }, when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.CheckingForUpdates) }); @@ -438,7 +442,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu command: { id: 'update.downloading', title: nls.localize('DownloadingUpdate', "Downloading Update..."), - precondition: FalseContext + precondition: ContextKeyExpr.false() }, when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Downloading) }); @@ -459,7 +463,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu command: { id: 'update.updating', title: nls.localize('installingUpdate', "Installing Update..."), - precondition: FalseContext + precondition: ContextKeyExpr.false() }, when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Updating) }); diff --git a/src/vs/workbench/contrib/url/common/trustedDomains.ts b/src/vs/workbench/contrib/url/common/trustedDomains.ts index 7c28ccf7ce8..dc46dcaaadb 100644 --- a/src/vs/workbench/contrib/url/common/trustedDomains.ts +++ b/src/vs/workbench/contrib/url/common/trustedDomains.ts @@ -10,6 +10,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; const TRUSTED_DOMAINS_URI = URI.parse('trustedDomains:/Trusted Domains'); @@ -26,64 +27,86 @@ export const manageTrustedDomainSettingsCommand = { } }; +type ConfigureTrustedDomainChoice = 'trustDomain' | 'trustSubdomain' | 'trustAll' | 'manage'; +interface ConfigureTrustedDomainsQuickPickItem extends IQuickPickItem { + id: ConfigureTrustedDomainChoice; +} +type ConfigureTrustedDomainsChoiceClassification = { + choice: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; +}; + export async function configureOpenerTrustedDomainsHandler( trustedDomains: string[], domainToConfigure: string, quickInputService: IQuickInputService, storageService: IStorageService, - editorService: IEditorService + editorService: IEditorService, + telemetryService: ITelemetryService ) { const parsedDomainToConfigure = URI.parse(domainToConfigure); const toplevelDomainSegements = parsedDomainToConfigure.authority.split('.'); const domainEnd = toplevelDomainSegements.slice(toplevelDomainSegements.length - 2).join('.'); const topLevelDomain = '*.' + domainEnd; - const trustDomainAndOpenLinkItem: IQuickPickItem = { + const trustDomainAndOpenLinkItem: ConfigureTrustedDomainsQuickPickItem = { type: 'item', label: localize('trustedDomain.trustDomain', 'Trust {0}', domainToConfigure), - id: domainToConfigure, + id: 'trustDomain', picked: true }; - const trustSubDomainAndOpenLinkItem: IQuickPickItem = { + const trustSubDomainAndOpenLinkItem: ConfigureTrustedDomainsQuickPickItem = { type: 'item', label: localize('trustedDomain.trustSubDomain', 'Trust {0} and all its subdomains', domainEnd), - id: topLevelDomain + id: 'trustSubdomain' }; - const openAllLinksItem: IQuickPickItem = { + const openAllLinksItem: ConfigureTrustedDomainsQuickPickItem = { type: 'item', label: localize('trustedDomain.trustAllDomains', 'Trust all domains (disables link protection)'), - id: '*' + id: 'trustAll' }; - const manageTrustedDomainItem: IQuickPickItem = { + const manageTrustedDomainItem: ConfigureTrustedDomainsQuickPickItem = { type: 'item', label: localize('trustedDomain.manageTrustedDomains', 'Manage Trusted Domains'), id: 'manage' }; - const pickedResult = await quickInputService.pick( + const pickedResult = await quickInputService.pick( [trustDomainAndOpenLinkItem, trustSubDomainAndOpenLinkItem, openAllLinksItem, manageTrustedDomainItem], { activeItem: trustDomainAndOpenLinkItem } ); - if (pickedResult) { - if (pickedResult.id === 'manage') { - editorService.openEditor({ - resource: TRUSTED_DOMAINS_URI, - mode: 'jsonc' - }); - return trustedDomains; - } - if (pickedResult.id && trustedDomains.indexOf(pickedResult.id) === -1) { - storageService.remove('http.linkProtectionTrustedDomainsContent', StorageScope.GLOBAL); - storageService.store( - 'http.linkProtectionTrustedDomains', - JSON.stringify([...trustedDomains, pickedResult.id]), - StorageScope.GLOBAL - ); + if (pickedResult && pickedResult.id) { + telemetryService.publicLog2<{ choice: string }, ConfigureTrustedDomainsChoiceClassification>( + 'trustedDomains.configureTrustedDomainsQuickPickChoice', + { choice: pickedResult.id } + ); - return [...trustedDomains, pickedResult.id]; + switch (pickedResult.id) { + case 'manage': + editorService.openEditor({ + resource: TRUSTED_DOMAINS_URI, + mode: 'jsonc' + }); + return trustedDomains; + case 'trustDomain': + case 'trustSubdomain': + case 'trustAll': + const itemToTrust = pickedResult.id === 'trustDomain' + ? domainToConfigure + : pickedResult.id === 'trustSubdomain' ? topLevelDomain : '*'; + + if (trustedDomains.indexOf(itemToTrust) === -1) { + storageService.remove('http.linkProtectionTrustedDomainsContent', StorageScope.GLOBAL); + storageService.store( + 'http.linkProtectionTrustedDomains', + JSON.stringify([...trustedDomains, itemToTrust]), + StorageScope.GLOBAL + ); + + return [...trustedDomains, pickedResult.id]; + } } } diff --git a/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts b/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts index d8b03dc04d0..3862d4fb66d 100644 --- a/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts +++ b/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts @@ -19,7 +19,11 @@ import { } from 'vs/workbench/contrib/url/common/trustedDomains'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +type TrustedDomainsDialogActionClassification = { + action: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; +}; export class OpenerValidatorContributions implements IWorkbenchContribution { constructor( @@ -29,7 +33,8 @@ export class OpenerValidatorContributions implements IWorkbenchContribution { @IProductService private readonly _productService: IProductService, @IQuickInputService private readonly _quickInputService: IQuickInputService, @IEditorService private readonly _editorService: IEditorService, - @IClipboardService private readonly _clipboardService: IClipboardService + @IClipboardService private readonly _clipboardService: IClipboardService, + @ITelemetryService private readonly _telemetryService: ITelemetryService ) { this._openerService.registerValidator({ shouldOpen: r => this.validateLink(r) }); } @@ -88,20 +93,34 @@ export class OpenerValidatorContributions implements IWorkbenchContribution { // Open Link if (choice === 0) { + this._telemetryService.publicLog2<{ action: string }, TrustedDomainsDialogActionClassification>( + 'trustedDomains.dialogAction', + { action: 'open' } + ); return true; } // Copy Link else if (choice === 1) { + this._telemetryService.publicLog2<{ action: string }, TrustedDomainsDialogActionClassification>( + 'trustedDomains.dialogAction', + { action: 'copy' } + ); this._clipboardService.writeText(resource.toString(true)); } // Configure Trusted Domains else if (choice === 3) { + this._telemetryService.publicLog2<{ action: string }, TrustedDomainsDialogActionClassification>( + 'trustedDomains.dialogAction', + { action: 'configure' } + ); + const pickedDomains = await configureOpenerTrustedDomainsHandler( trustedDomains, domainToOpen, this._quickInputService, this._storageService, - this._editorService + this._editorService, + this._telemetryService ); // Trust all domains if (pickedDomains.indexOf('*') !== -1) { @@ -114,6 +133,11 @@ export class OpenerValidatorContributions implements IWorkbenchContribution { return false; } + this._telemetryService.publicLog2<{ action: string }, TrustedDomainsDialogActionClassification>( + 'trustedDomains.dialogAction', + { action: 'cancel' } + ); + return false; } } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts index 367dd0b6fb3..d215668d3f0 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts @@ -7,6 +7,8 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } fr import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { UserDataSyncWorkbenchContribution } from 'vs/workbench/contrib/userDataSync/browser/userDataSync'; +import { UserDataSyncViewContribution } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncView'; const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(UserDataSyncWorkbenchContribution, LifecyclePhase.Ready); +workbenchRegistry.registerWorkbenchContribution(UserDataSyncViewContribution, LifecyclePhase.Ready); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 403c7e046bd..0be1cd39968 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -9,13 +9,13 @@ import { canceled, isPromiseCanceledError } from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, dispose, MutableDisposable, toDisposable, IDisposable } from 'vs/base/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; -import { isEqual } from 'vs/base/common/resources'; +import { isEqual, basename } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import type { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import type { IEditorContribution } from 'vs/editor/common/editorCommon'; import type { ITextModel } from 'vs/editor/common/model'; -import { AuthenticationSession } from 'vs/editor/common/modes'; +import { AuthenticationSession, AuthenticationSessionsChangeEvent } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; @@ -25,16 +25,19 @@ import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/c import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { CONTEXT_SYNC_STATE, getSyncSourceFromRemoteContentResource, getUserDataSyncStore, ISyncConfiguration, IUserDataAutoSyncService, IUserDataSyncService, IUserDataSyncStore, registerConfiguration, SyncSource, SyncStatus, toRemoteContentResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncEnablementService, ResourceKey, getSyncSourceFromPreviewResource, CONTEXT_SYNC_ENABLEMENT } from 'vs/platform/userDataSync/common/userDataSync'; +import { + CONTEXT_SYNC_STATE, getUserDataSyncStore, ISyncConfiguration, IUserDataAutoSyncService, IUserDataSyncService, IUserDataSyncStore, registerConfiguration, + SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncEnablementService, CONTEXT_SYNC_ENABLEMENT, + SyncResourceConflicts, Conflict, getSyncResourceFromLocalPreview +} from 'vs/platform/userDataSync/common/userDataSync'; import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets'; import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import type { IEditorInput } from 'vs/workbench/common/editor'; +import { IEditorInput, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import * as Constants from 'vs/workbench/contrib/logs/common/logConstants'; import { IOutputService } from 'vs/workbench/contrib/output/common/output'; @@ -49,7 +52,7 @@ import { fromNow } from 'vs/base/common/date'; import { IProductService } from 'vs/platform/product/common/productService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { timeout } from 'vs/base/common/async'; +import { distinct } from 'vs/base/common/arrays'; const enum AuthStatus { Initializing = 'Initializing', @@ -60,14 +63,17 @@ const enum AuthStatus { const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey('authTokenStatus', AuthStatus.Initializing); const CONTEXT_CONFLICTS_SOURCES = new RawContextKey('conflictsSources', ''); -type ConfigureSyncQuickPickItem = { id: ResourceKey, label: string, description?: string }; +const USER_DATA_SYNC_ACCOUNT_PREFERENCE_KEY = 'userDataSyncAccountPreference'; -function getSyncAreaLabel(source: SyncSource): string { +type ConfigureSyncQuickPickItem = { id: SyncResource, label: string, description?: string }; + +function getSyncAreaLabel(source: SyncResource): string { switch (source) { - case SyncSource.Settings: return localize('settings', "Settings"); - case SyncSource.Keybindings: return localize('keybindings', "Keybindings"); - case SyncSource.Extensions: return localize('extensions', "Extensions"); - case SyncSource.GlobalState: return localize('ui state label', "UI State"); + case SyncResource.Settings: return localize('settings', "Settings"); + case SyncResource.Keybindings: return localize('keybindings', "Keyboard Shortcuts"); + case SyncResource.Snippets: return localize('snippets', "User Snippets"); + case SyncResource.Extensions: return localize('extensions', "Extensions"); + case SyncResource.GlobalState: return localize('ui state label', "UI State"); } } @@ -92,18 +98,19 @@ const getActivityTitle = (label: string, userDataSyncService: IUserDataSyncServi const getIdentityTitle = (label: string, authenticationProviderId: string, account: AuthenticationSession | undefined, authenticationService: IAuthenticationService): string => { return account ? `${label} (${authenticationService.getDisplayName(authenticationProviderId)}:${account.accountName})` : label; }; -const turnOnSyncCommand = { id: 'workbench.userData.actions.syncStart', title: localize('turn on sync with category', "Sync: Turn on Sync") }; -const signInCommand = { id: 'workbench.userData.actions.signin', title: localize('sign in', "Sync: Sign in to sync") }; -const stopSyncCommand = { id: 'workbench.userData.actions.stopSync', title(authenticationProviderId: string, account: AuthenticationSession | undefined, authenticationService: IAuthenticationService) { return getIdentityTitle(localize('stop sync', "Sync: Turn off Sync"), authenticationProviderId, account, authenticationService); } }; -const resolveSettingsConflictsCommand = { id: 'workbench.userData.actions.resolveSettingsConflicts', title: localize('showConflicts', "Sync: Show Settings Conflicts") }; -const resolveKeybindingsConflictsCommand = { id: 'workbench.userData.actions.resolveKeybindingsConflicts', title: localize('showKeybindingsConflicts', "Sync: Show Keybindings Conflicts") }; -const configureSyncCommand = { id: 'workbench.userData.actions.configureSync', title: localize('configure sync', "Sync: Configure") }; +const turnOnSyncCommand = { id: 'workbench.userData.actions.syncStart', title: localize('turn on sync with category', "Preferences Sync: Turn on...") }; +const signInCommand = { id: 'workbench.userData.actions.signin', title: localize('sign in', "Preferences Sync: Sign in to sync") }; +const stopSyncCommand = { id: 'workbench.userData.actions.stopSync', title(authenticationProviderId: string, account: AuthenticationSession | undefined, authenticationService: IAuthenticationService) { return getIdentityTitle(localize('stop sync', "Preferences Sync: Turn Off"), authenticationProviderId, account, authenticationService); } }; +const resolveSettingsConflictsCommand = { id: 'workbench.userData.actions.resolveSettingsConflicts', title: localize('showConflicts', "Preferences Sync: Show Settings Conflicts") }; +const resolveKeybindingsConflictsCommand = { id: 'workbench.userData.actions.resolveKeybindingsConflicts', title: localize('showKeybindingsConflicts', "Preferences Sync: Show Keybindings Conflicts") }; +const resolveSnippetsConflictsCommand = { id: 'workbench.userData.actions.resolveSnippetsConflicts', title: localize('showSnippetsConflicts', "Preferences Sync: Show User Snippets Conflicts") }; +const configureSyncCommand = { id: 'workbench.userData.actions.configureSync', title: localize('configure sync', "Preferences Sync: Configure...") }; const showSyncActivityCommand = { id: 'workbench.userData.actions.showSyncActivity', title(userDataSyncService: IUserDataSyncService): string { - return getActivityTitle(localize('show sync log', "Sync: Show Log"), userDataSyncService); + return getActivityTitle(localize('show sync log', "Preferences Sync: Show Log"), userDataSyncService); } }; -const showSyncSettingsCommand = { id: 'workbench.userData.actions.syncSettings', title: localize('sync settings', "Sync: Settings"), }; +const showSyncSettingsCommand = { id: 'workbench.userData.actions.syncSettings', title: localize('sync settings', "Preferences Sync: Show Settings"), }; export class UserDataSyncWorkbenchContribution extends Disposable implements IWorkbenchContribution { @@ -116,6 +123,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private readonly badgeDisposable = this._register(new MutableDisposable()); private readonly signInNotificationDisposable = this._register(new MutableDisposable()); private _activeAccount: AuthenticationSession | undefined; + private loginInProgress: boolean = false; constructor( @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, @@ -133,10 +141,9 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo @IOutputService private readonly outputService: IOutputService, @IAuthenticationTokenService private readonly authTokenService: IAuthenticationTokenService, @IUserDataAutoSyncService userDataAutoSyncService: IUserDataAutoSyncService, - @ITextModelService textModelResolverService: ITextModelService, + @ITextModelService private readonly textModelResolverService: ITextModelService, @IPreferencesService private readonly preferencesService: IPreferencesService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IFileService private readonly fileService: IFileService, @IProductService private readonly productService: IProductService, @IStorageService private readonly storageService: IStorageService, @IOpenerService private readonly openerService: IOpenerService, @@ -150,10 +157,10 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo if (this.userDataSyncStore) { registerConfiguration(); this.onDidChangeSyncStatus(this.userDataSyncService.status); - this.onDidChangeConflicts(this.userDataSyncService.conflictsSources); + this.onDidChangeConflicts(this.userDataSyncService.conflicts); this.onDidChangeEnablement(this.userDataSyncEnablementService.isEnabled()); this._register(Event.debounce(userDataSyncService.onDidChangeStatus, () => undefined, 500)(() => this.onDidChangeSyncStatus(this.userDataSyncService.status))); - this._register(userDataSyncService.onDidChangeConflicts(() => this.onDidChangeConflicts(this.userDataSyncService.conflictsSources))); + this._register(userDataSyncService.onDidChangeConflicts(() => this.onDidChangeConflicts(this.userDataSyncService.conflicts))); this._register(userDataSyncService.onSyncErrors(errors => this.onSyncErrors(errors))); this._register(this.authTokenService.onTokenFailed(_ => this.onTokenFailed())); this._register(this.userDataSyncEnablementService.onDidChangeEnablement(enabled => this.onDidChangeEnablement(enabled))); @@ -191,18 +198,16 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo return; } - const selectedAccount = await this.quickInputService.pick(sessions.map(session => { - return { - id: session.id, - label: session.accountName - }; - }), { canPickMany: false }); - - if (selectedAccount) { - const selected = sessions.filter(account => selectedAccount.id === account.id)[0]; - this.logAuthenticatedEvent(selected); - await this.setActiveAccount(selected); + const accountPreference = this.storageService.get(USER_DATA_SYNC_ACCOUNT_PREFERENCE_KEY, StorageScope.GLOBAL); + if (accountPreference) { + const matchingSession = sessions.find(session => session.id === accountPreference); + if (matchingSession) { + this.setActiveAccount(matchingSession); + return; + } } + + await this.showSwitchAccountPicker(sessions); } private logAuthenticatedEvent(session: AuthenticationSession): void { @@ -242,15 +247,83 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.updateBadge(); } - private async onDidChangeSessions(providerId: string): Promise { + private async showSwitchAccountPicker(sessions: readonly AuthenticationSession[]): Promise { + return new Promise((resolve, _) => { + const quickPick = this.quickInputService.createQuickPick<{ label: string, session: AuthenticationSession }>(); + quickPick.title = localize('chooseAccountTitle', "Preferences Sync: Choose Account"); + quickPick.placeholder = localize('chooseAccount', "Choose an account you would like to use for preferences sync"); + const dedupedSessions = distinct(sessions, (session) => session.accountName); + quickPick.items = dedupedSessions.map(session => { + return { + label: session.accountName, + session: session + }; + }); + + quickPick.onDidHide(() => { + quickPick.dispose(); + resolve(); + }); + + quickPick.onDidAccept(() => { + const selected = quickPick.selectedItems[0]; + this.setActiveAccount(selected.session); + this.storageService.store(USER_DATA_SYNC_ACCOUNT_PREFERENCE_KEY, selected.session.id, StorageScope.GLOBAL); + quickPick.dispose(); + resolve(); + }); + + quickPick.show(); + }); + } + + private async onDidChangeSessions(e: { providerId: string, event: AuthenticationSessionsChangeEvent }): Promise { + const { providerId, event } = e; if (providerId === this.userDataSyncStore!.authenticationProviderId) { + if (this.loginInProgress) { + return; + } + if (this.activeAccount) { - // Try to update existing account, case where access token has been refreshed - const accounts = (await this.authenticationService.getSessions(this.userDataSyncStore!.authenticationProviderId) || []); - const matchingAccount = accounts.filter(a => a.id === this.activeAccount?.id)[0]; - this.setActiveAccount(matchingAccount); + if (event.removed.length) { + const activeWasRemoved = !!event.removed.find(removed => removed === this.activeAccount!.id); + if (activeWasRemoved) { + this.setActiveAccount(undefined); + this.notificationService.notify({ + severity: Severity.Info, + message: localize('turned off on logout', "Sync has stopped because you are no longer signed in."), + actions: { + primary: [new Action('turn on sync', localize('turn on sync', "Turn on Sync"), undefined, true, () => this.turnOn())] + } + }); + return; + return; + } + } + + if (event.added.length) { + // Offer to switch accounts + const accounts = (await this.authenticationService.getSessions(this.userDataSyncStore!.authenticationProviderId) || []); + await this.showSwitchAccountPicker(accounts); + return; + } + + if (event.changed.length) { + const activeWasChanged = !!event.changed.find(changed => changed === this.activeAccount!.id); + if (activeWasChanged) { + // Try to update existing account, case where access token has been refreshed + const accounts = (await this.authenticationService.getSessions(this.userDataSyncStore!.authenticationProviderId) || []); + const matchingAccount = accounts.filter(a => a.id === this.activeAccount?.id)[0]; + this.setActiveAccount(matchingAccount); + } + } } else { - this.initializeActiveAccount(); + await this.initializeActiveAccount(); + + // If logged in for the first time from accounts menu, prompt if sync should be turned on + if (this.activeAccount) { + this.turnOn(true); + } } } } @@ -283,45 +356,60 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.updateBadge(); } - private readonly conflictsDisposables = new Map(); - private onDidChangeConflicts(conflicts: SyncSource[]) { + private readonly conflictsDisposables = new Map(); + private onDidChangeConflicts(conflicts: SyncResourceConflicts[]) { this.updateBadge(); if (conflicts.length) { - this.conflictsSources.set(this.userDataSyncService.conflictsSources.join(',')); + const conflictsSources: SyncResource[] = conflicts.map(conflict => conflict.syncResource); + this.conflictsSources.set(conflictsSources.join(',')); + if (conflictsSources.indexOf(SyncResource.Snippets) !== -1) { + this.registerShowSnippetsConflictsAction(); + } // Clear and dispose conflicts those were cleared this.conflictsDisposables.forEach((disposable, conflictsSource) => { - if (this.userDataSyncService.conflictsSources.indexOf(conflictsSource) === -1) { + if (conflictsSources.indexOf(conflictsSource) === -1) { disposable.dispose(); this.conflictsDisposables.delete(conflictsSource); } }); - for (const conflictsSource of this.userDataSyncService.conflictsSources) { - const conflictsEditorInput = this.getConflictsEditorInput(conflictsSource); - if (!conflictsEditorInput && !this.conflictsDisposables.has(conflictsSource)) { - const conflictsArea = getSyncAreaLabel(conflictsSource); + for (const { syncResource, conflicts } of this.userDataSyncService.conflicts) { + const conflictsEditorInputs = this.getConflictsEditorInputs(syncResource); + + // close stale conflicts editor previews + if (conflictsEditorInputs.length) { + conflictsEditorInputs.forEach(input => { + if (!conflicts.some(({ local }) => isEqual(local, input.master.resource))) { + input.dispose(); + } + }); + } + + // Show conflicts notification if not shown before + else if (!this.conflictsDisposables.has(syncResource)) { + const conflictsArea = getSyncAreaLabel(syncResource); const handle = this.notificationService.prompt(Severity.Warning, localize('conflicts detected', "Unable to sync due to conflicts in {0}. Please resolve them to continue.", conflictsArea.toLowerCase()), [ { label: localize('accept remote', "Accept Remote"), run: () => { - this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: conflictsSource, action: 'acceptRemote' }); - this.acceptRemote(conflictsSource); + this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: syncResource, action: 'acceptRemote' }); + this.acceptRemote(syncResource, conflicts); } }, { label: localize('accept local', "Accept Local"), run: () => { - this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: conflictsSource, action: 'acceptLocal' }); - this.acceptLocal(conflictsSource); + this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: syncResource, action: 'acceptLocal' }); + this.acceptLocal(syncResource, conflicts); } }, { label: localize('show conflicts', "Show Conflicts"), run: () => { - this.telemetryService.publicLog2<{ source: string, action?: string }, SyncConflictsClassification>('sync/showConflicts', { source: conflictsSource }); - this.handleConflicts(conflictsSource); + this.telemetryService.publicLog2<{ source: string, action?: string }, SyncConflictsClassification>('sync/showConflicts', { source: syncResource }); + this.handleConflicts({ syncResource, conflicts }); } } ], @@ -329,18 +417,18 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo sticky: true } ); - this.conflictsDisposables.set(conflictsSource, toDisposable(() => { + this.conflictsDisposables.set(syncResource, toDisposable(() => { // close the conflicts warning notification handle.close(); // close opened conflicts editor previews - const conflictsEditorInput = this.getConflictsEditorInput(conflictsSource); - if (conflictsEditorInput) { - conflictsEditorInput.dispose(); + const conflictsEditorInputs = this.getConflictsEditorInputs(syncResource); + if (conflictsEditorInputs.length) { + conflictsEditorInputs.forEach(input => input.dispose()); } - this.conflictsDisposables.delete(conflictsSource); + this.conflictsDisposables.delete(syncResource); })); } } @@ -352,29 +440,24 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private async acceptRemote(syncSource: SyncSource) { + private async acceptRemote(syncResource: SyncResource, conflicts: Conflict[]) { try { - const contents = await this.userDataSyncService.getRemoteContent(syncSource, false); - if (contents) { - await this.userDataSyncService.accept(syncSource, contents); + for (const conflict of conflicts) { + const modelRef = await this.textModelResolverService.createModelReference(conflict.remote); + await this.userDataSyncService.acceptConflict(conflict.remote, modelRef.object.textEditorModel.getValue()); + modelRef.dispose(); } } catch (e) { this.notificationService.error(e); } } - private async acceptLocal(syncSource: SyncSource): Promise { + private async acceptLocal(syncResource: SyncResource, conflicts: Conflict[]): Promise { try { - const previewResource = syncSource === SyncSource.Settings - ? this.workbenchEnvironmentService.settingsSyncPreviewResource - : syncSource === SyncSource.Keybindings - ? this.workbenchEnvironmentService.keybindingsSyncPreviewResource - : null; - if (previewResource) { - const fileContent = await this.fileService.readFile(previewResource); - if (fileContent) { - this.userDataSyncService.accept(syncSource, fileContent.value.toString()); - } + for (const conflict of conflicts) { + const modelRef = await this.textModelResolverService.createModelReference(conflict.local); + await this.userDataSyncService.acceptConflict(conflict.local, modelRef.object.textEditorModel.getValue()); + modelRef.dispose(); } } catch (e) { this.notificationService.error(e); @@ -415,15 +498,15 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }); return; case UserDataSyncErrorCode.TooLarge: - if (error.source === SyncSource.Keybindings || error.source === SyncSource.Settings) { - this.disableSync(error.source); - const sourceArea = getSyncAreaLabel(error.source); + if (error.resource === SyncResource.Keybindings || error.resource === SyncResource.Settings) { + this.disableSync(error.resource); + const sourceArea = getSyncAreaLabel(error.resource); this.notificationService.notify({ severity: Severity.Error, message: localize('too large', "Disabled syncing {0} because size of the {1} file to sync is larger than {2}. Please open the file and reduce the size and enable sync", sourceArea.toLowerCase(), sourceArea.toLowerCase(), '100kb'), actions: { primary: [new Action('open sync file', localize('open file', "Open {0} File", sourceArea), undefined, true, - () => error.source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] + () => error.resource === SyncResource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] } }); } @@ -438,8 +521,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private readonly invalidContentErrorDisposables = new Map(); - private onSyncErrors(errors: [SyncSource, UserDataSyncError][]): void { + private readonly invalidContentErrorDisposables = new Map(); + private onSyncErrors(errors: [SyncResource, UserDataSyncError][]): void { if (errors.length) { for (const [source, error] of errors) { switch (error.code) { @@ -460,15 +543,15 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private handleInvalidContentError(source: SyncSource): void { + private handleInvalidContentError(source: SyncResource): void { if (this.invalidContentErrorDisposables.has(source)) { return; } - if (source !== SyncSource.Settings && source !== SyncSource.Keybindings) { + if (source !== SyncResource.Settings && source !== SyncResource.Keybindings) { return; } - const resource = source === SyncSource.Settings ? this.workbenchEnvironmentService.settingsResource : this.workbenchEnvironmentService.keybindingsResource; - if (isEqual(resource, this.editorService.activeEditor?.resource)) { + const resource = source === SyncResource.Settings ? this.workbenchEnvironmentService.settingsResource : this.workbenchEnvironmentService.keybindingsResource; + if (isEqual(resource, toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }))) { // Do not show notification if the file in error is active return; } @@ -478,7 +561,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo message: localize('errorInvalidConfiguration', "Unable to sync {0} because there are some errors/warnings in the file. Please open the file to correct errors/warnings in it.", errorArea.toLowerCase()), actions: { primary: [new Action('open sync file', localize('open file', "Open {0} File", errorArea), undefined, true, - () => source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] + () => source === SyncResource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] } }); this.invalidContentErrorDisposables.set(source, toDisposable(() => { @@ -497,8 +580,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo if (this.userDataSyncService.status !== SyncStatus.Uninitialized && this.userDataSyncEnablementService.isEnabled() && this.authenticationState.get() === AuthStatus.SignedOut) { badge = new NumberBadge(1, () => localize('sign in to sync', "Sign in to Sync")); - } else if (this.userDataSyncService.conflictsSources.length) { - badge = new NumberBadge(this.userDataSyncService.conflictsSources.length, () => localize('has conflicts', "Sync: Conflicts Detected")); + } else if (this.userDataSyncService.conflicts.length) { + badge = new NumberBadge(this.userDataSyncService.conflicts.reduce((result, syncResourceConflict) => { return result + syncResourceConflict.conflicts.length; }, 0), () => localize('has conflicts', "Preferences Sync: Conflicts Detected")); } if (badge) { @@ -506,7 +589,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private async turnOn(): Promise { + private async turnOn(skipAccountPick?: boolean): Promise { if (!this.storageService.getBoolean('sync.donotAskPreviewConfirmation', StorageScope.GLOBAL, false)) { const result = await this.dialogService.show( Severity.Info, @@ -524,12 +607,22 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo case 0: this.openerService.open(URI.parse('https://aka.ms/vscode-settings-sync-help')); return; case 2: return; } + } else if (skipAccountPick) { + const result = await this.dialogService.confirm({ + type: 'info', + message: localize('turn on sync confirmation', "Do you want to turn on preferences sync?"), + primaryButton: localize('turn on', "Turn On") + }); + if (!result.confirmed) { + return; + } } + return new Promise((c, e) => { const disposables: DisposableStore = new DisposableStore(); const quickPick = this.quickInputService.createQuickPick(); disposables.add(quickPick); - quickPick.title = localize('turn on title', "Sync: Turn On"); + quickPick.title = localize('turn on title', "Preferences Sync: Turn On"); quickPick.ok = false; quickPick.customButton = true; if (this.authenticationState.get() === AuthStatus.SignedIn) { @@ -548,7 +641,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo disposables.add(Event.any(quickPick.onDidAccept, quickPick.onDidCustom)(async () => { if (quickPick.selectedItems.length) { this.updateConfiguration(items, quickPick.selectedItems); - this.doTurnOn().then(c, e); + this.doTurnOn(skipAccountPick).then(c, e); quickPick.hide(); } })); @@ -557,8 +650,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }); } - private async doTurnOn(): Promise { - if (this.authenticationState.get() === AuthStatus.SignedIn) { + private async doTurnOn(skipAccountPick?: boolean): Promise { + if (this.authenticationState.get() === AuthStatus.SignedIn && !skipAccountPick) { await new Promise((c, e) => { const disposables: DisposableStore = new DisposableStore(); const displayName = this.authenticationService.getDisplayName(this.userDataSyncStore!.authenticationProviderId); @@ -596,24 +689,26 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } await this.handleFirstTimeSync(); this.userDataSyncEnablementService.setEnablement(true); - this.notificationService.info(localize('sync turned on', "Sync will happen automatically from now on.")); + this.notificationService.info(localize('sync turned on', "Preferences sync is turned on")); this.storageService.store('sync.donotAskPreviewConfirmation', true, StorageScope.GLOBAL); } private getConfigureSyncQuickPickItems(): ConfigureSyncQuickPickItem[] { return [{ - id: 'settings', - label: getSyncAreaLabel(SyncSource.Settings) + id: SyncResource.Settings, + label: getSyncAreaLabel(SyncResource.Settings) }, { - id: 'keybindings', - label: getSyncAreaLabel(SyncSource.Keybindings) + id: SyncResource.Keybindings, + label: getSyncAreaLabel(SyncResource.Keybindings) }, { - id: 'extensions', - label: getSyncAreaLabel(SyncSource.Extensions) + id: SyncResource.Snippets, + label: getSyncAreaLabel(SyncResource.Snippets) }, { - id: 'globalState', - label: getSyncAreaLabel(SyncSource.GlobalState), - description: localize('ui state description', "only 'Display Language' for now") + id: SyncResource.Extensions, + label: getSyncAreaLabel(SyncResource.Extensions) + }, { + id: SyncResource.GlobalState, + label: getSyncAreaLabel(SyncResource.GlobalState), }]; } @@ -632,7 +727,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo const disposables: DisposableStore = new DisposableStore(); const quickPick = this.quickInputService.createQuickPick(); disposables.add(quickPick); - quickPick.title = localize('turn on sync', "Turn on Sync"); + quickPick.title = localize('configure sync', "Preferences Sync: Configure..."); quickPick.placeholder = localize('configure sync placeholder', "Choose what to sync"); quickPick.canSelectMany = true; quickPick.ignoreFocusOut = true; @@ -689,7 +784,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private async turnOff(): Promise { const result = await this.dialogService.confirm({ type: 'info', - message: localize('turn off sync confirmation', "Turn off Sync"), + message: localize('turn off sync confirmation', "Do you want to turn off sync?"), detail: localize('turn off sync detail', "Your settings, keybindings, extensions and UI State will no longer be synced."), primaryButton: localize('turn off', "Turn Off"), checkbox: { @@ -707,57 +802,65 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private disableSync(source?: SyncSource): void { + private disableSync(source?: SyncResource): void { if (source === undefined) { this.userDataSyncEnablementService.setEnablement(false); } else { switch (source) { - case SyncSource.Settings: return this.userDataSyncEnablementService.setResourceEnablement('settings', false); - case SyncSource.Keybindings: return this.userDataSyncEnablementService.setResourceEnablement('keybindings', false); - case SyncSource.Extensions: return this.userDataSyncEnablementService.setResourceEnablement('extensions', false); - case SyncSource.GlobalState: return this.userDataSyncEnablementService.setResourceEnablement('globalState', false); + case SyncResource.Settings: return this.userDataSyncEnablementService.setResourceEnablement(SyncResource.Settings, false); + case SyncResource.Keybindings: return this.userDataSyncEnablementService.setResourceEnablement(SyncResource.Keybindings, false); + case SyncResource.Snippets: return this.userDataSyncEnablementService.setResourceEnablement(SyncResource.Snippets, false); + case SyncResource.Extensions: return this.userDataSyncEnablementService.setResourceEnablement(SyncResource.Extensions, false); + case SyncResource.GlobalState: return this.userDataSyncEnablementService.setResourceEnablement(SyncResource.GlobalState, false); } } } private async signIn(): Promise { try { + this.loginInProgress = true; await this.setActiveAccount(await this.authenticationService.login(this.userDataSyncStore!.authenticationProviderId, ['https://management.core.windows.net/.default', 'offline_access'])); + this.loginInProgress = false; } catch (e) { this.notificationService.error(localize('loginFailed', "Logging in failed: {0}", e)); throw e; } } - private getConflictsEditorInput(source: SyncSource): IEditorInput | undefined { - const previewResource = source === SyncSource.Settings ? this.workbenchEnvironmentService.settingsSyncPreviewResource - : source === SyncSource.Keybindings ? this.workbenchEnvironmentService.keybindingsSyncPreviewResource - : null; - return previewResource ? this.editorService.editors.filter(input => input instanceof DiffEditorInput && isEqual(previewResource, input.master.resource))[0] : undefined; + private getConflictsEditorInputs(syncResource: SyncResource): DiffEditorInput[] { + return this.editorService.editors.filter(input => { + const resource = input instanceof DiffEditorInput ? input.master.resource : input.resource; + return resource && getSyncResourceFromLocalPreview(resource!, this.workbenchEnvironmentService) === syncResource; + }) as DiffEditorInput[]; } private getAllConflictsEditorInputs(): IEditorInput[] { return this.editorService.editors.filter(input => { const resource = input instanceof DiffEditorInput ? input.master.resource : input.resource; - return isEqual(resource, this.workbenchEnvironmentService.settingsSyncPreviewResource) || isEqual(resource, this.workbenchEnvironmentService.keybindingsSyncPreviewResource); + return resource && getSyncResourceFromLocalPreview(resource!, this.workbenchEnvironmentService) !== undefined; }); } - private async handleConflicts(source: SyncSource): Promise { - let previewResource: URI | undefined = undefined; - let label: string = ''; - if (source === SyncSource.Settings) { - previewResource = this.workbenchEnvironmentService.settingsSyncPreviewResource; - label = localize('settings conflicts preview', "Settings Conflicts (Remote ↔ Local)"); - } else if (source === SyncSource.Keybindings) { - previewResource = this.workbenchEnvironmentService.keybindingsSyncPreviewResource; - label = localize('keybindings conflicts preview', "Keybindings Conflicts (Remote ↔ Local)"); + private async handleSyncResourceConflicts(resource: SyncResource): Promise { + const syncResourceCoflicts = this.userDataSyncService.conflicts.filter(({ syncResource }) => syncResource === resource)[0]; + if (syncResourceCoflicts) { + this.handleConflicts(syncResourceCoflicts); } - if (previewResource) { - const remoteContentResource = toRemoteContentResource(source); + } + + private async handleConflicts({ syncResource, conflicts }: SyncResourceConflicts): Promise { + for (const conflict of conflicts) { + let label: string | undefined = undefined; + if (syncResource === SyncResource.Settings) { + label = localize('settings conflicts preview', "Settings Conflicts (Remote ↔ Local)"); + } else if (syncResource === SyncResource.Keybindings) { + label = localize('keybindings conflicts preview', "Keybindings Conflicts (Remote ↔ Local)"); + } else if (syncResource === SyncResource.Snippets) { + label = localize('snippets conflicts preview', "User Snippet Conflicts (Remote ↔ Local) - {0}", basename(conflict.local)); + } await this.editorService.openEditor({ - leftResource: remoteContentResource, - rightResource: previewResource, + leftResource: conflict.remote, + rightResource: conflict.local, label, options: { preserveFocus: false, @@ -777,6 +880,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.registerSignInAction(); this.registerShowSettingsConflictsAction(); this.registerShowKeybindingsConflictsAction(); + this.registerShowSnippetsConflictsAction(); this.registerSyncStatusAction(); this.registerTurnOffSyncAction(); @@ -800,7 +904,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo group: '5_sync', command: { id: turnOnSyncCommand.id, - title: localize('global activity turn on sync', "Turn on Sync...") + title: localize('global activity turn on sync', "Turn on Preferences Sync...") }, when: turnOnSyncWhenContext, order: 1 @@ -813,7 +917,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo group: '5_sync', command: { id: turnOnSyncCommand.id, - title: localize('global activity turn on sync', "Turn on Sync...") + title: localize('global activity turn on sync', "Turn on Preferences Sync...") }, when: turnOnSyncWhenContext, }); @@ -825,7 +929,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo constructor() { super({ id: signInCommand.id, - title: signInCommand.title, + title: localize('sign in 2', "Preferences Sync: Sign in to sync (1)"), menu: { group: '5_sync', id: MenuId.GlobalActivity, @@ -846,12 +950,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private registerShowSettingsConflictsAction(): void { const resolveSettingsConflictsWhenContext = ContextKeyExpr.regex(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*settings.*/i); - CommandsRegistry.registerCommand(resolveSettingsConflictsCommand.id, () => this.handleConflicts(SyncSource.Settings)); + CommandsRegistry.registerCommand(resolveSettingsConflictsCommand.id, () => this.handleSyncResourceConflicts(SyncResource.Settings)); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '5_sync', command: { id: resolveSettingsConflictsCommand.id, - title: localize('resolveConflicts_global', "Sync: Show Settings Conflicts (1)"), + title: localize('resolveConflicts_global', "Preferences Sync: Show Settings Conflicts (1)"), }, when: resolveSettingsConflictsWhenContext, order: 2 @@ -860,7 +964,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo group: '5_sync', command: { id: resolveSettingsConflictsCommand.id, - title: localize('resolveConflicts_global', "Sync: Show Settings Conflicts (1)"), + title: localize('resolveConflicts_global', "Preferences Sync: Show Settings Conflicts (1)"), }, when: resolveSettingsConflictsWhenContext, order: 2 @@ -873,12 +977,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private registerShowKeybindingsConflictsAction(): void { const resolveKeybindingsConflictsWhenContext = ContextKeyExpr.regex(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*keybindings.*/i); - CommandsRegistry.registerCommand(resolveKeybindingsConflictsCommand.id, () => this.handleConflicts(SyncSource.Keybindings)); + CommandsRegistry.registerCommand(resolveKeybindingsConflictsCommand.id, () => this.handleSyncResourceConflicts(SyncResource.Keybindings)); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '5_sync', command: { id: resolveKeybindingsConflictsCommand.id, - title: localize('resolveKeybindingsConflicts_global', "Sync: Show Keybindings Conflicts (1)"), + title: localize('resolveKeybindingsConflicts_global', "Preferences Sync: Show Keybindings Conflicts (1)"), }, when: resolveKeybindingsConflictsWhenContext, order: 2 @@ -887,7 +991,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo group: '5_sync', command: { id: resolveKeybindingsConflictsCommand.id, - title: localize('resolveKeybindingsConflicts_global', "Sync: Show Keybindings Conflicts (1)"), + title: localize('resolveKeybindingsConflicts_global', "Preferences Sync: Show Keybindings Conflicts (1)"), }, when: resolveKeybindingsConflictsWhenContext, order: 2 @@ -896,7 +1000,36 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo command: resolveKeybindingsConflictsCommand, when: resolveKeybindingsConflictsWhenContext, }); + } + private _snippetsConflictsActionsDisposable: DisposableStore = new DisposableStore(); + private registerShowSnippetsConflictsAction(): void { + this._snippetsConflictsActionsDisposable.clear(); + const resolveSnippetsConflictsWhenContext = ContextKeyExpr.regex(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*snippets.*/i); + const conflicts: Conflict[] | undefined = this.userDataSyncService.conflicts.filter(({ syncResource }) => syncResource === SyncResource.Snippets)[0]?.conflicts; + this._snippetsConflictsActionsDisposable.add(CommandsRegistry.registerCommand(resolveSnippetsConflictsCommand.id, () => this.handleSyncResourceConflicts(SyncResource.Snippets))); + this._snippetsConflictsActionsDisposable.add(MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { + group: '5_sync', + command: { + id: resolveSnippetsConflictsCommand.id, + title: localize('resolveSnippetsConflicts_global', "Preferences Sync: Show User Snippets Conflicts ({0})", conflicts?.length || 1), + }, + when: resolveSnippetsConflictsWhenContext, + order: 2 + })); + this._snippetsConflictsActionsDisposable.add(MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + group: '5_sync', + command: { + id: resolveSnippetsConflictsCommand.id, + title: localize('resolveSnippetsConflicts_global', "Preferences Sync: Show User Snippets Conflicts ({0})", conflicts?.length || 1), + }, + when: resolveSnippetsConflictsWhenContext, + order: 2 + })); + this._snippetsConflictsActionsDisposable.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: resolveSnippetsConflictsCommand, + when: resolveSnippetsConflictsWhenContext, + })); } private registerSyncStatusAction(): void { @@ -906,7 +1039,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo constructor() { super({ id: 'workbench.userData.actions.syncStatus', - title: localize('sync is on', "Sync is on"), + title: localize('sync is on', "Preferences Sync is On"), menu: [ { id: MenuId.GlobalActivity, @@ -931,15 +1064,18 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo const quickPick = quickInputService.createQuickPick(); disposables.add(quickPick); const items: Array = []; - if (that.userDataSyncService.conflictsSources.length) { - for (const source of that.userDataSyncService.conflictsSources) { - switch (source) { - case SyncSource.Settings: + if (that.userDataSyncService.conflicts.length) { + for (const { syncResource } of that.userDataSyncService.conflicts) { + switch (syncResource) { + case SyncResource.Settings: items.push({ id: resolveSettingsConflictsCommand.id, label: resolveSettingsConflictsCommand.title }); break; - case SyncSource.Keybindings: + case SyncResource.Keybindings: items.push({ id: resolveKeybindingsConflictsCommand.id, label: resolveKeybindingsConflictsCommand.title }); break; + case SyncResource.Snippets: + items.push({ id: resolveSnippetsConflictsCommand.id, label: resolveSnippetsConflictsCommand.title }); + break; } } items.push({ type: 'separator' }); @@ -952,8 +1088,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo quickPick.items = items; disposables.add(quickPick.onDidAccept(() => { if (quickPick.selectedItems[0] && quickPick.selectedItems[0].id) { - // Introduce timeout as workaround - #91661 #91740 - timeout(0).then(() => commandService.executeCommand(quickPick.selectedItems[0].id!)); + commandService.executeCommand(quickPick.selectedItems[0].id); } quickPick.hide(); })); @@ -1056,15 +1191,8 @@ class UserDataRemoteContentProvider implements ITextModelContentProvider { } provideTextContent(uri: URI): Promise | null { - let promise: Promise | undefined; - if (isEqual(uri, toRemoteContentResource(SyncSource.Settings))) { - promise = this.userDataSyncService.getRemoteContent(SyncSource.Settings, true); - } - if (isEqual(uri, toRemoteContentResource(SyncSource.Keybindings))) { - promise = this.userDataSyncService.getRemoteContent(SyncSource.Keybindings, true); - } - if (promise) { - return promise.then(content => this.modelService.createModel(content || '', this.modeService.create('jsonc'), uri)); + if (uri.scheme === USER_DATA_SYNC_SCHEME) { + return this.userDataSyncService.resolveContent(uri).then(content => this.modelService.createModel(content || '', this.modeService.create('jsonc'), uri)); } return null; } @@ -1083,7 +1211,6 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio constructor( private editor: ICodeEditor, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, @INotificationService private readonly notificationService: INotificationService, @IDialogService private readonly dialogService: IDialogService, @@ -1097,7 +1224,8 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio } private registerListeners(): void { - this._register(this.editor.onDidChangeModel(e => this.update())); + this._register(this.editor.onDidChangeModel(() => this.update())); + this._register(this.userDataSyncService.onDidChangeConflicts(() => this.update())); this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('diffEditor.renderSideBySide'))(() => this.update())); } @@ -1116,11 +1244,16 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio return false; // we need a model } - if (getSyncSourceFromPreviewResource(model.uri, this.environmentService) !== undefined) { + const syncResourceConflicts = this.getSyncResourceConflicts(model.uri); + if (!syncResourceConflicts) { + return false; + } + + if (syncResourceConflicts.conflicts.some(({ local }) => isEqual(local, model.uri))) { return true; } - if (getSyncSourceFromRemoteContentResource(model.uri) !== undefined) { + if (syncResourceConflicts.conflicts.some(({ remote }) => isEqual(remote, model.uri))) { return this.configurationService.getValue('diffEditor.renderSideBySide'); } @@ -1130,32 +1263,34 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio private createAcceptChangesWidgetRenderer(): void { if (!this.acceptChangesButton) { - const isRemote = getSyncSourceFromRemoteContentResource(this.editor.getModel()!.uri) !== undefined; + const resource = this.editor.getModel()!.uri; + const syncResourceConflicts = this.getSyncResourceConflicts(resource)!; + const isRemote = syncResourceConflicts.conflicts.some(({ remote }) => isEqual(remote, resource)); const acceptRemoteLabel = localize('accept remote', "Accept Remote"); const acceptLocalLabel = localize('accept local', "Accept Local"); this.acceptChangesButton = this.instantiationService.createInstance(FloatingClickWidget, this.editor, isRemote ? acceptRemoteLabel : acceptLocalLabel, null); this._register(this.acceptChangesButton.onClick(async () => { const model = this.editor.getModel(); if (model) { - const conflictsSource = (getSyncSourceFromPreviewResource(model.uri, this.environmentService) || getSyncSourceFromRemoteContentResource(model.uri))!; - this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: conflictsSource, action: isRemote ? 'acceptRemote' : 'acceptLocal' }); - const syncAreaLabel = getSyncAreaLabel(conflictsSource); + this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: syncResourceConflicts.syncResource, action: isRemote ? 'acceptRemote' : 'acceptLocal' }); + const syncAreaLabel = getSyncAreaLabel(syncResourceConflicts.syncResource); const result = await this.dialogService.confirm({ type: 'info', title: isRemote - ? localize('Sync accept remote', "Sync: {0}", acceptRemoteLabel) - : localize('Sync accept local', "Sync: {0}", acceptLocalLabel), + ? localize('Sync accept remote', "Preferences Sync: {0}", acceptRemoteLabel) + : localize('Sync accept local', "Preferences Sync: {0}", acceptLocalLabel), message: isRemote - ? localize('confirm replace and overwrite local', "Would you like to accept Remote {0} and replace Local {1}?", syncAreaLabel.toLowerCase(), syncAreaLabel.toLowerCase()) - : localize('confirm replace and overwrite remote', "Would you like to accept Local {0} and replace Remote {1}?", syncAreaLabel.toLowerCase(), syncAreaLabel.toLowerCase()), + ? localize('confirm replace and overwrite local', "Would you like to accept remote {0} and replace local {1}?", syncAreaLabel.toLowerCase(), syncAreaLabel.toLowerCase()) + : localize('confirm replace and overwrite remote', "Would you like to accept local {0} and replace remote {1}?", syncAreaLabel.toLowerCase(), syncAreaLabel.toLowerCase()), primaryButton: isRemote ? acceptRemoteLabel : acceptLocalLabel }); if (result.confirmed) { try { - await this.userDataSyncService.accept(conflictsSource, model.getValue()); + await this.userDataSyncService.acceptConflict(model.uri, model.getValue()); } catch (e) { if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.LocalPreconditionFailed) { - if (this.userDataSyncService.conflictsSources.indexOf(conflictsSource) !== -1) { + const syncResourceCoflicts = this.userDataSyncService.conflicts.filter(({ syncResource }) => syncResource === syncResourceConflicts.syncResource)[0]; + if (syncResourceCoflicts && syncResourceCoflicts.conflicts.some(conflict => isEqual(conflict.local, model.uri) || isEqual(conflict.remote, model.uri))) { this.notificationService.warn(localize('update conflicts', "Could not resolve conflicts as there is new local version available. Please try again.")); } } else { @@ -1170,6 +1305,10 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio } } + private getSyncResourceConflicts(resource: URI): SyncResourceConflicts | undefined { + return this.userDataSyncService.conflicts.filter(({ conflicts }) => conflicts.some(({ local, remote }) => isEqual(local, resource) || isEqual(remote, resource)))[0]; + } + private disposeAcceptChangesWidgetRenderer(): void { dispose(this.acceptChangesButton); this.acceptChangesButton = undefined; diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts new file mode 100644 index 00000000000..ba6877133d5 --- /dev/null +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts @@ -0,0 +1,202 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IViewsRegistry, Extensions, ITreeViewDescriptor, ITreeViewDataProvider, ITreeItem, TreeItemCollapsibleState, IViewsService, TreeViewItemHandleArg, IViewContainersRegistry, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views'; +import { localize } from 'vs/nls'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { TreeViewPane, TreeView } from 'vs/workbench/browser/parts/views/treeView'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ALL_SYNC_RESOURCES, CONTEXT_SYNC_ENABLEMENT, SyncResource, IUserDataSyncService, ISyncResourceHandle } from 'vs/platform/userDataSync/common/userDataSync'; +import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; +import { IContextKeyService, RawContextKey, ContextKeyExpr, ContextKeyEqualsExpr } from 'vs/platform/contextkey/common/contextkey'; +import { URI } from 'vs/base/common/uri'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { FolderThemeIcon } from 'vs/platform/theme/common/themeService'; +import { fromNow } from 'vs/base/common/date'; +import { pad, uppercaseFirstLetter } from 'vs/base/common/strings'; +import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; + +export class UserDataSyncViewContribution implements IWorkbenchContribution { + + constructor( + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, + ) { + const container = this.registerSyncViewContainer(); + this.registerBackupView(container, true); + this.registerBackupView(container, false); + } + + private registerSyncViewContainer(): ViewContainer { + return Registry.as(Extensions.ViewContainersRegistry).registerViewContainer( + { + id: 'workbench.view.sync', + name: localize('sync preferences', "Preferences Sync"), + ctorDescriptor: new SyncDescriptor( + ViewPaneContainer, + ['workbench.view.sync', `workbench.view.sync.state`, { mergeViewWithContainerWhenSingleView: true }] + ), + icon: 'codicon-sync', + hideIfEmpty: true, + }, ViewContainerLocation.Sidebar); + } + + private registerBackupView(container: ViewContainer, remote: boolean): void { + const id = `workbench.views.sync.${remote ? 'remote' : 'local'}BackupView`; + const name = remote ? localize('remote title', "Remote Backup") : localize('local title', "Local Backup"); + const contextKey = new RawContextKey(`showUserDataSync${remote ? 'Remote' : 'Local'}BackupView`, false); + const viewEnablementContext = contextKey.bindTo(this.contextKeyService); + const treeView = this.instantiationService.createInstance(TreeView, id, name); + treeView.showCollapseAllAction = true; + treeView.showRefreshAction = true; + const disposable = treeView.onDidChangeVisibility(visible => { + if (visible && !treeView.dataProvider) { + disposable.dispose(); + treeView.dataProvider = new UserDataSyncHistoryViewDataProvider(remote, this.userDataSyncService); + } + }); + const viewsRegistry = Registry.as(Extensions.ViewsRegistry); + viewsRegistry.registerViews([{ + id, + name, + ctorDescriptor: new SyncDescriptor(TreeViewPane), + when: ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, contextKey), + canToggleVisibility: true, + canMoveView: true, + treeView, + collapsed: false, + order: 100, + }], container); + + registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.actions.showSync${remote ? 'Remote' : 'Local'}BackupView`, + title: remote ? + { value: localize('workbench.action.showSyncRemoteBackup', "Show Remote Backup"), original: `Show Remote Backup` } + : { value: localize('workbench.action.showSyncLocalBackup', "Show Local Backup"), original: `Show Local Backup` }, + category: { value: localize('sync preferences', "Preferences Sync"), original: `Preferences Sync` }, + menu: { + id: MenuId.CommandPalette, + when: CONTEXT_SYNC_ENABLEMENT + }, + }); + } + async run(accessor: ServicesAccessor): Promise { + viewEnablementContext.set(true); + accessor.get(IViewsService).openView(id, true); + } + }); + + this.registerActions(id); + } + + private registerActions(viewId: string) { + registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.actions.sync.resolveResource`, + title: localize('workbench.actions.sync.resolveResourceRef', "Show raw JSON sync data"), + menu: { + id: MenuId.ViewItemContext, + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', viewId), ContextKeyExpr.regex('viewItem', /sync-resource-.*/i)) + }, + }); + } + async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise { + const editorService = accessor.get(IEditorService); + await editorService.openEditor({ resource: URI.parse(handle.$treeItemHandle) }); + } + }); + + registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.actions.sync.commpareWithLocal`, + title: localize('workbench.actions.sync.commpareWithLocal', "Open Changes"), + }); + } + async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise { + const editorService = accessor.get(IEditorService); + const { resource, comparableResource } = <{ resource: string, comparableResource?: string }>JSON.parse(handle.$treeItemHandle); + if (comparableResource) { + await editorService.openEditor({ + leftResource: URI.parse(resource), + rightResource: URI.parse(comparableResource), + options: { + preserveFocus: true, + revealIfVisible: true, + }, + }); + } else { + await editorService.openEditor({ resource: URI.parse(resource) }); + } + } + }); + } + +} + +interface SyncResourceTreeItem extends ITreeItem { + resource: SyncResource; + resourceHandle: ISyncResourceHandle; +} + +class UserDataSyncHistoryViewDataProvider implements ITreeViewDataProvider { + + constructor(private readonly remote: boolean, private userDataSyncService: IUserDataSyncService) { } + + async getChildren(element?: ITreeItem): Promise { + if (!element) { + return ALL_SYNC_RESOURCES.map(resourceKey => ({ + handle: resourceKey, + collapsibleState: TreeItemCollapsibleState.Collapsed, + label: { label: uppercaseFirstLetter(resourceKey) }, + themeIcon: FolderThemeIcon, + })); + } + const resourceKey = ALL_SYNC_RESOURCES.filter(key => key === element.handle)[0] as SyncResource; + if (resourceKey) { + const refHandles = this.remote ? await this.userDataSyncService.getRemoteSyncResourceHandles(resourceKey) : await this.userDataSyncService.getLocalSyncResourceHandles(resourceKey); + return refHandles.map(({ uri, created }) => { + return { + handle: uri.toString(), + collapsibleState: TreeItemCollapsibleState.Collapsed, + label: { label: label(new Date(created)) }, + description: fromNow(created, true), + resourceUri: uri, + resource: resourceKey, + resourceHandle: { uri, created }, + contextValue: `sync-resource-${resourceKey}` + }; + }); + } + if ((element).resourceHandle) { + const associatedResources = await this.userDataSyncService.getAssociatedResources((element).resource, (element).resourceHandle); + return associatedResources.map(({ resource, comparableResource }) => { + const handle = JSON.stringify({ resource: resource.toString(), comparableResource: comparableResource?.toString() }); + return { + handle, + collapsibleState: TreeItemCollapsibleState.None, + resourceUri: resource, + command: { id: `workbench.actions.sync.commpareWithLocal`, title: '', arguments: [{ $treeViewId: '', $treeItemHandle: handle }] }, + contextValue: `sync-associatedResource-${(element).resource}` + }; + }); + } + return []; + } +} + +function label(date: Date): string { + return date.toLocaleDateString() + + ' ' + pad(date.getHours(), 2) + + ':' + pad(date.getMinutes(), 2) + + ':' + pad(date.getSeconds(), 2); +} + diff --git a/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts index 1a637d676cf..60d058a8fbd 100644 --- a/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts @@ -33,7 +33,8 @@ registerAction2(class OpenSyncBackupsFolder extends Action2 { constructor() { super({ id: 'workbench.userData.actions.openSyncBackupsFolder', - title: localize('Open Backup folder', "Sync: Open Local Backups Folder"), + title: { value: localize('Open Backup folder', "Open Local Backups Folder"), original: 'Open Local Backups Folder' }, + category: { value: localize('sync preferences', "Preferences Sync"), original: `Preferences Sync` }, menu: { id: MenuId.CommandPalette, when: CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), diff --git a/src/vs/workbench/contrib/watermark/browser/watermark.ts b/src/vs/workbench/contrib/watermark/browser/watermark.ts index 9a84c9a84c1..de9c26428a0 100644 --- a/src/vs/workbench/contrib/watermark/browser/watermark.ts +++ b/src/vs/workbench/contrib/watermark/browser/watermark.ts @@ -17,16 +17,14 @@ import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/ import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { GlobalNewUntitledFileAction } from 'vs/workbench/contrib/files/browser/fileActions'; import { OpenFolderAction, OpenFileFolderAction, OpenFileAction } from 'vs/workbench/browser/actions/workspaceActions'; -import { ShowAllCommandsAction } from 'vs/workbench/contrib/quickopen/browser/commandsHandler'; +import { ShowAllCommandsAction } from 'vs/workbench/contrib/quickaccess/browser/commandsQuickAccess'; import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { StartAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { FindInFilesActionId } from 'vs/workbench/contrib/search/common/constants'; -import { QUICKOPEN_ACTION_ID } from 'vs/workbench/browser/parts/quickopen/quickopen'; import * as dom from 'vs/base/browser/dom'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IDimension } from 'vs/platform/layout/browser/layoutService'; import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { assertIsDefined } from 'vs/base/common/types'; import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; @@ -40,7 +38,7 @@ interface WatermarkEntry { } const showCommands: WatermarkEntry = { text: nls.localize('watermark.showCommands', "Show All Commands"), id: ShowAllCommandsAction.ID }; -const quickOpen: WatermarkEntry = { text: nls.localize('watermark.quickOpen', "Go to File"), id: QUICKOPEN_ACTION_ID }; +const quickAccess: WatermarkEntry = { text: nls.localize('watermark.quickAccess', "Go to File"), id: 'workbench.action.quickOpen' }; const openFileNonMacOnly: WatermarkEntry = { text: nls.localize('watermark.openFile', "Open File"), id: OpenFileAction.ID, mac: false }; const openFolderNonMacOnly: WatermarkEntry = { text: nls.localize('watermark.openFolder', "Open Folder"), id: OpenFolderAction.ID, mac: false }; const openFileOrFolderMacOnly: WatermarkEntry = { text: nls.localize('watermark.openFileFolder', "Open File or Folder"), id: OpenFileFolderAction.ID, mac: true }; @@ -62,7 +60,7 @@ const noFolderEntries = [ const folderEntries = [ showCommands, - quickOpen, + quickAccess, findInFiles, startDebugging, toggleTerminal @@ -157,7 +155,7 @@ export class WatermarkContribution extends Disposable implements IWorkbenchContr this.handleEditorPartSize(container, this.editorGroupsService.contentDimension); } - private handleEditorPartSize(container: HTMLElement, dimension: IDimension): void { + private handleEditorPartSize(container: HTMLElement, dimension: dom.IDimension): void { if (dimension.height <= 478) { dom.addClass(container, 'max-height-478px'); } else { diff --git a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts index cf25afc53a0..b7c7a60d8fc 100644 --- a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts +++ b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts @@ -3,19 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Dimension } from 'vs/base/browser/dom'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { memoize } from 'vs/base/common/decorators'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { IWebviewService, Webview, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewOptions, WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { Dimension } from 'vs/base/browser/dom'; +import { IWebviewService, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, Webview, WebviewContentOptions, WebviewElement, WebviewExtensionDescription, WebviewOptions, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; /** * Webview editor overlay that creates and destroys the underlying webview as needed. */ -export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEditorOverlay { +export class DynamicWebviewEditorOverlay extends Disposable implements WebviewOverlay { + private readonly _onDidWheel = this._register(new Emitter()); public readonly onDidWheel = this._onDidWheel.event; @@ -33,19 +35,32 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd private _owner: any = undefined; + private readonly _scopedContextKeyService = this._register(new MutableDisposable()); + private _findWidgetVisible: IContextKey; + public constructor( private readonly id: string, initialOptions: WebviewOptions, initialContentOptions: WebviewContentOptions, - @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, - @IWebviewService private readonly _webviewService: IWebviewService + @ILayoutService private readonly _layoutService: ILayoutService, + @IWebviewService private readonly _webviewService: IWebviewService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService ) { super(); this._options = initialOptions; this._contentOptions = initialContentOptions; - this._register(toDisposable(() => this.container.remove())); + this._findWidgetVisible = KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE.bindTo(_contextKeyService); + } + + private readonly _onDispose = this._register(new Emitter()); + public onDispose = this._onDispose.event; + + dispose() { + this.container.remove(); + this._onDispose.fire(); + super.dispose(); } @memoize @@ -56,7 +71,7 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd // Webviews cannot be reparented in the dom as it will destory their contents. // Mount them to a high level node to avoid this. - this._layoutService.getWorkbenchElement().appendChild(container); + this._layoutService.container.appendChild(container); return container; } @@ -93,7 +108,7 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd private show() { if (!this._webview.value) { - const webview = this._webviewService.createWebview(this.id, this._options, this._contentOptions); + const webview = this._webviewService.createWebviewElement(this.id, this._options, this._contentOptions); this._webview.value = webview; webview.state = this._state; webview.html = this._html; @@ -101,7 +116,10 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd if (this._options.tryRestoreScrollPosition) { webview.initialScrollProgress = this._initialScrollProgress; } + webview.mountTo(this.container); + this._scopedContextKeyService.value = this._contextKeyService.createScoped(this.container); + this._findWidgetVisible = KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE.bindTo(this._scopedContextKeyService.value); // Forward events from inner webview to outer listeners this._webviewEvents.clear(); @@ -188,11 +206,22 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd focus(): void { this.withWebview(webview => webview.focus()); } reload(): void { this.withWebview(webview => webview.reload()); } - showFind(): void { this.withWebview(webview => webview.showFind()); } - hideFind(): void { this.withWebview(webview => webview.hideFind()); } - runFindAction(previous: boolean): void { this.withWebview(webview => webview.runFindAction(previous)); } selectAll(): void { this.withWebview(webview => webview.selectAll()); } + showFind() { + if (this._webview.value) { + this._webview.value.showFind(); + this._findWidgetVisible.set(true); + } + } + + hideFind() { + this._findWidgetVisible.reset(); + this._webview.value?.hideFind(); + } + + runFindAction(previous: boolean): void { this.withWebview(webview => webview.runFindAction(previous)); } + public getInnerWebview() { return this._webview.value; } diff --git a/src/vs/workbench/contrib/webview/browser/pre/index.html b/src/vs/workbench/contrib/webview/browser/pre/index.html index d142be649a6..36d86f6abe3 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index.html @@ -12,7 +12,7 @@ Virtual Document - + diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index 3fed6a33da9..b378daa5a06 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -125,10 +125,11 @@ }`; /** + * @param {boolean} allowMultipleAPIAcquire * @param {*} [state] * @return {string} */ - function getVsCodeApiScript(state) { + function getVsCodeApiScript(allowMultipleAPIAcquire, state) { return ` const acquireVsCodeApi = (function() { const originalPostMessage = window.parent.postMessage.bind(window.parent); @@ -138,7 +139,7 @@ let state = ${state ? `JSON.parse(${JSON.stringify(state)})` : undefined}; return () => { - if (acquired) { + if (acquired && !${allowMultipleAPIAcquire}) { throw new Error('An instance of the VS Code API has already been acquired'); } acquired = true; @@ -325,7 +326,7 @@ if (options.allowScripts) { const defaultScript = newDocument.createElement('script'); defaultScript.id = '_vscodeApiScript'; - defaultScript.textContent = getVsCodeApiScript(data.state); + defaultScript.textContent = getVsCodeApiScript(options.allowMultipleAPIAcquire, data.state); newDocument.head.prepend(defaultScript); } diff --git a/src/vs/workbench/contrib/webview/browser/webview.ts b/src/vs/workbench/contrib/webview/browser/webview.ts index 68d53e8c4c7..dccddca8426 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.ts @@ -36,17 +36,17 @@ export interface WebviewIcons { export interface IWebviewService { _serviceBrand: undefined; - createWebview( + createWebviewElement( id: string, options: WebviewOptions, contentOptions: WebviewContentOptions, ): WebviewElement; - createWebviewEditorOverlay( + createWebviewOverlay( id: string, options: WebviewOptions, contentOptions: WebviewContentOptions, - ): WebviewEditorOverlay; + ): WebviewOverlay; setIcons(id: string, value: WebviewIcons | undefined): void; } @@ -59,6 +59,7 @@ export interface WebviewOptions { } export interface WebviewContentOptions { + readonly allowMultipleAPIAcquire?: boolean; readonly allowScripts?: boolean; readonly localResourceRoots?: ReadonlyArray; readonly portMapping?: ReadonlyArray; @@ -71,7 +72,6 @@ export interface WebviewExtensionDescription { } export interface Webview extends IDisposable { - html: string; contentOptions: WebviewContentOptions; extension: WebviewExtensionDescription | undefined; @@ -101,14 +101,22 @@ export interface Webview extends IDisposable { windowDidDragEnd(): void; } +/** + * Basic webview rendered in the dom + */ export interface WebviewElement extends Webview { mountTo(parent: HTMLElement): void; } -export interface WebviewEditorOverlay extends Webview { +/** + * Dynamically created webview drawn over another element. + */ +export interface WebviewOverlay extends Webview { readonly container: HTMLElement; options: WebviewOptions; + readonly onDispose: Event; + claim(owner: any): void; release(owner: any): void; diff --git a/src/vs/workbench/contrib/webview/browser/webviewCommands.ts b/src/vs/workbench/contrib/webview/browser/webviewCommands.ts index 66a2f7f9955..9f949d73926 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewCommands.ts @@ -8,12 +8,12 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import * as nls from 'vs/nls'; import { Action2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; +import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE } from 'vs/workbench/contrib/webview/browser/webview'; -import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor'; +import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, Webview } from 'vs/workbench/contrib/webview/browser/webview'; +import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; export class ShowWebViewEditorFindWidgetAction extends Action2 { public static readonly ID = 'editor.action.webvieweditor.showFind'; @@ -74,7 +74,7 @@ export class WebViewEditorFindNextCommand extends Action2 { } public run(accessor: ServicesAccessor): void { - getActiveWebviewEditor(accessor)?.find(false); + getActiveWebviewEditor(accessor)?.runFindAction(false); } } @@ -95,7 +95,7 @@ export class WebViewEditorFindPreviousCommand extends Action2 { } public run(accessor: ServicesAccessor): void { - getActiveWebviewEditor(accessor)?.find(true); + getActiveWebviewEditor(accessor)?.runFindAction(true); } } @@ -116,7 +116,7 @@ export class SelectAllWebviewEditorCommand extends Action2 { }); } - public run(accessor: ServicesAccessor, args: any): void { + public run(accessor: ServicesAccessor): void { getActiveWebviewEditor(accessor)?.selectAll(); } } @@ -128,27 +128,22 @@ export class ReloadWebviewAction extends Action { public constructor( id: string, label: string, - @IEditorService private readonly editorService: IEditorService + @IEditorService private readonly _editorService: IEditorService ) { super(id, label); } - public run(): Promise { - for (const webview of this.getVisibleWebviews()) { - webview.reload(); + public async run(): Promise { + for (const editor of this._editorService.visibleEditors) { + if (editor instanceof WebviewInput) { + editor.webview.reload(); + } } - return Promise.resolve(true); - } - - private getVisibleWebviews() { - return this.editorService.visibleControls - .filter(control => control && (control as WebviewEditor).isWebviewEditor) - .map(control => control as WebviewEditor); } } -export function getActiveWebviewEditor(accessor: ServicesAccessor): WebviewEditor | undefined { +export function getActiveWebviewEditor(accessor: ServicesAccessor): Webview | undefined { const editorService = accessor.get(IEditorService); - const activeControl = editorService.activeControl as WebviewEditor | undefined; - return activeControl?.isWebviewEditor ? activeControl : undefined; + const activeEditor = editorService.activeEditor; + return activeEditor instanceof WebviewInput ? activeEditor.webview : undefined; } diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts index 97eb2c50bd0..dd29d133c52 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts @@ -8,14 +8,13 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; -import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; +import { WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -25,10 +24,7 @@ export class WebviewEditor extends BaseEditor { public static readonly ID = 'WebviewEditor'; - private readonly _scopedContextKeyService = this._register(new MutableDisposable()); - private _findWidgetVisible: IContextKey; - private _editorFrame?: HTMLElement; - private _content?: HTMLElement; + private _element?: HTMLElement; private _dimension?: DOM.Dimension; private readonly _webviewVisibleDisposables = this._register(new DisposableStore()); @@ -41,59 +37,32 @@ export class WebviewEditor extends BaseEditor { @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @IStorageService storageService: IStorageService, - @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IEditorService private readonly _editorService: IEditorService, @IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService, @IHostService private readonly _hostService: IHostService, ) { super(WebviewEditor.ID, telemetryService, themeService, storageService); - - this._findWidgetVisible = KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE.bindTo(_contextKeyService); } - public get isWebviewEditor() { - return true; + private get webview(): WebviewOverlay | undefined { + return this.input instanceof WebviewInput ? this.input.webview : undefined; } protected createEditor(parent: HTMLElement): void { - this._editorFrame = parent; - this._content = document.createElement('div'); - parent.appendChild(this._content); + const element = document.createElement('div'); + this._element = element; + parent.appendChild(element); } public dispose(): void { - if (this._content) { - this._content.remove(); - this._content = undefined; + if (this._element) { + this._element.remove(); + this._element = undefined; } super.dispose(); } - public showFind() { - if (this.webview) { - this.webview.showFind(); - this._findWidgetVisible.set(true); - } - } - - public hideFind() { - this._findWidgetVisible.reset(); - this.webview?.hideFind(); - } - - public find(previous: boolean) { - this.webview?.runFindAction(previous); - } - - public selectAll() { - this.webview?.selectAll(); - } - - public reload() { - this.webview?.reload(); - } - public layout(dimension: DOM.Dimension): void { this._dimension = dimension; if (this.webview) { @@ -106,7 +75,7 @@ export class WebviewEditor extends BaseEditor { if (!this._onFocusWindowHandler.value && !isWeb) { // Make sure we restore focus when switching back to a VS Code window this._onFocusWindowHandler.value = this._hostService.onDidChangeFocus(focused => { - if (focused && this._editorService.activeControl === this) { + if (focused && this._editorService.activeEditorPane === this) { this.focus(); } }); @@ -114,10 +83,6 @@ export class WebviewEditor extends BaseEditor { this.webview?.focus(); } - public get webview(): WebviewEditorOverlay | undefined { - return this.input instanceof WebviewInput ? this.input.webview : undefined; - } - protected setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { if (this.input instanceof WebviewInput && this.webview) { if (visible) { @@ -144,12 +109,14 @@ export class WebviewEditor extends BaseEditor { return; } - if (this.webview) { + const alreadyOwnsWebview = input instanceof WebviewInput && input.webview === this.webview; + if (this.webview && !alreadyOwnsWebview) { this.webview.release(this); } await super.setInput(input, options, token); await input.resolve(); + if (token.isCancellationRequested) { return; } @@ -159,7 +126,9 @@ export class WebviewEditor extends BaseEditor { input.updateGroup(this.group.id); } - this.claimWebview(input); + if (!alreadyOwnsWebview) { + this.claimWebview(input); + } if (this._dimension) { this.layout(this._dimension); } @@ -169,13 +138,8 @@ export class WebviewEditor extends BaseEditor { private claimWebview(input: WebviewInput): void { input.webview.claim(this); - if (input.webview.options.enableFindWidget) { - this._scopedContextKeyService.value = this._contextKeyService.createScoped(input.webview.container); - this._findWidgetVisible = KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE.bindTo(this._scopedContextKeyService.value); - } - - if (this._content) { - this._content.setAttribute('aria-flowto', input.webview.container.id); + if (this._element) { + this._element.setAttribute('aria-flowto', input.webview.container.id); } this._webviewVisibleDisposables.clear(); @@ -205,13 +169,13 @@ export class WebviewEditor extends BaseEditor { this._webviewVisibleDisposables.add(this.trackFocus(input.webview)); } - private synchronizeWebviewContainerDimensions(webview: WebviewEditorOverlay, dimension?: DOM.Dimension) { - if (this._editorFrame) { - webview.layoutWebviewOverElement(this._editorFrame, dimension); + private synchronizeWebviewContainerDimensions(webview: WebviewOverlay, dimension?: DOM.Dimension) { + if (this._element) { + webview.layoutWebviewOverElement(this._element.parentElement!, dimension); } } - private trackFocus(webview: WebviewEditorOverlay): IDisposable { + private trackFocus(webview: WebviewOverlay): IDisposable { const store = new DisposableStore(); // Track focus in webview content diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts index 6999c441c59..cee6146281e 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts @@ -3,14 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter } from 'vs/base/common/event'; import { Lazy } from 'vs/base/common/lazy'; import { URI } from 'vs/base/common/uri'; -import { IEditorModel } from 'vs/platform/editor/common/editor'; -import { EditorInput, EditorModel, GroupIdentifier, IEditorInput, Verbosity } from 'vs/workbench/common/editor'; -import { IWebviewService, WebviewEditorOverlay, WebviewIcons } from 'vs/workbench/contrib/webview/browser/webview'; - -const WebviewPanelResourceScheme = 'webview-panel'; +import { EditorInput, GroupIdentifier, IEditorInput, Verbosity } from 'vs/workbench/common/editor'; +import { IWebviewService, WebviewIcons, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; +import { Schemas } from 'vs/base/common/network'; export class WebviewInput extends EditorInput { @@ -20,15 +17,13 @@ export class WebviewInput extends EditorInput { private _iconPath?: WebviewIcons; private _group?: GroupIdentifier; - private readonly _webview: Lazy; - private _didSomeoneTakeMyWebview = false; + private _webview: Lazy; - private readonly _onDisposeWebview = this._register(new Emitter()); - readonly onDisposeWebview = this._onDisposeWebview.event; + private _hasTransfered = false; get resource() { return URI.from({ - scheme: WebviewPanelResourceScheme, + scheme: Schemas.webviewPanel, path: `webview-panel/webview-${this.id}` }); } @@ -37,7 +32,7 @@ export class WebviewInput extends EditorInput { public readonly id: string, public readonly viewType: string, name: string, - webview: Lazy, + webview: Lazy, @IWebviewService private readonly _webviewService: IWebviewService, ) { super(); @@ -47,9 +42,8 @@ export class WebviewInput extends EditorInput { dispose() { if (!this.isDisposed()) { - if (!this._didSomeoneTakeMyWebview) { - this._webview?.rawValue?.dispose(); - this._onDisposeWebview.fire(); + if (!this._hasTransfered) { + this._webview.rawValue?.dispose(); } } super.dispose(); @@ -76,7 +70,7 @@ export class WebviewInput extends EditorInput { this._onDidChangeLabel.fire(); } - public get webview(): WebviewEditorOverlay { + public get webview(): WebviewOverlay { return this._webview.getValue(); } @@ -105,19 +99,20 @@ export class WebviewInput extends EditorInput { this._group = group; } - public async resolve(): Promise { - return new EditorModel(); + public async resolve(): Promise { + return null; } public supportsSplitEditor() { return false; } - protected takeOwnershipOfWebview(): WebviewEditorOverlay | undefined { - if (this._didSomeoneTakeMyWebview) { + protected transfer(other: WebviewInput): WebviewInput | undefined { + if (this._hasTransfered) { return undefined; } - this._didSomeoneTakeMyWebview = true; - return this.webview; + this._hasTransfered = true; + other._webview = this._webview; + return other; } } diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts index 0247ac9089b..4cdd70cc9ad 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts @@ -16,7 +16,7 @@ interface SerializedIconPath { dark: string | UriComponents; } -interface SerializedWebview { +export interface SerializedWebview { readonly id?: string; readonly viewType: string; readonly title: string; diff --git a/src/vs/workbench/contrib/webview/browser/webviewIconManager.ts b/src/vs/workbench/contrib/webview/browser/webviewIconManager.ts index 60be9bac8db..ee10e07a8d6 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewIconManager.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewIconManager.ts @@ -5,10 +5,9 @@ import * as dom from 'vs/base/browser/dom'; import { memoize } from 'vs/base/common/decorators'; -import { URI } from 'vs/base/common/uri'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { WebviewIcons } from 'vs/workbench/contrib/webview/browser/webview'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class WebviewIconManager { @@ -48,22 +47,20 @@ export class WebviewIconManager { private async updateStyleSheet() { await this._lifecycleService.when(LifecyclePhase.Starting); - try { - const cssRules: string[] = []; - if (this._configService.getValue('workbench.iconTheme') !== null) { - this._icons.forEach((value, key) => { - const webviewSelector = `.show-file-icons .webview-${key}-name-file-icon::before`; - if (URI.isUri(value)) { - cssRules.push(`${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value)}; }`); - } else { - cssRules.push(`.vs ${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value.light)}; }`); - cssRules.push(`.vs-dark ${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value.dark)}; }`); - } - }); + const cssRules: string[] = []; + if (this._configService.getValue('workbench.iconTheme') !== null) { + for (const [key, value] of this._icons) { + const webviewSelector = `.show-file-icons .webview-${key}-name-file-icon::before`; + try { + cssRules.push( + `.vs ${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value.light)}; }`, + `.vs-dark ${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value.dark)}; }` + ); + } catch { + // noop + } } - this._styleElement.innerHTML = cssRules.join('\n'); - } catch { - // noop } + this._styleElement.innerHTML = cssRules.join('\n'); } } diff --git a/src/vs/workbench/contrib/webview/browser/webviewService.ts b/src/vs/workbench/contrib/webview/browser/webviewService.ts index 2dd94d48621..5336e2d2750 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewService.ts @@ -5,7 +5,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IWebviewService, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewIcons, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; +import { IWebviewService, WebviewContentOptions, WebviewOverlay, WebviewElement, WebviewIcons, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; import { IFrameWebview } from 'vs/workbench/contrib/webview/browser/webviewElement'; import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/common/themeing'; import { DynamicWebviewEditorOverlay } from './dynamicWebviewEditorOverlay'; @@ -24,7 +24,7 @@ export class WebviewService implements IWebviewService { this._iconManager = this._instantiationService.createInstance(WebviewIconManager); } - createWebview( + createWebviewElement( id: string, options: WebviewOptions, contentOptions: WebviewContentOptions @@ -32,11 +32,11 @@ export class WebviewService implements IWebviewService { return this._instantiationService.createInstance(IFrameWebview, id, options, contentOptions, this._webviewThemeDataProvider); } - createWebviewEditorOverlay( + createWebviewOverlay( id: string, options: WebviewOptions, contentOptions: WebviewContentOptions, - ): WebviewEditorOverlay { + ): WebviewOverlay { return this._instantiationService.createInstance(DynamicWebviewEditorOverlay, id, options, contentOptions); } diff --git a/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts b/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts index 1c6da9e2e3e..050c32d0203 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts @@ -4,16 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import { equals } from 'vs/base/common/arrays'; +import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { memoize } from 'vs/base/common/decorators'; +import { isPromiseCanceledError } from 'vs/base/common/errors'; +import { Iterable } from 'vs/base/common/iterator'; import { Lazy } from 'vs/base/common/lazy'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { values } from 'vs/base/common/map'; import { isEqual } from 'vs/base/common/resources'; -import { EditorActivation, IEditorModel } from 'vs/platform/editor/common/editor'; +import { EditorActivation } from 'vs/platform/editor/common/editor'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { GroupIdentifier } from 'vs/workbench/common/editor'; -import { IWebviewService, WebviewContentOptions, WebviewEditorOverlay, WebviewExtensionDescription, WebviewIcons, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; +import { IWebviewService, WebviewContentOptions, WebviewExtensionDescription, WebviewIcons, WebviewOptions, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; import { WebviewInput } from './webviewEditorInput'; @@ -35,6 +38,7 @@ export function areWebviewInputOptionsEqual(a: WebviewInputOptions, b: WebviewIn return a.enableCommandUris === b.enableCommandUris && a.enableFindWidget === b.enableFindWidget && a.allowScripts === b.allowScripts + && a.allowMultipleAPIAcquire === b.allowMultipleAPIAcquire && a.retainContextWhenHidden === b.retainContextWhenHidden && a.tryRestoreScrollPosition === b.tryRestoreScrollPosition && equals(a.localResourceRoots, b.localResourceRoots, isEqual) @@ -80,7 +84,7 @@ export interface IWebviewWorkbenchService { resolveWebview( webview: WebviewInput, - ): Promise; + ): CancelablePromise; } export interface WebviewResolver { @@ -90,6 +94,7 @@ export interface WebviewResolver { resolveWebview( webview: WebviewInput, + cancellation: CancellationToken, ): Promise; } @@ -106,18 +111,46 @@ export class LazilyResolvedWebviewEditorInput extends WebviewInput { id: string, viewType: string, name: string, - webview: Lazy, + webview: Lazy, @IWebviewService webviewService: IWebviewService, @IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService, ) { super(id, viewType, name, webview, webviewService); } + #resolved = false; + #resolvePromise?: CancelablePromise; + + dispose() { + super.dispose(); + this.#resolvePromise?.cancel(); + this.#resolvePromise = undefined; + } + @memoize - public async resolve(): Promise { - await this._webviewWorkbenchService.resolveWebview(this); + public async resolve() { + if (!this.#resolved) { + this.#resolved = true; + this.#resolvePromise = this._webviewWorkbenchService.resolveWebview(this); + try { + await this.#resolvePromise; + } catch (e) { + if (!isPromiseCanceledError(e)) { + throw e; + } + } + } return super.resolve(); } + + protected transfer(other: LazilyResolvedWebviewEditorInput): WebviewInput | undefined { + if (!super.transfer(other)) { + return; + } + + other.#resolved = this.#resolved; + return other; + } } @@ -128,12 +161,12 @@ class RevivalPool { this._awaitingRevival.push({ input, resolve }); } - public reviveFor(reviver: WebviewResolver) { + public reviveFor(reviver: WebviewResolver, cancellation: CancellationToken) { const toRevive = this._awaitingRevival.filter(({ input }) => canRevive(reviver, input)); this._awaitingRevival = this._awaitingRevival.filter(({ input }) => !canRevive(reviver, input)); for (const { input, resolve } of toRevive) { - reviver.resolveWebview(input).then(resolve); + reviver.resolveWebview(input, cancellation).then(resolve); } } } @@ -160,7 +193,7 @@ export class WebviewEditorService implements IWebviewWorkbenchService { options: WebviewInputOptions, extension: WebviewExtensionDescription | undefined, ): WebviewInput { - const webview = new Lazy(() => this.createWebiew(id, extension, options)); + const webview = new Lazy(() => this.createWebviewElement(id, extension, options)); const webviewInput = this._instantiationService.createInstance(WebviewInput, id, viewType, title, webview); this._editorService.openEditor(webviewInput, { pinned: true, @@ -203,7 +236,7 @@ export class WebviewEditorService implements IWebviewWorkbenchService { group: number | undefined, ): WebviewInput { const webview = new Lazy(() => { - const webview = this.createWebiew(id, extension, options); + const webview = this.createWebviewElement(id, extension, options); webview.state = state; return webview; }); @@ -221,17 +254,20 @@ export class WebviewEditorService implements IWebviewWorkbenchService { reviver: WebviewResolver ): IDisposable { this._revivers.add(reviver); - this._revivalPool.reviveFor(reviver); + + const cts = new CancellationTokenSource(); + this._revivalPool.reviveFor(reviver, cts.token); return toDisposable(() => { this._revivers.delete(reviver); + cts.dispose(true); }); } public shouldPersist( webview: WebviewInput ): boolean { - if (values(this._revivers).some(reviver => canRevive(reviver, webview))) { + if (Iterable.some(this._revivers.values(), reviver => canRevive(reviver, webview))) { return true; } @@ -241,32 +277,39 @@ export class WebviewEditorService implements IWebviewWorkbenchService { } private async tryRevive( - webview: WebviewInput + webview: WebviewInput, + cancellation: CancellationToken, ): Promise { - for (const reviver of values(this._revivers)) { + for (const reviver of this._revivers.values()) { if (canRevive(reviver, webview)) { - await reviver.resolveWebview(webview); + await reviver.resolveWebview(webview, cancellation); return true; } } return false; } - public async resolveWebview( + public resolveWebview( webview: WebviewInput, - ): Promise { - const didRevive = await this.tryRevive(webview); - if (!didRevive) { - // A reviver may not be registered yet. Put into pool and resolve promise when we can revive - let resolve: () => void; - const promise = new Promise(r => { resolve = r; }); - this._revivalPool.add(webview, resolve!); - return promise; - } + ): CancelablePromise { + return createCancelablePromise(async (cancellation) => { + const didRevive = await this.tryRevive(webview, cancellation); + if (!didRevive) { + // A reviver may not be registered yet. Put into pool and resolve promise when we can revive + let resolve: () => void; + const promise = new Promise(r => { resolve = r; }); + this._revivalPool.add(webview, resolve!); + return promise; + } + }); } - private createWebiew(id: string, extension: WebviewExtensionDescription | undefined, options: WebviewInputOptions) { - const webview = this._webviewService.createWebviewEditorOverlay(id, { + private createWebviewElement( + id: string, + extension: WebviewExtensionDescription | undefined, + options: WebviewInputOptions + ) { + const webview = this._webviewService.createWebviewOverlay(id, { enableFindWidget: options.enableFindWidget, retainContextWhenHidden: options.retainContextWhenHidden }, options); diff --git a/src/vs/workbench/contrib/webview/common/resourceLoader.ts b/src/vs/workbench/contrib/webview/common/resourceLoader.ts index 68d4c606728..d3ced130584 100644 --- a/src/vs/workbench/contrib/webview/common/resourceLoader.ts +++ b/src/vs/workbench/contrib/webview/common/resourceLoader.ts @@ -5,7 +5,6 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { sep } from 'vs/base/common/path'; -import { startsWith, endsWith } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; @@ -96,7 +95,7 @@ function normalizeRequestPath(requestUri: URI) { } function containsResource(root: URI, resource: URI): boolean { - let rootPath = root.fsPath + (endsWith(root.fsPath, sep) ? '' : sep); + let rootPath = root.fsPath + (root.fsPath.endsWith(sep) ? '' : sep); let resourceFsPath = resource.fsPath; if (isUNC(root.fsPath) && isUNC(resource.fsPath)) { @@ -104,5 +103,5 @@ function containsResource(root: URI, resource: URI): boolean { resourceFsPath = resourceFsPath.toLowerCase(); } - return startsWith(resourceFsPath, rootPath); + return resourceFsPath.startsWith(rootPath); } diff --git a/src/vs/workbench/contrib/webview/common/themeing.ts b/src/vs/workbench/contrib/webview/common/themeing.ts index 8ee85c8aa55..cd8e73fafc4 100644 --- a/src/vs/workbench/contrib/webview/common/themeing.ts +++ b/src/vs/workbench/contrib/webview/common/themeing.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; -import { DARK, ITheme, IThemeService, LIGHT } from 'vs/platform/theme/common/themeService'; +import { DARK, IColorTheme, IThemeService, LIGHT } from 'vs/platform/theme/common/themeService'; import { Emitter } from 'vs/base/common/event'; interface WebviewThemeData { @@ -30,7 +30,7 @@ export class WebviewThemeDataProvider extends Disposable { ) { super(); - this._register(this._themeService.onThemeChange(() => { + this._register(this._themeService.onDidColorThemeChange(() => { this.reset(); })); @@ -42,8 +42,8 @@ export class WebviewThemeDataProvider extends Disposable { })); } - public getTheme(): ITheme { - return this._themeService.getTheme(); + public getTheme(): IColorTheme { + return this._themeService.getColorTheme(); } @WebviewThemeDataProvider.MEMOIZER @@ -53,7 +53,7 @@ export class WebviewThemeDataProvider extends Disposable { const editorFontWeight = configuration.fontWeight || EDITOR_FONT_DEFAULTS.fontWeight; const editorFontSize = configuration.fontSize || EDITOR_FONT_DEFAULTS.fontSize; - const theme = this._themeService.getTheme(); + const theme = this._themeService.getColorTheme(); const exportedColors = colorRegistry.getColorRegistry().getColors().reduce((colors, entry) => { const color = theme.getColor(entry.id); if (color) { @@ -89,7 +89,7 @@ enum ApiThemeClassName { } namespace ApiThemeClassName { - export function fromTheme(theme: ITheme): ApiThemeClassName { + export function fromTheme(theme: IColorTheme): ApiThemeClassName { switch (theme.type) { case LIGHT: return ApiThemeClassName.light; case DARK: return ApiThemeClassName.dark; diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts index 43c0e653b7f..83f2a34044a 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts @@ -12,7 +12,7 @@ import { Action2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { WebviewEditorOverlay, webviewHasOwnEditFunctionsContextKey } from 'vs/workbench/contrib/webview/browser/webview'; +import { WebviewOverlay, webviewHasOwnEditFunctionsContextKey } from 'vs/workbench/contrib/webview/browser/webview'; import { getActiveWebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewCommands'; import { ElectronWebviewBasedWebview } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; @@ -25,7 +25,7 @@ export class OpenWebviewDeveloperToolsAction extends Action { super(id, label); } - public run(): Promise { + public async run(): Promise { const elements = document.querySelectorAll('webview.ready'); for (let i = 0; i < elements.length; i++) { try { @@ -34,7 +34,7 @@ export class OpenWebviewDeveloperToolsAction extends Action { console.error(e); } } - return Promise.resolve(true); + return true; } } @@ -42,7 +42,7 @@ export class CopyWebviewEditorCommand extends Action2 { public static readonly ID = 'editor.action.webvieweditor.copy'; public static readonly LABEL = nls.localize('editor.action.webvieweditor.copy', "Copy2"); - constructor(contextKeyExpr: ContextKeyExpression) { + constructor(contextKeyExpr: ContextKeyExpression, readonly getActiveElectronBasedWebviewDelegate: (accessor: ServicesAccessor) => ElectronWebviewBasedWebview | undefined = getActiveElectronBasedWebview) { super({ id: CopyWebviewEditorCommand.ID, title: CopyWebviewEditorCommand.LABEL, @@ -55,7 +55,7 @@ export class CopyWebviewEditorCommand extends Action2 { } public run(accessor: ServicesAccessor): void { - getActiveWebviewBasedWebview(accessor)?.copy(); + this.getActiveElectronBasedWebviewDelegate(accessor)?.copy(); } } @@ -63,7 +63,7 @@ export class PasteWebviewEditorCommand extends Action2 { public static readonly ID = 'editor.action.webvieweditor.paste'; public static readonly LABEL = nls.localize('editor.action.webvieweditor.paste', 'Paste'); - constructor(contextKeyExpr: ContextKeyExpression) { + constructor(contextKeyExpr: ContextKeyExpression, readonly getActiveElectronBasedWebviewDelegate: (accessor: ServicesAccessor) => ElectronWebviewBasedWebview | undefined = getActiveElectronBasedWebview) { super({ id: PasteWebviewEditorCommand.ID, title: PasteWebviewEditorCommand.LABEL, @@ -76,7 +76,7 @@ export class PasteWebviewEditorCommand extends Action2 { } public run(accessor: ServicesAccessor): void { - getActiveWebviewBasedWebview(accessor)?.paste(); + this.getActiveElectronBasedWebviewDelegate(accessor)?.paste(); } } @@ -84,7 +84,7 @@ export class CutWebviewEditorCommand extends Action2 { public static readonly ID = 'editor.action.webvieweditor.cut'; public static readonly LABEL = nls.localize('editor.action.webvieweditor.cut', 'Cut'); - constructor(contextKeyExpr: ContextKeyExpression) { + constructor(contextKeyExpr: ContextKeyExpression, readonly getActiveElectronBasedWebviewDelegate: (accessor: ServicesAccessor) => ElectronWebviewBasedWebview | undefined = getActiveElectronBasedWebview) { super({ id: CutWebviewEditorCommand.ID, title: CutWebviewEditorCommand.LABEL, @@ -97,7 +97,7 @@ export class CutWebviewEditorCommand extends Action2 { } public run(accessor: ServicesAccessor): void { - getActiveWebviewBasedWebview(accessor)?.cut(); + this.getActiveElectronBasedWebviewDelegate(accessor)?.cut(); } } @@ -105,7 +105,7 @@ export class UndoWebviewEditorCommand extends Action2 { public static readonly ID = 'editor.action.webvieweditor.undo'; public static readonly LABEL = nls.localize('editor.action.webvieweditor.undo', "Undo"); - constructor(contextKeyExpr: ContextKeyExpression) { + constructor(contextKeyExpr: ContextKeyExpression, readonly getActiveElectronBasedWebviewDelegate: (accessor: ServicesAccessor) => ElectronWebviewBasedWebview | undefined = getActiveElectronBasedWebview) { super({ id: UndoWebviewEditorCommand.ID, title: UndoWebviewEditorCommand.LABEL, @@ -117,8 +117,8 @@ export class UndoWebviewEditorCommand extends Action2 { }); } - public run(accessor: ServicesAccessor, args: any): void { - getActiveWebviewBasedWebview(accessor)?.undo(); + public run(accessor: ServicesAccessor): void { + this.getActiveElectronBasedWebviewDelegate(accessor)?.undo(); } } @@ -126,7 +126,7 @@ export class RedoWebviewEditorCommand extends Action2 { public static readonly ID = 'editor.action.webvieweditor.redo'; public static readonly LABEL = nls.localize('editor.action.webvieweditor.redo', "Redo"); - constructor(contextKeyExpr: ContextKeyExpression) { + constructor(contextKeyExpr: ContextKeyExpression, readonly getActiveElectronBasedWebviewDelegate: (accessor: ServicesAccessor) => ElectronWebviewBasedWebview | undefined = getActiveElectronBasedWebview) { super({ id: RedoWebviewEditorCommand.ID, title: RedoWebviewEditorCommand.LABEL, @@ -140,21 +140,21 @@ export class RedoWebviewEditorCommand extends Action2 { }); } - public run(accessor: ServicesAccessor, args: any): void { - getActiveWebviewBasedWebview(accessor)?.redo(); + public run(accessor: ServicesAccessor): void { + this.getActiveElectronBasedWebviewDelegate(accessor)?.redo(); } } -function getActiveWebviewBasedWebview(accessor: ServicesAccessor): ElectronWebviewBasedWebview | undefined { - const webview = getActiveWebviewEditor(accessor)?.webview; +function getActiveElectronBasedWebview(accessor: ServicesAccessor): ElectronWebviewBasedWebview | undefined { + const webview = getActiveWebviewEditor(accessor); if (!webview) { return undefined; } if (webview instanceof ElectronWebviewBasedWebview) { return webview; - } else if ((webview as WebviewEditorOverlay).getInnerWebview) { - const innerWebview = (webview as WebviewEditorOverlay).getInnerWebview(); + } else if ((webview as WebviewOverlay).getInnerWebview) { + const innerWebview = (webview as WebviewOverlay).getInnerWebview(); if (innerWebview instanceof ElectronWebviewBasedWebview) { return innerWebview; } diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index 00aa8576189..d09a02b6c2e 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -113,23 +113,33 @@ class WebviewSession extends Disposable { } class WebviewProtocolProvider extends Disposable { + + private _resolve!: () => void; + private _reject!: () => void; + + public readonly ready: Promise; + constructor( handle: WebviewTagHandle, - private readonly _getExtensionLocation: () => URI | undefined, - private readonly _getLocalResourceRoots: () => ReadonlyArray, - private readonly _fileService: IFileService, + getExtensionLocation: () => URI | undefined, + getLocalResourceRoots: () => ReadonlyArray, + fileService: IFileService, ) { super(); - this._register(handle.onFirstLoad(contents => { - this.registerProtocols(contents); - })); - } + this.ready = new Promise((resolve, reject) => { + this._resolve = resolve; + this._reject = reject; + }); - private registerProtocols(contents: WebContents) { - registerFileProtocol(contents, WebviewResourceScheme, this._fileService, this._getExtensionLocation(), () => - this._getLocalResourceRoots() - ); + this._register(handle.onFirstLoad(contents => { + try { + registerFileProtocol(contents, WebviewResourceScheme, fileService, getExtensionLocation(), getLocalResourceRoots); + this._resolve(); + } catch { + this._reject(); + } + })); } } @@ -204,6 +214,9 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme private _findStarted: boolean = false; public extension: WebviewExtensionDescription | undefined; + private readonly _protocolProvider: WebviewProtocolProvider; + + private readonly _domReady: Promise; constructor( id: string, @@ -222,11 +235,11 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme const webviewAndContents = this._register(new WebviewTagHandle(this.element!)); const session = this._register(new WebviewSession(webviewAndContents)); - this._register(new WebviewProtocolProvider( - webviewAndContents, - () => this.extension ? this.extension.location : undefined, + this._protocolProvider = new WebviewProtocolProvider(webviewAndContents, + () => this.extension?.location, () => (this.content.options.localResourceRoots || []), - fileService)); + fileService); + this._register(this._protocolProvider); this._register(new WebviewPortMappingProvider( session, @@ -237,6 +250,13 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme this._register(new WebviewKeyboardHandler(webviewAndContents)); + this._domReady = new Promise(resolve => { + const subscription = this._register(this.on(WebviewMessageChannels.webviewReady, () => { + subscription.dispose(); + resolve(); + })); + }); + this._register(addDisposableListener(this.element!, 'console-message', function (e: { level: number; message: string; line: number; sourceId: string; }) { console.log(`[Embedded Page] ${e.message}`); })); @@ -304,7 +324,7 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme element.style.outline = '0'; element.preload = require.toUrl('./pre/electron-index.js'); - element.src = 'data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%20lang%3D%22en%22%20style%3D%22width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3Chead%3E%0D%0A%09%3Ctitle%3EVirtual%20Document%3C%2Ftitle%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%20style%3D%22margin%3A%200%3B%20overflow%3A%20hidden%3B%20width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3C%2Fbody%3E%0D%0A%3C%2Fhtml%3E'; + element.src = 'data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%20lang%3D%22en%22%20style%3D%22width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3Chead%3E%0D%0A%3Ctitle%3EVirtual%20Document%3C%2Ftitle%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%20style%3D%22margin%3A%200%3B%20overflow%3A%20hidden%3B%20width%3A%20100%25%3B%20height%3A%20100%25%22%20role%3D%22document%22%3E%0D%0A%3C%2Fbody%3E%0D%0A%3C%2Fhtml%3E'; return element; } @@ -322,7 +342,11 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme parent.appendChild(this.element); } - protected postMessage(channel: string, data?: any): void { + protected async postMessage(channel: string, data?: any): Promise { + await Promise.all([ + this._protocolProvider.ready, + this._domReady, + ]); this.element?.send(channel, data); } diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts index af5e1726b2d..8e5ea202c5a 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts @@ -6,7 +6,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { DynamicWebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay'; -import { IWebviewService, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewIcons, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; +import { IWebviewService, WebviewContentOptions, WebviewOverlay, WebviewElement, WebviewIcons, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; import { IFrameWebview } from 'vs/workbench/contrib/webview/browser/webviewElement'; import { WebviewIconManager } from 'vs/workbench/contrib/webview/browser/webviewIconManager'; import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/common/themeing'; @@ -26,7 +26,7 @@ export class ElectronWebviewService implements IWebviewService { this._iconManager = this._instantiationService.createInstance(WebviewIconManager); } - createWebview( + createWebviewElement( id: string, options: WebviewOptions, contentOptions: WebviewContentOptions @@ -39,11 +39,11 @@ export class ElectronWebviewService implements IWebviewService { } } - createWebviewEditorOverlay( + createWebviewOverlay( id: string, options: WebviewOptions, contentOptions: WebviewContentOptions, - ): WebviewEditorOverlay { + ): WebviewOverlay { return this._instantiationService.createInstance(DynamicWebviewEditorOverlay, id, options, contentOptions); } diff --git a/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts b/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts index e7e50346aa5..3ee82a7b2c2 100644 --- a/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts +++ b/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts @@ -7,10 +7,9 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { ViewsWelcomeExtensionPoint, ViewWelcome, viewsWelcomeExtensionPointDescriptor, ViewIdentifierMap } from './viewsWelcomeExtensionPoint'; +import { ViewsWelcomeExtensionPoint, ViewWelcome, ViewIdentifierMap } from './viewsWelcomeExtensionPoint'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as ViewContainerExtensions, IViewsRegistry, ViewContentPriority } from 'vs/workbench/common/views'; -import { localize } from 'vs/nls'; const viewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); @@ -23,11 +22,6 @@ export class ViewsWelcomeContribution extends Disposable implements IWorkbenchCo extensionPoint.setHandler((_, { added, removed }) => { for (const contribution of removed) { - // Proposed API check - if (!contribution.description.enableProposedApi) { - continue; - } - for (const welcome of contribution.value) { const disposable = this.viewWelcomeContents.get(welcome); @@ -38,12 +32,6 @@ export class ViewsWelcomeContribution extends Disposable implements IWorkbenchCo } for (const contribution of added) { - // Proposed API check - if (!contribution.description.enableProposedApi) { - contribution.collector.error(localize('proposedAPI.invalid', "The '{0}' contribution is a proposed API and is only available when running out of dev or with the following command line switch: --enable-proposed-api {1}", viewsWelcomeExtensionPointDescriptor.extensionPoint, contribution.description.identifier.value)); - continue; - } - for (const welcome of contribution.value) { const id = ViewIdentifierMap[welcome.view] ?? welcome.view; const disposable = viewsRegistry.registerViewWelcomeContent(id, { diff --git a/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts b/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts index 9a1fdbe078e..68d5c4a6e64 100644 --- a/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts +++ b/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts @@ -22,7 +22,7 @@ export type ViewsWelcomeExtensionPoint = ViewWelcome[]; export const ViewIdentifierMap: { [key: string]: string } = { 'explorer': 'workbench.explorer.emptyView', - 'debug': 'workbench.debug.startView', + 'debug': 'workbench.debug.welcome', 'scm': 'workbench.scm', }; @@ -38,9 +38,17 @@ const viewsWelcomeExtensionPointSchema = Object.freeze `
-
+

${escape(localize('welcomePage.vscode', "Visual Studio Code"))}

${escape(localize({ key: 'welcomePage.editingEvolved', comment: ['Shown as subtitle on the Welcome page.'] }, "Editing evolved"))}

diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough.ts index 252b682d8b8..6f7b6c61db3 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough.ts @@ -174,7 +174,7 @@ let easy = true; easy = 42; ||| ->**Tip:** You can also enable the checks workspace or application wide by adding |"javascript.implicitProjectConfig.checkJs": true| to your workspace or user settings and explicitly ignoring files or lines using |// @ts-nocheck| and |// @ts-ignore|. Check out the docs on [JavaScript in VS Code](https://code.visualstudio.com/docs/languages/javascript) to learn more. +>**Tip:** You can also enable the checks workspace or application wide by adding |"javascript.implicitProjectConfig.checkJs": true| to your workspace or user settings and explicitly ignoring files or lines using |// @ts-nocheck| and |// @ts-expect-error|. Check out the docs on [JavaScript in VS Code](https://code.visualstudio.com/docs/languages/javascript) to learn more. ## Thanks! diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughActions.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughActions.ts index cd4caf83cb7..cf4da0d6a33 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughActions.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughActions.ts @@ -17,9 +17,9 @@ export const WalkThroughArrowUp: ICommandAndKeybindingRule = { primary: KeyCode.UpArrow, handler: accessor => { const editorService = accessor.get(IEditorService); - const activeControl = editorService.activeControl; - if (activeControl instanceof WalkThroughPart) { - activeControl.arrowUp(); + const activeEditorPane = editorService.activeEditorPane; + if (activeEditorPane instanceof WalkThroughPart) { + activeEditorPane.arrowUp(); } } }; @@ -31,9 +31,9 @@ export const WalkThroughArrowDown: ICommandAndKeybindingRule = { primary: KeyCode.DownArrow, handler: accessor => { const editorService = accessor.get(IEditorService); - const activeControl = editorService.activeControl; - if (activeControl instanceof WalkThroughPart) { - activeControl.arrowDown(); + const activeEditorPane = editorService.activeEditorPane; + if (activeEditorPane instanceof WalkThroughPart) { + activeEditorPane.arrowDown(); } } }; @@ -45,9 +45,9 @@ export const WalkThroughPageUp: ICommandAndKeybindingRule = { primary: KeyCode.PageUp, handler: accessor => { const editorService = accessor.get(IEditorService); - const activeControl = editorService.activeControl; - if (activeControl instanceof WalkThroughPart) { - activeControl.pageUp(); + const activeEditorPane = editorService.activeEditorPane; + if (activeEditorPane instanceof WalkThroughPart) { + activeEditorPane.pageUp(); } } }; @@ -59,9 +59,9 @@ export const WalkThroughPageDown: ICommandAndKeybindingRule = { primary: KeyCode.PageDown, handler: accessor => { const editorService = accessor.get(IEditorService); - const activeControl = editorService.activeControl; - if (activeControl instanceof WalkThroughPart) { - activeControl.pageDown(); + const activeEditorPane = editorService.activeEditorPane; + if (activeEditorPane instanceof WalkThroughPart) { + activeEditorPane.pageDown(); } } -}; \ No newline at end of file +}; diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts index 1803459b953..7cc9d291293 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts @@ -426,7 +426,7 @@ export class WalkThroughPart extends BaseEditor { alwaysConsumeMouseWheel: false }, overviewRulerLanes: 3, - fixedOverflowWidgets: true, + fixedOverflowWidgets: false, lineNumbersMinChars: 1, minimap: { enabled: false }, }; diff --git a/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils.ts b/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils.ts index f82792295f1..3d1616d55ad 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ITheme } from 'vs/platform/theme/common/themeService'; +import { IColorTheme } from 'vs/platform/theme/common/themeService'; import { editorBackground, ColorDefaults, ColorValue } from 'vs/platform/theme/common/colorRegistry'; -export function getExtraColor(theme: ITheme, colorId: string, defaults: ColorDefaults & { extra_dark: string }): ColorValue | null { +export function getExtraColor(theme: IColorTheme, colorId: string, defaults: ColorDefaults & { extra_dark: string }): ColorValue | null { const color = theme.getColor(colorId); if (color) { return color; @@ -20,4 +20,4 @@ export function getExtraColor(theme: ITheme, colorId: string, defaults: ColorDef } return defaults[theme.type]; -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/parts/editor/media/editorpicker.css b/src/vs/workbench/electron-browser/actions/media/actions.css similarity index 61% rename from src/vs/workbench/browser/parts/editor/media/editorpicker.css rename to src/vs/workbench/electron-browser/actions/media/actions.css index 49b6cd9921e..82096e85995 100644 --- a/src/vs/workbench/browser/parts/editor/media/editorpicker.css +++ b/src/vs/workbench/electron-browser/actions/media/actions.css @@ -3,6 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .monaco-quick-open-widget .quick-open-tree .quick-open-entry.editor-preview { - font-style: italic; -} \ No newline at end of file +.quick-input-list .quick-input-list-entry.has-actions:hover .quick-input-list-entry-action-bar .action-label.dirty-window::before { + content: "\ea76"; /* Close icon flips between black dot and "X" for dirty windows */ +} diff --git a/src/vs/workbench/electron-browser/actions/windowActions.ts b/src/vs/workbench/electron-browser/actions/windowActions.ts index 9bc18a02043..e8526d7ef95 100644 --- a/src/vs/workbench/electron-browser/actions/windowActions.ts +++ b/src/vs/workbench/electron-browser/actions/windowActions.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./media/actions'; + import { URI } from 'vs/base/common/uri'; import { Action } from 'vs/base/common/actions'; import * as nls from 'vs/nls'; @@ -18,7 +20,8 @@ import { ICommandHandler } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IElectronService } from 'vs/platform/electron/node/electron'; -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export class CloseCurrentWindowAction extends Action { @@ -33,10 +36,8 @@ export class CloseCurrentWindowAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.electronService.closeWindow(); - - return Promise.resolve(true); } } @@ -90,10 +91,8 @@ export class ZoomInAction extends BaseZoomAction { super(id, label, configurationService); } - run(): Promise { + async run(): Promise { this.setConfiguredZoomLevel(webFrame.getZoomLevel() + 1); - - return Promise.resolve(true); } } @@ -110,10 +109,8 @@ export class ZoomOutAction extends BaseZoomAction { super(id, label, configurationService); } - run(): Promise { + async run(): Promise { this.setConfiguredZoomLevel(webFrame.getZoomLevel() - 1); - - return Promise.resolve(true); } } @@ -130,10 +127,8 @@ export class ZoomResetAction extends BaseZoomAction { super(id, label, configurationService); } - run(): Promise { + async run(): Promise { this.setConfiguredZoomLevel(0); - - return Promise.resolve(true); } } @@ -159,15 +154,21 @@ export class ReloadWindowWithExtensionsDisabledAction extends Action { export abstract class BaseSwitchWindow extends Action { - private closeWindowAction: IQuickInputButton = { - iconClass: 'action-remove-from-recently-opened', + private readonly closeWindowAction: IQuickInputButton = { + iconClass: 'codicon-close', tooltip: nls.localize('close', "Close Window") }; + private readonly closeDirtyWindowAction: IQuickInputButton = { + iconClass: 'dirty-window codicon-circle-filled', + tooltip: nls.localize('close', "Close Window"), + alwaysVisible: true + }; + constructor( id: string, label: string, - private electronEnvironmentService: IElectronEnvironmentService, + private environmentService: INativeWorkbenchEnvironmentService, private quickInputService: IQuickInputService, private keybindingService: IKeybindingService, private modelService: IModelService, @@ -180,7 +181,7 @@ export abstract class BaseSwitchWindow extends Action { protected abstract isQuickNavigate(): boolean; async run(): Promise { - const currentWindowId = this.electronEnvironmentService.windowId; + const currentWindowId = this.environmentService.configuration.windowId; const windows = await this.electronService.getWindows(); const placeHolder = nls.localize('switchWindowPlaceHolder', "Select a window to switch to"); @@ -190,9 +191,10 @@ export abstract class BaseSwitchWindow extends Action { return { payload: win.id, label: win.title, + ariaLabel: win.dirty ? nls.localize('windowDirtyAriaLabel', "{0}, dirty window", win.title) : win.title, iconClasses: getIconClasses(this.modelService, this.modeService, resource, fileKind), description: (currentWindowId === win.id) ? nls.localize('current', "Current Window") : undefined, - buttons: (!this.isQuickNavigate() && currentWindowId !== win.id) ? [this.closeWindowAction] : undefined + buttons: currentWindowId !== win.id ? win.dirty ? [this.closeDirtyWindowAction] : [this.closeWindowAction] : undefined }; }); const autoFocusIndex = (picks.indexOf(picks.filter(pick => pick.payload === currentWindowId)[0]) + 1) % picks.length; @@ -203,7 +205,7 @@ export abstract class BaseSwitchWindow extends Action { placeHolder, quickNavigate: this.isQuickNavigate() ? { keybindings: this.keybindingService.lookupKeybindings(this.id) } : undefined, onDidTriggerItemButton: async context => { - await this.electronService.closeWindow(); + await this.electronService.closeWindowById(context.item.payload); context.removeItem(); } }); @@ -222,14 +224,14 @@ export class SwitchWindow extends BaseSwitchWindow { constructor( id: string, label: string, - @IElectronEnvironmentService electronEnvironmentService: IElectronEnvironmentService, + @IWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, @IQuickInputService quickInputService: IQuickInputService, @IKeybindingService keybindingService: IKeybindingService, @IModelService modelService: IModelService, @IModeService modeService: IModeService, @IElectronService electronService: IElectronService ) { - super(id, label, electronEnvironmentService, quickInputService, keybindingService, modelService, modeService, electronService); + super(id, label, environmentService, quickInputService, keybindingService, modelService, modeService, electronService); } protected isQuickNavigate(): boolean { @@ -245,14 +247,14 @@ export class QuickSwitchWindow extends BaseSwitchWindow { constructor( id: string, label: string, - @IElectronEnvironmentService electronEnvironmentService: IElectronEnvironmentService, + @IWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, @IQuickInputService quickInputService: IQuickInputService, @IKeybindingService keybindingService: IKeybindingService, @IModelService modelService: IModelService, @IModeService modeService: IModeService, @IElectronService electronService: IElectronService ) { - super(id, label, electronEnvironmentService, quickInputService, keybindingService, modelService, modeService, electronService); + super(id, label, environmentService, quickInputService, keybindingService, modelService, modeService, electronService); } protected isQuickNavigate(): boolean { diff --git a/src/vs/workbench/electron-browser/desktop.contribution.ts b/src/vs/workbench/electron-browser/desktop.contribution.ts index bc750183d6e..9b594104e66 100644 --- a/src/vs/workbench/electron-browser/desktop.contribution.ts +++ b/src/vs/workbench/electron-browser/desktop.contribution.ts @@ -17,7 +17,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IsMacContext, HasMacNativeTabsContext, IsDevelopmentContext } from 'vs/workbench/browser/contextkeys'; +import { IsDevelopmentContext, IsMacContext } from 'vs/platform/contextkey/common/contextkeys'; import { NoEditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench/common/editor'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; @@ -82,7 +82,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command, - when: HasMacNativeTabsContext + when: ContextKeyExpr.equals('config.window.nativeTabs', 'true') }); }); } diff --git a/src/vs/workbench/electron-browser/desktop.main.ts b/src/vs/workbench/electron-browser/desktop.main.ts index e9da83b049d..800f1f03096 100644 --- a/src/vs/workbench/electron-browser/desktop.main.ts +++ b/src/vs/workbench/electron-browser/desktop.main.ts @@ -5,23 +5,20 @@ import * as fs from 'fs'; import * as gracefulFs from 'graceful-fs'; -import { createHash } from 'crypto'; +import { webFrame } from 'electron'; import { importEntries, mark } from 'vs/base/common/performance'; import { Workbench } from 'vs/workbench/browser/workbench'; -import { ElectronWindow } from 'vs/workbench/electron-browser/window'; +import { NativeWindow } from 'vs/workbench/electron-browser/window'; import { setZoomLevel, setZoomFactor, setFullscreen } from 'vs/base/browser/browser'; import { domContentLoaded, addDisposableListener, EventType, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { stat } from 'vs/base/node/pfs'; import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; -import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; -import { webFrame } from 'electron'; +import { INativeWindowConfiguration } from 'vs/platform/windows/node/window'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ConsoleLogService, MultiplexLogService, ILogService, ConsoleLogInMainService } from 'vs/platform/log/common/log'; import { NativeStorageService } from 'vs/platform/storage/node/storageService'; @@ -51,16 +48,17 @@ import { FileUserDataProvider } from 'vs/workbench/services/userData/common/file import { basename } from 'vs/base/common/resources'; import { IProductService } from 'vs/platform/product/common/productService'; import product from 'vs/platform/product/common/product'; -import { ElectronEnvironmentService, IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; +import { NativeResourceIdentityService } from 'vs/platform/resource/node/resourceIdentityServiceImpl'; +import { IResourceIdentityService } from 'vs/platform/resource/common/resourceIdentityService'; class DesktopMain extends Disposable { private readonly environmentService: NativeWorkbenchEnvironmentService; - constructor(private configuration: IWindowConfiguration) { + constructor(private configuration: INativeWindowConfiguration) { super(); - this.environmentService = new NativeWorkbenchEnvironmentService(configuration, configuration.execPath, configuration.windowId); + this.environmentService = new NativeWorkbenchEnvironmentService(configuration, configuration.execPath); this.init(); } @@ -127,7 +125,7 @@ class DesktopMain extends Disposable { const instantiationService = workbench.startup(); // Window - this._register(instantiationService.createInstance(ElectronWindow)); + this._register(instantiationService.createInstance(NativeWindow)); // Driver if (this.environmentService.configuration.driver) { @@ -180,11 +178,6 @@ class DesktopMain extends Disposable { // Environment serviceCollection.set(IWorkbenchEnvironmentService, this.environmentService); - serviceCollection.set(IElectronEnvironmentService, new ElectronEnvironmentService( - this.configuration.windowId, - this.environmentService.sharedIPCHandle, - this.environmentService - )); // Product serviceCollection.set(IProductService, { _serviceBrand: undefined, ...product }); @@ -220,7 +213,10 @@ class DesktopMain extends Disposable { fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider); } - const payload = await this.resolveWorkspaceInitializationPayload(); + const resourceIdentityService = this._register(new NativeResourceIdentityService()); + serviceCollection.set(IResourceIdentityService, resourceIdentityService); + + const payload = await this.resolveWorkspaceInitializationPayload(resourceIdentityService); const services = await Promise.all([ this.createWorkspaceService(payload, fileService, remoteAgentService, logService).then(service => { @@ -246,7 +242,7 @@ class DesktopMain extends Disposable { return { serviceCollection, logService, storageService: services[1] }; } - private async resolveWorkspaceInitializationPayload(): Promise { + private async resolveWorkspaceInitializationPayload(resourceIdentityService: IResourceIdentityService): Promise { // Multi-root workspace if (this.environmentService.configuration.workspace) { @@ -256,7 +252,7 @@ class DesktopMain extends Disposable { // Single-folder workspace let workspaceInitializationPayload: IWorkspaceInitializationPayload | undefined; if (this.environmentService.configuration.folderUri) { - workspaceInitializationPayload = await this.resolveSingleFolderWorkspaceInitializationPayload(this.environmentService.configuration.folderUri); + workspaceInitializationPayload = await this.resolveSingleFolderWorkspaceInitializationPayload(this.environmentService.configuration.folderUri, resourceIdentityService); } // Fallback to empty workspace if we have no payload yet. @@ -276,46 +272,16 @@ class DesktopMain extends Disposable { return workspaceInitializationPayload; } - private async resolveSingleFolderWorkspaceInitializationPayload(folderUri: ISingleFolderWorkspaceIdentifier): Promise { - - // Return early the folder is not local - if (folderUri.scheme !== Schemas.file) { - return { id: createHash('md5').update(folderUri.toString()).digest('hex'), folder: folderUri }; - } - - function computeLocalDiskFolderId(folder: URI, stat: fs.Stats): string { - let ctime: number | undefined; - if (isLinux) { - ctime = stat.ino; // Linux: birthtime is ctime, so we cannot use it! We use the ino instead! - } else if (isMacintosh) { - ctime = stat.birthtime.getTime(); // macOS: birthtime is fine to use as is - } else if (isWindows) { - if (typeof stat.birthtimeMs === 'number') { - ctime = Math.floor(stat.birthtimeMs); // Windows: fix precision issue in node.js 8.x to get 7.x results (see https://github.com/nodejs/node/issues/19897) - } else { - ctime = stat.birthtime.getTime(); - } - } - - // we use the ctime as extra salt to the ID so that we catch the case of a folder getting - // deleted and recreated. in that case we do not want to carry over previous state - return createHash('md5').update(folder.fsPath).update(ctime ? String(ctime) : '').digest('hex'); - } - - // For local: ensure path is absolute and exists + private async resolveSingleFolderWorkspaceInitializationPayload(folderUri: ISingleFolderWorkspaceIdentifier, resourceIdentityService: IResourceIdentityService): Promise { try { - const sanitizedFolderPath = sanitizeFilePath(folderUri.fsPath, process.env['VSCODE_CWD'] || process.cwd()); - const fileStat = await stat(sanitizedFolderPath); - - const sanitizedFolderUri = URI.file(sanitizedFolderPath); - return { - id: computeLocalDiskFolderId(sanitizedFolderUri, fileStat), - folder: sanitizedFolderUri - }; + const folder = folderUri.scheme === Schemas.file + ? URI.file(sanitizeFilePath(folderUri.fsPath, process.env['VSCODE_CWD'] || process.cwd())) // For local: ensure path is absolute + : folderUri; + const id = await resourceIdentityService.resolveResourceIdentity(folderUri); + return { id, folder }; } catch (error) { onUnexpectedError(error); } - return; } @@ -373,7 +339,7 @@ class DesktopMain extends Disposable { } } -export function main(configuration: IWindowConfiguration): Promise { +export function main(configuration: INativeWindowConfiguration): Promise { const renderer = new DesktopMain(configuration); return renderer.open(); diff --git a/src/vs/workbench/electron-browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-browser/parts/titlebar/titlebarPart.ts new file mode 100644 index 00000000000..05d911cc4b6 --- /dev/null +++ b/src/vs/workbench/electron-browser/parts/titlebar/titlebarPart.ts @@ -0,0 +1,242 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as browser from 'vs/base/browser/browser'; +import * as DOM from 'vs/base/browser/dom'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { isMacintosh, isWindows, isLinux } from 'vs/base/common/platform'; +import { IMenuService } from 'vs/platform/actions/common/actions'; +import { TitlebarPart as BrowserTitleBarPart } from 'vs/workbench/browser/parts/titlebar/titlebarPart'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { getTitleBarStyle } from 'vs/platform/windows/common/windows'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; + +export class TitlebarPart extends BrowserTitleBarPart { + private appIcon: HTMLElement | undefined; + private windowControls: HTMLElement | undefined; + private maxRestoreControl: HTMLElement | undefined; + private dragRegion: HTMLElement | undefined; + private resizer: HTMLElement | undefined; + + constructor( + @IContextMenuService contextMenuService: IContextMenuService, + @IConfigurationService protected readonly configurationService: IConfigurationService, + @IEditorService editorService: IEditorService, + @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IInstantiationService instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @ILabelService labelService: ILabelService, + @IStorageService storageService: IStorageService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @IMenuService menuService: IMenuService, + @IContextKeyService contextKeyService: IContextKeyService, + @IHostService hostService: IHostService, + @IProductService productService: IProductService, + @IElectronService private readonly electronService: IElectronService + ) { + super(contextMenuService, configurationService, editorService, environmentService, contextService, instantiationService, themeService, labelService, storageService, layoutService, menuService, contextKeyService, hostService, productService); + } + + private onUpdateAppIconDragBehavior() { + const setting = this.configurationService.getValue('window.doubleClickIconToClose'); + if (setting && this.appIcon) { + (this.appIcon.style as any)['-webkit-app-region'] = 'no-drag'; + } else if (this.appIcon) { + (this.appIcon.style as any)['-webkit-app-region'] = 'drag'; + } + } + + private onDidChangeMaximized(maximized: boolean) { + if (this.maxRestoreControl) { + if (maximized) { + DOM.removeClass(this.maxRestoreControl, 'codicon-chrome-maximize'); + DOM.addClass(this.maxRestoreControl, 'codicon-chrome-restore'); + } else { + DOM.removeClass(this.maxRestoreControl, 'codicon-chrome-restore'); + DOM.addClass(this.maxRestoreControl, 'codicon-chrome-maximize'); + } + } + + if (this.resizer) { + if (maximized) { + DOM.hide(this.resizer); + } else { + DOM.show(this.resizer); + } + } + + this.adjustTitleMarginToCenter(); + } + + private onMenubarFocusChanged(focused: boolean) { + if ((isWindows || isLinux) && this.currentMenubarVisibility !== 'compact' && this.dragRegion) { + if (focused) { + DOM.hide(this.dragRegion); + } else { + DOM.show(this.dragRegion); + } + } + } + + protected onMenubarVisibilityChanged(visible: boolean) { + // Hide title when toggling menu bar + if ((isWindows || isLinux) && this.currentMenubarVisibility === 'toggle' && visible) { + // Hack to fix issue #52522 with layered webkit-app-region elements appearing under cursor + if (this.dragRegion) { + DOM.hide(this.dragRegion); + setTimeout(() => DOM.show(this.dragRegion!), 50); + } + } + + super.onMenubarVisibilityChanged(visible); + } + + protected onConfigurationChanged(event: IConfigurationChangeEvent): void { + + super.onConfigurationChanged(event); + + if (event.affectsConfiguration('window.doubleClickIconToClose')) { + if (this.appIcon) { + this.onUpdateAppIconDragBehavior(); + } + } + } + + protected adjustTitleMarginToCenter(): void { + if (this.customMenubar && this.menubar) { + const leftMarker = (this.appIcon ? this.appIcon.clientWidth : 0) + this.menubar.clientWidth + 10; + const rightMarker = this.element.clientWidth - (this.windowControls ? this.windowControls.clientWidth : 0) - 10; + + // Not enough space to center the titlebar within window, + // Center between menu and window controls + if (leftMarker > (this.element.clientWidth - this.title.clientWidth) / 2 || + rightMarker < (this.element.clientWidth + this.title.clientWidth) / 2) { + this.title.style.position = ''; + this.title.style.left = ''; + this.title.style.transform = ''; + return; + } + } + + this.title.style.position = 'absolute'; + this.title.style.left = '50%'; + this.title.style.transform = 'translate(-50%, 0)'; + } + + protected installMenubar(): void { + super.installMenubar(); + + if (this.menubar) { + return; + } + + if (this.customMenubar) { + this._register(this.customMenubar.onFocusStateChange(e => this.onMenubarFocusChanged(e))); + } + } + + createContentArea(parent: HTMLElement): HTMLElement { + const ret = super.createContentArea(parent); + + // App Icon (Native Windows/Linux) + if (!isMacintosh) { + this.appIcon = DOM.prepend(this.element, DOM.$('div.window-appicon')); + this.onUpdateAppIconDragBehavior(); + + this._register(DOM.addDisposableListener(this.appIcon, DOM.EventType.DBLCLICK, (e => { + this.electronService.closeWindow(); + }))); + } + + // Draggable region that we can manipulate for #52522 + this.dragRegion = DOM.prepend(this.element, DOM.$('div.titlebar-drag-region')); + + // Window Controls (Native Windows/Linux) + if (!isMacintosh) { + this.windowControls = DOM.append(this.element, DOM.$('div.window-controls-container')); + + // Minimize + const minimizeIcon = DOM.append(this.windowControls, DOM.$('div.window-icon.window-minimize.codicon.codicon-chrome-minimize')); + this._register(DOM.addDisposableListener(minimizeIcon, DOM.EventType.CLICK, e => { + this.electronService.minimizeWindow(); + })); + + // Restore + this.maxRestoreControl = DOM.append(this.windowControls, DOM.$('div.window-icon.window-max-restore.codicon')); + this._register(DOM.addDisposableListener(this.maxRestoreControl, DOM.EventType.CLICK, async e => { + const maximized = await this.electronService.isMaximized(); + if (maximized) { + return this.electronService.unmaximizeWindow(); + } + + return this.electronService.maximizeWindow(); + })); + + // Close + const closeIcon = DOM.append(this.windowControls, DOM.$('div.window-icon.window-close.codicon.codicon-chrome-close')); + this._register(DOM.addDisposableListener(closeIcon, DOM.EventType.CLICK, e => { + this.electronService.closeWindow(); + })); + + // Resizer + this.resizer = DOM.append(this.element, DOM.$('div.resizer')); + + this._register(this.layoutService.onMaximizeChange(maximized => this.onDidChangeMaximized(maximized))); + this.onDidChangeMaximized(this.layoutService.isWindowMaximized()); + } + + return ret; + } + + updateLayout(dimension: DOM.Dimension): void { + this.lastLayoutDimensions = dimension; + + if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { + // Only prevent zooming behavior on macOS or when the menubar is not visible + if (isMacintosh || this.currentMenubarVisibility === 'hidden') { + this.title.style.zoom = `${1 / browser.getZoomFactor()}`; + if (isWindows || isLinux) { + if (this.appIcon) { + this.appIcon.style.zoom = `${1 / browser.getZoomFactor()}`; + } + + if (this.windowControls) { + this.windowControls.style.zoom = `${1 / browser.getZoomFactor()}`; + } + } + } else { + this.title.style.zoom = ''; + if (isWindows || isLinux) { + if (this.appIcon) { + this.appIcon.style.zoom = ''; + } + + if (this.windowControls) { + this.windowControls.style.zoom = ''; + } + } + } + + DOM.runAtThisOrScheduleAtNextAnimationFrame(() => this.adjustTitleMarginToCenter()); + + if (this.customMenubar) { + const menubarDimension = new DOM.Dimension(0, dimension.height); + this.customMenubar.layout(menubarDimension); + } + } + } +} diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index e1c129c76f1..69b36a77e20 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -6,20 +6,20 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import * as errors from 'vs/base/common/errors'; -import { equals, deepClone, assign } from 'vs/base/common/objects'; +import { equals, deepClone } from 'vs/base/common/objects'; import * as DOM from 'vs/base/browser/dom'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAction } from 'vs/base/common/actions'; import { IFileService } from 'vs/platform/files/common/files'; -import { toResource, IUntitledTextResourceInput, SideBySideEditor, pathsToEditors } from 'vs/workbench/common/editor'; -import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { toResource, IUntitledTextResourceEditorInput, SideBySideEditor, pathsToEditors } from 'vs/workbench/common/editor'; +import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService'; +import { ITelemetryService, crashReporterIdStorageKey } from 'vs/platform/telemetry/common/telemetry'; import { IWindowSettings, IOpenFileRequest, IWindowsConfiguration, IAddFoldersRequest, IRunActionInWindowRequest, IRunKeybindingInWindowRequest, getTitleBarStyle } from 'vs/platform/windows/common/windows'; import { ITitleService } from 'vs/workbench/services/title/common/titleService'; import { IWorkbenchThemeService, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService'; import * as browser from 'vs/base/browser/browser'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; import { ipcRenderer as ipc, webFrame, crashReporter, CrashReporterStartOptions, Event as IpcEvent } from 'electron'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; @@ -46,7 +46,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { MenubarControl } from '../browser/parts/titlebar/menubarControl'; import { ILabelService } from 'vs/platform/label/common/label'; import { IUpdateService } from 'vs/platform/update/common/update'; -import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IPreferencesService } from '../services/preferences/common/preferences'; import { IMenubarService, IMenubarData, IMenubarMenu, IMenubarKeybinding, IMenubarMenuItemSubmenu, IMenubarMenuItemAction, MenubarMenuItem } from 'vs/platform/menubar/node/menubar'; import { withNullAsUndefined, assertIsDefined } from 'vs/base/common/types'; @@ -58,12 +58,13 @@ import { getBaseLabel } from 'vs/base/common/labels'; import { ITunnelService, extractLocalHostUriMetaDataForPortMapping } from 'vs/platform/remote/common/tunnel'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; import { IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { Event } from 'vs/base/common/event'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; +import { clearAllFontInfos } from 'vs/editor/browser/config/configuration'; -export class ElectronWindow extends Disposable { +export class NativeWindow extends Disposable { private touchBarMenu: IMenu | undefined; private readonly touchBarDisposables = this._register(new DisposableStore()); @@ -94,7 +95,7 @@ export class ElectronWindow extends Disposable { @IMenuService private readonly menuService: IMenuService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @IIntegrityService private readonly integrityService: IIntegrityService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService, @IAccessibilityService private readonly accessibilityService: IAccessibilityService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -102,9 +103,9 @@ export class ElectronWindow extends Disposable { @IElectronService private readonly electronService: IElectronService, @ITunnelService private readonly tunnelService: ITunnelService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, - @IElectronEnvironmentService private readonly electronEnvironmentService: IElectronEnvironmentService, @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, - @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, + @IStorageService private readonly storageService: IStorageService, ) { super(); @@ -180,6 +181,10 @@ export class ElectronWindow extends Disposable { this.notificationService.info(message); }); + ipc.on('vscode:displayChanged', (event: IpcEvent) => { + clearAllFontInfos(); + }); + // Fullscreen Events ipc.on('vscode:enterFullScreen', async () => { await this.lifecycleService.when(LifecyclePhase.Ready); @@ -246,10 +251,10 @@ export class ElectronWindow extends Disposable { const file = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.file }); // Represented Filename - this.updateRepresentedFilename(file ? file.fsPath : undefined); + this.updateRepresentedFilename(file?.fsPath); // Custom title menu - this.provideCustomTitleContextMenu(file ? file.fsPath : undefined); + this.provideCustomTitleContextMenu(file?.fsPath); })); } @@ -264,24 +269,22 @@ export class ElectronWindow extends Disposable { })); } - // Document edited (macOS only): indicate for dirty working copies - if (isMacintosh) { - this._register(this.workingCopyService.onDidChangeDirty(workingCopy => { - const gotDirty = workingCopy.isDirty(); - if (gotDirty && !(workingCopy.capabilities & WorkingCopyCapabilities.Untitled) && this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) { - return; // do not indicate dirty of working copies that are auto saved after short delay - } + // Document edited: indicate for dirty working copies + this._register(this.workingCopyService.onDidChangeDirty(workingCopy => { + const gotDirty = workingCopy.isDirty(); + if (gotDirty && !(workingCopy.capabilities & WorkingCopyCapabilities.Untitled) && this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) { + return; // do not indicate dirty of working copies that are auto saved after short delay + } - this.updateDocumentEdited(gotDirty); - })); + this.updateDocumentEdited(gotDirty); + })); - this.updateDocumentEdited(); - } + this.updateDocumentEdited(); // Detect minimize / maximize this._register(Event.any( - Event.map(Event.filter(this.electronService.onWindowMaximize, id => id === this.electronEnvironmentService.windowId), () => true), - Event.map(Event.filter(this.electronService.onWindowUnmaximize, id => id === this.electronEnvironmentService.windowId), () => false) + Event.map(Event.filter(this.electronService.onWindowMaximize, id => id === this.environmentService.configuration.windowId), () => true), + Event.map(Event.filter(this.electronService.onWindowUnmaximize, id => id === this.environmentService.configuration.windowId), () => false) )(e => this.onDidChangeMaximized(e))); this.onDidChangeMaximized(this.environmentService.configuration.maximized ?? false); @@ -304,8 +307,8 @@ export class ElectronWindow extends Disposable { // Close when empty: check if we should close the window based on the setting // Overruled by: window has a workspace opened or this window is for extension development // or setting is disabled. Also enabled when running with --wait from the command line. - const visibleEditors = this.editorService.visibleControls; - if (visibleEditors.length === 0 && this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && !this.environmentService.isExtensionDevelopment) { + const visibleEditorPanes = this.editorService.visibleEditorPanes; + if (visibleEditorPanes.length === 0 && this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && !this.environmentService.isExtensionDevelopment) { const closeWhenEmpty = this.configurationService.getValue('window.closeWhenEmpty'); if (closeWhenEmpty || this.environmentService.args.wait) { this.closeEmptyWindowScheduler.schedule(); @@ -314,8 +317,8 @@ export class ElectronWindow extends Disposable { } private onAllEditorsClosed(): void { - const visibleEditors = this.editorService.visibleControls.length; - if (visibleEditors === 0) { + const visibleEditorPanes = this.editorService.visibleEditorPanes.length; + if (visibleEditorPanes === 0) { this.electronService.closeWindow(); } } @@ -395,7 +398,7 @@ export class ElectronWindow extends Disposable { this.setupOpenHandlers(); // Emit event when vscode is ready - this.lifecycleService.when(LifecyclePhase.Ready).then(() => ipc.send('vscode:workbenchReady', this.electronEnvironmentService.windowId)); + this.lifecycleService.when(LifecyclePhase.Ready).then(() => ipc.send('vscode:workbenchReady', this.environmentService.configuration.windowId)); // Integrity warning this.integrityService.isPure().then(res => this.titleService.updateProperties({ isPure: res.isPure })); @@ -422,8 +425,8 @@ export class ElectronWindow extends Disposable { this.updateTouchbarMenu(); // Crash reporter (if enabled) - if (!this.environmentService.disableCrashReporter && product.crashReporter && product.hockeyApp && this.configurationService.getValue('telemetry.enableCrashReporter')) { - this.setupCrashReporter(product.crashReporter.companyName, product.crashReporter.productName, product.hockeyApp); + if (!this.environmentService.disableCrashReporter && product.crashReporter && product.appCenter && this.configurationService.getValue('telemetry.enableCrashReporter')) { + this.setupCrashReporter(product.crashReporter.companyName, product.crashReporter.productName, product.appCenter); } } @@ -536,31 +539,36 @@ export class ElectronWindow extends Disposable { } } - private async setupCrashReporter(companyName: string, productName: string, hockeyAppConfig: typeof product.hockeyApp): Promise { - if (!hockeyAppConfig) { + private async setupCrashReporter(companyName: string, productName: string, appCenterConfig: typeof product.appCenter): Promise { + if (!appCenterConfig) { return; } + const appCenterURL = isWindows ? appCenterConfig[process.arch === 'ia32' ? 'win32-ia32' : 'win32-x64'] + : isLinux ? appCenterConfig[`linux-x64`] : appCenterConfig.darwin; + const info = await this.telemetryService.getTelemetryInfo(); + const crashReporterId = this.storageService.get(crashReporterIdStorageKey, StorageScope.GLOBAL)!; + // base options with product info const options: CrashReporterStartOptions = { companyName, productName, - submitURL: isWindows ? hockeyAppConfig[process.arch === 'ia32' ? 'win32-ia32' : 'win32-x64'] : isLinux ? hockeyAppConfig[`linux-x64`] : hockeyAppConfig.darwin, + submitURL: appCenterURL.concat('&uid=', crashReporterId, '&iid=', crashReporterId, '&sid=', info.sessionId), extra: { vscode_version: product.version, vscode_commit: product.commit || '' } }; - // mixin telemetry info - const info = await this.telemetryService.getTelemetryInfo(); - assign(options.extra, { vscode_sessionId: info.sessionId }); + // start crash reporter in the main process first. + // On windows crashpad excepts a name pipe for the client to connect, + // this pipe is created by crash reporter initialization from the main process, + // changing this order of initialization will cause issues. + // For more info: https://chromium.googlesource.com/crashpad/crashpad/+/HEAD/doc/overview_design.md#normal-registration + await this.electronService.startCrashReporter(options); // start crash reporter right here crashReporter.start(deepClone(options)); - - // start crash reporter in the main process - return this.electronService.startCrashReporter(options); } private onAddFoldersRequest(request: IAddFoldersRequest): void { @@ -587,7 +595,7 @@ export class ElectronWindow extends Disposable { } private async onOpenFiles(request: IOpenFileRequest): Promise { - const inputs: IResourceEditor[] = []; + const inputs: IResourceEditorInputType[] = []; const diffMode = !!(request.filesToDiff && (request.filesToDiff.length === 2)); if (!diffMode && request.filesToOpenOrCreate) { @@ -667,7 +675,7 @@ export class ElectronWindow extends Disposable { }); } - private async openResources(resources: Array, diffMode: boolean): Promise { + private async openResources(resources: Array, diffMode: boolean): Promise { await this.lifecycleService.when(LifecyclePhase.Ready); // In diffMode we open 2 resources as diff @@ -697,11 +705,10 @@ class NativeMenubarControl extends MenubarControl { @IStorageService storageService: IStorageService, @INotificationService notificationService: INotificationService, @IPreferencesService preferencesService: IPreferencesService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService protected readonly environmentService: INativeWorkbenchEnvironmentService, @IAccessibilityService accessibilityService: IAccessibilityService, @IMenubarService private readonly menubarService: IMenubarService, @IHostService hostService: IHostService, - @IElectronEnvironmentService private readonly electronEnvironmentService: IElectronEnvironmentService ) { super( menuService, @@ -750,7 +757,7 @@ class NativeMenubarControl extends MenubarControl { // Send menus to main process to be rendered by Electron const menubarData = { menus: {}, keybindings: {} }; if (this.getMenubarMenus(menubarData)) { - this.menubarService.updateMenubar(this.electronEnvironmentService.windowId, menubarData); + this.menubarService.updateMenubar(this.environmentService.configuration.windowId, menubarData); } } diff --git a/src/vs/workbench/services/accessibility/node/accessibilityService.ts b/src/vs/workbench/services/accessibility/electron-browser/accessibilityService.ts similarity index 91% rename from src/vs/workbench/services/accessibility/node/accessibilityService.ts rename to src/vs/workbench/services/accessibility/electron-browser/accessibilityService.ts index 38d831833b1..19d44947f6b 100644 --- a/src/vs/workbench/services/accessibility/node/accessibilityService.ts +++ b/src/vs/workbench/services/accessibility/electron-browser/accessibilityService.ts @@ -16,6 +16,7 @@ import { IJSONEditingService } from 'vs/workbench/services/configuration/common/ import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; interface AccessibilityMetrics { enabled: boolean; @@ -24,14 +25,14 @@ type AccessibilityMetricsClassification = { enabled: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; }; -export class NodeAccessibilityService extends AccessibilityService implements IAccessibilityService { +export class NativeAccessibilityService extends AccessibilityService implements IAccessibilityService { _serviceBrand: undefined; private didSendTelemetry = false; constructor( - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, @IContextKeyService contextKeyService: IContextKeyService, @IConfigurationService configurationService: IConfigurationService, @ITelemetryService private readonly _telemetryService: ITelemetryService @@ -69,7 +70,7 @@ export class NodeAccessibilityService extends AccessibilityService implements IA } } -registerSingleton(IAccessibilityService, NodeAccessibilityService, true); +registerSingleton(IAccessibilityService, NativeAccessibilityService, true); // On linux we do not automatically detect that a screen reader is detected, thus we have to implicitly notify the renderer to enable accessibility when user configures it in settings class LinuxAccessibilityContribution implements IWorkbenchContribution { diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index ef3552ed42f..81c61d8f718 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -3,12 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { AuthenticationSession } from 'vs/editor/common/modes'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { AuthenticationSession, AuthenticationSessionsChangeEvent } from 'vs/editor/common/modes'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { MainThreadAuthenticationProvider } from 'vs/workbench/api/browser/mainThreadAuthentication'; +import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; export const IAuthenticationService = createDecorator('IAuthenticationService'); @@ -17,12 +19,12 @@ export interface IAuthenticationService { registerAuthenticationProvider(id: string, provider: MainThreadAuthenticationProvider): void; unregisterAuthenticationProvider(id: string): void; - sessionsUpdate(providerId: string): void; + sessionsUpdate(providerId: string, event: AuthenticationSessionsChangeEvent): void; readonly onDidRegisterAuthenticationProvider: Event; readonly onDidUnregisterAuthenticationProvider: Event; - readonly onDidChangeSessions: Event; + readonly onDidChangeSessions: Event<{ providerId: string, event: AuthenticationSessionsChangeEvent }>; getSessions(providerId: string): Promise | undefined>; getDisplayName(providerId: string): string; login(providerId: string, scopes: string[]): Promise; @@ -31,6 +33,7 @@ export interface IAuthenticationService { export class AuthenticationService extends Disposable implements IAuthenticationService { _serviceBrand: undefined; + private _placeholderMenuItem: IDisposable | undefined; private _authenticationProviders: Map = new Map(); @@ -40,25 +43,53 @@ export class AuthenticationService extends Disposable implements IAuthentication private _onDidUnregisterAuthenticationProvider: Emitter = this._register(new Emitter()); readonly onDidUnregisterAuthenticationProvider: Event = this._onDidUnregisterAuthenticationProvider.event; - private _onDidChangeSessions: Emitter = this._register(new Emitter()); - readonly onDidChangeSessions: Event = this._onDidChangeSessions.event; + private _onDidChangeSessions: Emitter<{ providerId: string, event: AuthenticationSessionsChangeEvent }> = this._register(new Emitter<{ providerId: string, event: AuthenticationSessionsChangeEvent }>()); + readonly onDidChangeSessions: Event<{ providerId: string, event: AuthenticationSessionsChangeEvent }> = this._onDidChangeSessions.event; constructor() { super(); + this._placeholderMenuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, { + command: { + id: 'noAuthenticationProviders', + title: nls.localize('loading', "Loading...") + }, + }); } registerAuthenticationProvider(id: string, authenticationProvider: MainThreadAuthenticationProvider): void { this._authenticationProviders.set(id, authenticationProvider); this._onDidRegisterAuthenticationProvider.fire(id); + + if (authenticationProvider.dependents.length && this._placeholderMenuItem) { + this._placeholderMenuItem.dispose(); + this._placeholderMenuItem = undefined; + } } unregisterAuthenticationProvider(id: string): void { - this._authenticationProviders.delete(id); - this._onDidUnregisterAuthenticationProvider.fire(id); + const provider = this._authenticationProviders.get(id); + if (provider) { + provider.dispose(); + this._authenticationProviders.delete(id); + this._onDidUnregisterAuthenticationProvider.fire(id); + } + + if (!this._authenticationProviders.size) { + this._placeholderMenuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, { + command: { + id: 'noAuthenticationProviders', + title: nls.localize('loading', "Loading...") + }, + }); + } } - sessionsUpdate(id: string): void { - this._onDidChangeSessions.fire(id); + sessionsUpdate(id: string, event: AuthenticationSessionsChangeEvent): void { + this._onDidChangeSessions.fire({ providerId: id, event: event }); + const provider = this._authenticationProviders.get(id); + if (provider) { + provider.updateSessionItems(event); + } } getDisplayName(id: string): string { diff --git a/src/vs/workbench/services/backup/common/backup.ts b/src/vs/workbench/services/backup/common/backup.ts index 66750a668b8..087ccd70e7f 100644 --- a/src/vs/workbench/services/backup/common/backup.ts +++ b/src/vs/workbench/services/backup/common/backup.ts @@ -10,8 +10,8 @@ import { ITextBufferFactory, ITextSnapshot } from 'vs/editor/common/model'; export const IBackupFileService = createDecorator('backupFileService'); export interface IResolvedBackup { - value: ITextBufferFactory; - meta?: T; + readonly value: ITextBufferFactory; + readonly meta?: T; } /** @@ -63,9 +63,14 @@ export interface IBackupFileService { backup(resource: URI, content?: ITextSnapshot, versionId?: number, meta?: T): Promise; /** - * Discards the backup associated with a resource if it exists.. + * Discards the backup associated with a resource if it exists. * * @param resource The resource whose backup is being discarded discard to back up. */ discardBackup(resource: URI): Promise; + + /** + * Discards all backups. + */ + discardBackups(): Promise; } diff --git a/src/vs/workbench/services/backup/common/backupFileService.ts b/src/vs/workbench/services/backup/common/backupFileService.ts index 0b5cf5a1d8a..1058dd89b8c 100644 --- a/src/vs/workbench/services/backup/common/backupFileService.ts +++ b/src/vs/workbench/services/backup/common/backupFileService.ts @@ -14,12 +14,13 @@ import { IResolvedBackup, IBackupFileService } from 'vs/workbench/services/backu import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { ITextSnapshot } from 'vs/editor/common/model'; import { createTextBufferFactoryFromStream, createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; -import { keys, ResourceMap } from 'vs/base/common/map'; +import { ResourceMap } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { VSBuffer } from 'vs/base/common/buffer'; import { TextSnapshotReadable, stringToSnapshot } from 'vs/workbench/services/textfile/common/textfiles'; import { Disposable } from 'vs/base/common/lifecycle'; +import { ILogService } from 'vs/platform/log/common/log'; export interface IBackupFilesModel { resolve(backupRoot: URI): Promise; @@ -114,7 +115,8 @@ export class BackupFileService implements IBackupFileService { constructor( @IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService, - @IFileService protected fileService: IFileService + @IFileService protected fileService: IFileService, + @ILogService private readonly logService: ILogService ) { this.impl = this.initialize(); } @@ -128,7 +130,7 @@ export class BackupFileService implements IBackupFileService { private initialize(): BackupFileServiceImpl | InMemoryBackupFileService { const backupWorkspaceResource = this.environmentService.configuration.backupWorkspaceResource; if (backupWorkspaceResource) { - return new BackupFileServiceImpl(backupWorkspaceResource, this.hashPath, this.fileService); + return new BackupFileServiceImpl(backupWorkspaceResource, this.hashPath, this.fileService, this.logService); } return new InMemoryBackupFileService(this.hashPath); @@ -163,6 +165,10 @@ export class BackupFileService implements IBackupFileService { return this.impl.discardBackup(resource); } + discardBackups(): Promise { + return this.impl.discardBackups(); + } + getBackups(): Promise { return this.impl.getBackups(); } @@ -194,7 +200,8 @@ class BackupFileServiceImpl extends Disposable implements IBackupFileService { constructor( backupWorkspaceResource: URI, private readonly hashPath: (resource: URI) => string, - @IFileService private readonly fileService: IFileService + @IFileService private readonly fileService: IFileService, + @ILogService private readonly logService: ILogService ) { super(); @@ -257,6 +264,14 @@ class BackupFileServiceImpl extends Disposable implements IBackupFileService { }); } + async discardBackups(): Promise { + const model = await this.ready; + + await this.deleteIgnoreFileNotFound(this.backupWorkspacePath); + + model.clear(); + } + discardBackup(resource: URI): Promise { const backupResource = this.toBackupResource(resource); @@ -372,7 +387,9 @@ class BackupFileServiceImpl extends Disposable implements IBackupFileService { // the meta-end marker ('\n') and as such the backup can only be invalid. We bail out // here if that is the case. if (!metaEndFound) { - throw new Error(`Backup: Could not find meta end marker in ${backupResource}. The file is probably corrupt.`); + this.logService.error(`Backup: Could not find meta end marker in ${backupResource}. The file is probably corrupt.`); + + return undefined; } return { value: factory, meta }; @@ -417,13 +434,17 @@ export class InMemoryBackupFileService implements IBackupFileService { } async getBackups(): Promise { - return keys(this.backups).map(key => URI.parse(key)); + return Array.from(this.backups.keys()).map(key => URI.parse(key)); } async discardBackup(resource: URI): Promise { this.backups.delete(this.toBackupResource(resource).toString()); } + async discardBackups(): Promise { + this.backups.clear(); + } + toBackupResource(resource: URI): URI { return URI.file(join(resource.scheme, this.hashPath(resource))); } diff --git a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts index 1edea0078b3..e546cc1e728 100644 --- a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts +++ b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts @@ -43,12 +43,13 @@ const barFile = URI.file(platform.isWindows ? 'c:\\Bar' : '/Bar'); const fooBarFile = URI.file(platform.isWindows ? 'c:\\Foo Bar' : '/Foo Bar'); const untitledFile = URI.from({ scheme: Schemas.untitled, path: 'Untitled-1' }); const fooBackupPath = path.join(workspaceBackupPath, 'file', hashPath(fooFile)); +const barBackupPath = path.join(workspaceBackupPath, 'file', hashPath(barFile)); const untitledBackupPath = path.join(workspaceBackupPath, 'untitled', hashPath(untitledFile)); class TestBackupEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(backupPath: string) { - super({ ...TestWindowConfiguration, backupPath, 'user-data-dir': userdataDir }, TestWindowConfiguration.execPath, TestWindowConfiguration.windowId); + super({ ...TestWindowConfiguration, backupPath, 'user-data-dir': userdataDir }, TestWindowConfiguration.execPath); } } @@ -62,12 +63,13 @@ export class NodeTestBackupFileService extends BackupFileService { constructor(workspaceBackupPath: string) { const environmentService = new TestBackupEnvironmentService(workspaceBackupPath); - const fileService = new FileService(new NullLogService()); - const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + const logService = new NullLogService(); + const fileService = new FileService(logService); + const diskFileSystemProvider = new DiskFileSystemProvider(logService); fileService.registerProvider(Schemas.file, diskFileSystemProvider); fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService)); - super(environmentService, fileService); + super(environmentService, fileService, logService); this.fileService = fileService; this.backupResourceJoiners = []; @@ -99,6 +101,14 @@ export class NodeTestBackupFileService extends BackupFileService { this.discardBackupJoiners.pop()!(); } } + + async getBackupContents(resource: URI): Promise { + const backupResource = this.toBackupResource(resource); + + const fileContents = await this.fileService.readFile(backupResource); + + return fileContents.value.toString(); + } } suite('BackupFileService', () => { @@ -276,6 +286,33 @@ suite('BackupFileService', () => { }); }); + suite('discardBackups', () => { + test('text file', async () => { + await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)); + assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1); + await service.backup(barFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)); + assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 2); + await service.discardBackups(); + assert.equal(fs.existsSync(fooBackupPath), false); + assert.equal(fs.existsSync(barBackupPath), false); + assert.equal(fs.existsSync(path.join(workspaceBackupPath, 'file')), false); + }); + + test('untitled file', async () => { + await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)); + assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1); + await service.discardBackups(); + assert.equal(fs.existsSync(untitledBackupPath), false); + assert.equal(fs.existsSync(path.join(workspaceBackupPath, 'untitled')), false); + }); + + test('can backup after discarding all', async () => { + await service.discardBackups(); + await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)); + assert.equal(fs.existsSync(workspaceBackupPath), true); + }); + }); + suite('getBackups', () => { test('("file") - text file', async () => { await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)); @@ -473,7 +510,7 @@ suite('BackupFileService', () => { await testResolveBackup(fooBarFile, contents, meta, null); }); - test('should throw an error when restoring invalid backup', async () => { + test('should ignore invalid backups', async () => { const contents = 'test\nand more stuff'; await service.backup(fooBarFile, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).createSnapshot(false), 1); @@ -485,14 +522,14 @@ suite('BackupFileService', () => { await service.fileService.writeFile(service.toBackupResource(fooBarFile), VSBuffer.fromString('')); - let err: Error; + let err: Error | undefined = undefined; try { await service.resolve(fooBarFile); } catch (error) { err = error; } - assert.ok(err!); + assert.ok(!err); }); async function testResolveBackup(resource: URI, contents: string, meta?: IBackupTestMetaData, expectedMeta?: IBackupTestMetaData | null) { diff --git a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts index dcb5a2c3346..23cc7b80b96 100644 --- a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts +++ b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts @@ -28,7 +28,7 @@ import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerServ import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; -import { EditStackElement, MultiModelEditStackElement } from 'vs/editor/common/model/editStack'; +import { SingleModelEditStackElement, MultiModelEditStackElement } from 'vs/editor/common/model/editStack'; type ValidationResult = { canApply: true } | { canApply: false, reason: URI }; @@ -234,7 +234,7 @@ class BulkEditModel implements IDisposable { const multiModelEditStackElement = new MultiModelEditStackElement( this._label || localize('workspaceEdit', "Workspace Edit"), - tasks.map(t => new EditStackElement(t.model, t.getBeforeCursorState())) + tasks.map(t => new SingleModelEditStackElement(t.model, t.getBeforeCursorState())) ); this._undoRedoService.pushElement(multiModelEditStackElement); @@ -433,7 +433,7 @@ export class BulkEditService implements IBulkEditService { // try to find code editor if (!codeEditor) { - let candidate = this._editorService.activeTextEditorWidget; + let candidate = this._editorService.activeTextEditorControl; if (isCodeEditor(candidate)) { codeEditor = candidate; } diff --git a/src/vs/workbench/services/commands/common/commandService.ts b/src/vs/workbench/services/commands/common/commandService.ts index 8bd181690e0..0b8653ad474 100644 --- a/src/vs/workbench/services/commands/common/commandService.ts +++ b/src/vs/workbench/services/commands/common/commandService.ts @@ -81,7 +81,7 @@ export class CommandService extends Disposable implements ICommandService { } try { this._onWillExecuteCommand.fire({ commandId: id, args }); - const result = this._instantiationService.invokeFunction.apply(this._instantiationService, [command.handler, ...args]); + const result = this._instantiationService.invokeFunction(command.handler, ...args); this._onDidExecuteCommand.fire({ commandId: id, args }); return Promise.resolve(result); } catch (err) { @@ -90,4 +90,4 @@ export class CommandService extends Disposable implements ICommandService { } } -registerSingleton(ICommandService, CommandService, true); \ No newline at end of file +registerSingleton(ICommandService, CommandService, true); diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index a334a34a15e..60691e87e95 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -8,7 +8,7 @@ import * as resources from 'vs/base/common/resources'; import { Event, Emitter } from 'vs/base/common/event'; import * as errors from 'vs/base/common/errors'; import { Disposable, IDisposable, dispose, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { RunOnceScheduler, runWhenIdle } from 'vs/base/common/async'; +import { RunOnceScheduler } from 'vs/base/common/async'; import { FileChangeType, FileChangesEvent, IFileService, whenProviderRegistered, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { ConfigurationModel, ConfigurationModelParser, UserSettings } from 'vs/platform/configuration/common/configurationModels'; import { WorkspaceConfigurationModelParser, StandaloneConfigurationModelParser } from 'vs/workbench/services/configuration/common/configurationModels'; @@ -17,7 +17,7 @@ import { IStoredWorkspaceFolder, IWorkspaceIdentifier } from 'vs/platform/worksp import { JSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditingService'; import { WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; -import { extname, join } from 'vs/base/common/path'; +import { join } from 'vs/base/common/path'; import { equals } from 'vs/base/common/objects'; import { Schemas } from 'vs/base/common/network'; import { IConfigurationModel } from 'vs/platform/configuration/common/configuration'; @@ -26,13 +26,14 @@ import { hash } from 'vs/base/common/hash'; export class UserConfiguration extends Disposable { - private readonly _onDidInitializeCompleteConfiguration: Emitter = this._register(new Emitter()); private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; - private readonly userConfiguration: MutableDisposable = this._register(new MutableDisposable()); + private readonly userConfiguration: MutableDisposable = this._register(new MutableDisposable()); private readonly reloadConfigurationScheduler: RunOnceScheduler; + get hasTasksLoaded(): boolean { return this.userConfiguration.value instanceof FileServiceBasedConfiguration; } + constructor( private readonly userSettingsResource: URI, private readonly scopes: ConfigurationScope[] | undefined, @@ -42,9 +43,6 @@ export class UserConfiguration extends Disposable { this.userConfiguration.value = new UserSettings(this.userSettingsResource, this.scopes, this.fileService); this._register(this.userConfiguration.value.onDidChange(() => this.reloadConfigurationScheduler.schedule())); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50)); - - runWhenIdle(() => this._onDidInitializeCompleteConfiguration.fire(), 5000); - this._register(Event.once(this._onDidInitializeCompleteConfiguration.event)(() => this.reloadConfigurationScheduler.schedule())); } async initialize(): Promise { @@ -52,11 +50,22 @@ export class UserConfiguration extends Disposable { } async reload(): Promise { - if (!(this.userConfiguration.value instanceof FileServiceBasedConfigurationWithNames)) { - this.userConfiguration.value = new FileServiceBasedConfigurationWithNames(resources.dirname(this.userSettingsResource), [FOLDER_SETTINGS_NAME, TASKS_CONFIGURATION_KEY], this.scopes, this.fileService); + if (this.hasTasksLoaded) { + return this.userConfiguration.value!.loadConfiguration(); + } + + const folder = resources.dirname(this.userSettingsResource); + const standAloneConfigurationResources: [string, URI][] = [TASKS_CONFIGURATION_KEY].map(name => ([name, resources.joinPath(folder, `${name}.json`)])); + const fileServiceBasedConfiguration = new FileServiceBasedConfiguration(folder.toString(), [this.userSettingsResource], standAloneConfigurationResources, this.scopes, this.fileService); + const configurationModel = await fileServiceBasedConfiguration.loadConfiguration(); + this.userConfiguration.value = fileServiceBasedConfiguration; + + // Check for value because userConfiguration might have been disposed. + if (this.userConfiguration.value) { this._register(this.userConfiguration.value.onDidChange(() => this.reloadConfigurationScheduler.schedule())); } - return this.userConfiguration.value!.loadConfiguration(); + + return configurationModel; } reprocess(): ConfigurationModel { @@ -64,24 +73,27 @@ export class UserConfiguration extends Disposable { } } -class FileServiceBasedConfigurationWithNames extends Disposable { +class FileServiceBasedConfiguration extends Disposable { + private readonly allResources: URI[]; private _folderSettingsModelParser: ConfigurationModelParser; private _standAloneConfigurations: ConfigurationModel[]; private _cache: ConfigurationModel; - protected readonly configurationResources: URI[]; - protected changeEventTriggerScheduler: RunOnceScheduler; - protected readonly _onDidChange: Emitter = this._register(new Emitter()); + private readonly changeEventTriggerScheduler: RunOnceScheduler; + private readonly _onDidChange: Emitter = this._register(new Emitter()); readonly onDidChange: Event = this._onDidChange.event; - constructor(protected readonly configurationFolder: URI, - private readonly configurationNames: string[], + constructor( + name: string, + private readonly settingsResources: URI[], + private readonly standAloneConfigurationResources: [string, URI][], private readonly scopes: ConfigurationScope[] | undefined, - private fileService: IFileService) { + private fileService: IFileService + ) { super(); - this.configurationResources = this.configurationNames.map(name => resources.joinPath(this.configurationFolder, `${name}.json`)); - this._folderSettingsModelParser = new ConfigurationModelParser(this.configurationFolder.toString(), this.scopes); + this.allResources = [...this.settingsResources, ...this.standAloneConfigurationResources.map(([, resource]) => resource)]; + this._folderSettingsModelParser = new ConfigurationModelParser(name, this.scopes); this._standAloneConfigurations = []; this._cache = new ConfigurationModel(); @@ -90,30 +102,38 @@ class FileServiceBasedConfigurationWithNames extends Disposable { } async loadConfiguration(): Promise { - const configurationContents = await Promise.all(this.configurationResources.map(async resource => { - try { - const content = await this.fileService.readFile(resource); - return content.value.toString(); - } catch (error) { - if ((error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) { - errors.onUnexpectedError(error); + const resolveContents = async (resources: URI[]): Promise<(string | undefined)[]> => { + return Promise.all(resources.map(async resource => { + try { + const content = await this.fileService.readFile(resource); + return content.value.toString(); + } catch (error) { + if ((error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND + && (error).fileOperationResult !== FileOperationResult.FILE_NOT_DIRECTORY) { + errors.onUnexpectedError(error); + } } - } - return undefined; - })); + return '{}'; + })); + }; + + const [settingsContents, standAloneConfigurationContents] = await Promise.all([ + resolveContents(this.settingsResources), + resolveContents(this.standAloneConfigurationResources.map(([, resource]) => resource)), + ]); // reset this._standAloneConfigurations = []; this._folderSettingsModelParser.parseContent(''); // parse - if (configurationContents[0]) { - this._folderSettingsModelParser.parseContent(configurationContents[0]); + if (settingsContents[0]) { + this._folderSettingsModelParser.parseContent(settingsContents[0]); } - for (let index = 1; index < configurationContents.length; index++) { - const contents = configurationContents[index]; + for (let index = 0; index < standAloneConfigurationContents.length; index++) { + const contents = standAloneConfigurationContents[index]; if (contents) { - const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(this.configurationResources[index].toString(), this.configurationNames[index]); + const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(this.standAloneConfigurationResources[index][1].toString(), this.standAloneConfigurationResources[index][0]); standAloneConfigurationModelParser.parseContent(contents); this._standAloneConfigurations.push(standAloneConfigurationModelParser.configurationModel); } @@ -139,49 +159,22 @@ class FileServiceBasedConfigurationWithNames extends Disposable { } protected async handleFileEvents(event: FileChangesEvent): Promise { - const events = event.changes; - let affectedByChanges = false; - - // Find changes that affect workspace configuration files - for (let i = 0, len = events.length; i < len; i++) { - const resource = events[i].resource; - const basename = resources.basename(resource); - const isJson = extname(basename) === '.json'; - const isConfigurationFolderDeleted = (events[i].type === FileChangeType.DELETED && resources.isEqual(resource, this.configurationFolder)); - - if (!isJson && !isConfigurationFolderDeleted) { - continue; // only JSON files or the actual settings folder + const isAffectedByChanges = (): boolean => { + // One of the resources has changed + if (this.allResources.some(resource => event.contains(resource))) { + return true; } - - const folderRelativePath = this.toFolderRelativePath(resource); - if (!folderRelativePath) { - continue; // event is not inside folder + // One of the resource's parent got deleted + if (this.allResources.some(resource => event.contains(resources.dirname(resource), FileChangeType.DELETED))) { + return true; } - - // Handle case where ".vscode" got deleted - if (isConfigurationFolderDeleted) { - affectedByChanges = true; - break; - } - - // only valid workspace config files - if (this.configurationResources.some(configurationResource => resources.isEqual(configurationResource, resource))) { - affectedByChanges = true; - break; - } - } - - if (affectedByChanges) { + return false; + }; + if (isAffectedByChanges()) { this.changeEventTriggerScheduler.schedule(); } } - private toFolderRelativePath(resource: URI): string | undefined { - if (resources.isEqualOrParent(resource, this.configurationFolder)) { - return resources.relativePath(this.configurationFolder, resource); - } - return undefined; - } } export class RemoteUserConfiguration extends Disposable { @@ -667,14 +660,6 @@ export interface IFolderConfiguration extends IDisposable { reprocess(): ConfigurationModel; } -class FileServiceBasedFolderConfiguration extends FileServiceBasedConfigurationWithNames implements IFolderConfiguration { - - constructor(configurationFolder: URI, workbenchState: WorkbenchState, fileService: IFileService) { - super(configurationFolder, [FOLDER_SETTINGS_NAME /*First one should be settings */, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY], WorkbenchState.WORKSPACE === workbenchState ? FOLDER_SCOPES : WORKSPACE_SCOPES, fileService); - } - -} - class CachedFolderConfiguration extends Disposable implements IFolderConfiguration { private readonly _onDidChange: Emitter = this._register(new Emitter()); @@ -742,18 +727,18 @@ export class FolderConfiguration extends Disposable implements IFolderConfigurat this.configurationFolder = resources.joinPath(workspaceFolder.uri, configFolderRelativePath); this.folderConfiguration = this.cachedFolderConfiguration = new CachedFolderConfiguration(workspaceFolder.uri, configFolderRelativePath, configurationCache); if (workspaceFolder.uri.scheme === Schemas.file) { - this.folderConfiguration = new FileServiceBasedFolderConfiguration(this.configurationFolder, this.workbenchState, fileService); + this.folderConfiguration = this.createFileServiceBasedConfiguration(fileService); + this.folderConfigurationDisposable = this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange())); } else { whenProviderRegistered(workspaceFolder.uri, fileService) .then(() => { this.folderConfiguration.dispose(); this.folderConfigurationDisposable.dispose(); - this.folderConfiguration = new FileServiceBasedFolderConfiguration(this.configurationFolder, this.workbenchState, fileService); + this.folderConfiguration = this.createFileServiceBasedConfiguration(fileService); this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange())); this.onDidFolderConfigurationChange(); }); } - this.folderConfigurationDisposable = this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange())); } loadConfiguration(): Promise { @@ -769,8 +754,14 @@ export class FolderConfiguration extends Disposable implements IFolderConfigurat this._onDidChange.fire(); } + private createFileServiceBasedConfiguration(fileService: IFileService) { + const settingsResources = [resources.joinPath(this.configurationFolder, `${FOLDER_SETTINGS_NAME}.json`)]; + const standAloneConfigurationResources: [string, URI][] = [TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY].map(name => ([name, resources.joinPath(this.configurationFolder, `${name}.json`)])); + return new FileServiceBasedConfiguration(this.configurationFolder.toString(), settingsResources, standAloneConfigurationResources, WorkbenchState.WORKSPACE === this.workbenchState ? FOLDER_SCOPES : WORKSPACE_SCOPES, fileService); + } + private updateCache(): Promise { - if (this.configurationFolder.scheme !== Schemas.file && this.folderConfiguration instanceof FileServiceBasedFolderConfiguration) { + if (this.configurationFolder.scheme !== Schemas.file && this.folderConfiguration instanceof FileServiceBasedConfiguration) { return this.folderConfiguration.loadConfiguration() .then(configurationModel => this.cachedFolderConfiguration.updateConfiguration(configurationModel)); } diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 799867a700f..c44f5db8e0b 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -8,7 +8,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { ResourceMap } from 'vs/base/common/map'; import { equals } from 'vs/base/common/objects'; import { Disposable } from 'vs/base/common/lifecycle'; -import { Queue, Barrier } from 'vs/base/common/async'; +import { Queue, Barrier, runWhenIdle } from 'vs/base/common/async'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { IWorkspaceContextService, Workspace, WorkbenchState, IWorkspaceFolder, toWorkspaceFolders, IWorkspaceFoldersChangeEvent, WorkspaceFolder, toWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent, mergeChanges } from 'vs/platform/configuration/common/configurationModels'; @@ -156,7 +156,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic return false; } - private doUpdateFolders(foldersToAdd: IWorkspaceFolderCreationData[], foldersToRemove: URI[], index?: number): Promise { + private async doUpdateFolders(foldersToAdd: IWorkspaceFolderCreationData[], foldersToRemove: URI[], index?: number): Promise { if (this.getWorkbenchState() !== WorkbenchState.WORKSPACE) { return Promise.resolve(undefined); // we need a workspace to begin with } @@ -192,13 +192,19 @@ export class WorkspaceService extends Disposable implements IConfigurationServic const storedFoldersToAdd: IStoredWorkspaceFolder[] = []; - foldersToAdd.forEach(folderToAdd => { + for (const folderToAdd of foldersToAdd) { const folderURI = folderToAdd.uri; if (this.contains(currentWorkspaceFolderUris, folderURI)) { - return; // already existing + continue; // already existing } - storedFoldersToAdd.push(getStoredWorkspaceFolder(folderURI, folderToAdd.name, workspaceConfigFolder, slashForPath)); - }); + try { + const result = await this.fileService.resolve(folderURI); + if (!result.isDirectory) { + continue; + } + } catch (e) { /* Ignore */ } + storedFoldersToAdd.push(getStoredWorkspaceFolder(folderURI, false, folderToAdd.name, workspaceConfigFolder, slashForPath)); + } // Apply to array of newStoredFolders if (storedFoldersToAdd.length > 0) { @@ -380,6 +386,15 @@ export class WorkspaceService extends Disposable implements IConfigurationServic if (folderChanges && (folderChanges.added.length || folderChanges.removed.length || folderChanges.changed.length)) { this._onDidChangeWorkspaceFolders.fire(folderChanges); } + + } else { + // Not waiting on this validation to unblock start up + this.validateWorkspaceFoldersAndReload(); + } + + if (!this.localUserConfiguration.hasTasksLoaded) { + // Reload local user configuration again to load user tasks + runWhenIdle(() => this.reloadLocalUserConfiguration().then(configurationModel => this.onLocalUserConfigurationChanged(configurationModel)), 5000); } }); } @@ -531,11 +546,11 @@ export class WorkspaceService extends Disposable implements IConfigurationServic this.triggerConfigurationChange(change, previous, ConfigurationTarget.USER); } - private onWorkspaceConfigurationChanged(): Promise { + private async onWorkspaceConfigurationChanged(): Promise { if (this.workspace && this.workspace.configuration) { const previous = { data: this._configuration.toData(), workspace: this.workspace }; const change = this._configuration.compareAndUpdateWorkspaceConfiguration(this.workspaceConfiguration.getConfiguration()); - let configuredFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), this.workspace.configuration); + let configuredFolders = await this.toValidWorkspaceFolders(toWorkspaceFolders(this.workspaceConfiguration.getFolders(), this.workspace.configuration)); const changes = this.compareFolders(this.workspace.folders, configuredFolders); if (changes.added.length || changes.removed.length || changes.changed.length) { this.workspace.folders = configuredFolders; @@ -600,6 +615,28 @@ export class WorkspaceService extends Disposable implements IConfigurationServic })]); } + private async validateWorkspaceFoldersAndReload(): Promise { + const validWorkspaceFolders = await this.toValidWorkspaceFolders(this.workspace.folders); + const { removed } = this.compareFolders(this.workspace.folders, validWorkspaceFolders); + if (removed.length) { + return this.onWorkspaceConfigurationChanged(); + } + } + + private async toValidWorkspaceFolders(workspaceFolders: WorkspaceFolder[]): Promise { + const validWorkspaceFolders: WorkspaceFolder[] = []; + for (const workspaceFolder of workspaceFolders) { + try { + const result = await this.fileService.resolve(workspaceFolder.uri); + if (!result.isDirectory) { + continue; + } + } catch (e) { /* Ignore */ } + validWorkspaceFolders.push(workspaceFolder); + } + return validWorkspaceFolders; + } + private writeConfigurationValue(key: string, value: any, target: ConfigurationTarget, overrides: IConfigurationOverrides | undefined, donotNotifyError: boolean): Promise { if (target === ConfigurationTarget.DEFAULT) { return Promise.reject(new Error('Invalid configuration target')); diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index 888f442d2fe..630086370ba 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -40,3 +40,5 @@ export interface IConfigurationCache { remove(key: ConfigurationKey): Promise; } + +export const TASKS_DEFAULT = '{\n\t\"version\": \"2.0.0\",\n\t\"tasks\": []\n}'; diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index 29f4c406cc6..573b4f0f546 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -20,7 +20,7 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/ import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IConfigurationService, IConfigurationOverrides, keyFromOverrideIdentifier } from 'vs/platform/configuration/common/configuration'; -import { FOLDER_SETTINGS_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY, USER_STANDALONE_CONFIGURATIONS } from 'vs/workbench/services/configuration/common/configuration'; +import { FOLDER_SETTINGS_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY, USER_STANDALONE_CONFIGURATIONS, TASKS_DEFAULT } from 'vs/workbench/services/configuration/common/configuration'; import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; import { OVERRIDE_PROPERTY_PATTERN, IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; @@ -230,7 +230,7 @@ export class ConfigurationEditingService { } } - private onInvalidConfigurationError(error: ConfigurationEditingError, operation: IConfigurationEditOperation, ): void { + private onInvalidConfigurationError(error: ConfigurationEditingError, operation: IConfigurationEditOperation,): void { const openStandAloneConfigurationActionLabel = operation.workspaceStandAloneConfigurationKey === TASKS_CONFIGURATION_KEY ? nls.localize('openTasksConfiguration', "Open Tasks Configuration") : operation.workspaceStandAloneConfigurationKey === LAUNCH_CONFIGURATION_KEY ? nls.localize('openLaunchConfiguration', "Open Launch Configuration") : null; @@ -434,10 +434,19 @@ export class ConfigurationEditingService { return setProperty(model.getValue(), jsonPath, value, { tabSize, insertSpaces, eol }); } + private defaultResourceValue(resource: URI): string { + const basename: string = resources.basename(resource); + const configurationValue: string = basename.substr(0, basename.length - resources.extname(resource).length); + switch (configurationValue) { + case TASKS_CONFIGURATION_KEY: return TASKS_DEFAULT; + default: return '{}'; + } + } + private async resolveModelReference(resource: URI): Promise> { const exists = await this.fileService.exists(resource); if (!exists) { - await this.textFileService.write(resource, '{}', { encoding: 'utf8' }); + await this.textFileService.write(resource, this.defaultResourceValue(resource), { encoding: 'utf8' }); } return this.textModelResolverService.createModelReference(resource); } diff --git a/src/vs/workbench/services/configuration/common/jsonEditingService.ts b/src/vs/workbench/services/configuration/common/jsonEditingService.ts index 10f76510c7e..289c66fb2f8 100644 --- a/src/vs/workbench/services/configuration/common/jsonEditingService.ts +++ b/src/vs/workbench/services/configuration/common/jsonEditingService.ts @@ -41,18 +41,18 @@ export class JSONEditingService implements IJSONEditingService { private async doWriteConfiguration(resource: URI, values: IJSONValue[], save: boolean): Promise { const reference = await this.resolveAndValidate(resource, save); - await this.writeToBuffer(reference.object.textEditorModel, values); + await this.writeToBuffer(reference.object.textEditorModel, values, save); reference.dispose(); } - private async writeToBuffer(model: ITextModel, values: IJSONValue[]): Promise { + private async writeToBuffer(model: ITextModel, values: IJSONValue[], save: boolean): Promise { let hasEdits: boolean = false; for (const value of values) { const edit = this.getEdits(model, value)[0]; hasEdits = this.applyEditsToBuffer(edit, model); } - if (hasEdits) { + if (hasEdits && save) { return this.textFileService.save(model.uri); } } diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts index 606660daeac..ab358080c44 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts @@ -45,7 +45,7 @@ import { FileUserDataProvider } from 'vs/workbench/services/userData/common/file class TestEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(private _appSettingsHome: URI) { - super(TestWindowConfiguration, TestWindowConfiguration.execPath, TestWindowConfiguration.windowId); + super(TestWindowConfiguration, TestWindowConfiguration.execPath); } get appSettingsHome() { return this._appSettingsHome; } diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts index 613a0148272..6fb46f13923 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts @@ -30,8 +30,8 @@ import { IJSONEditingService } from 'vs/workbench/services/configuration/common/ import { JSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditingService'; import { createHash } from 'crypto'; import { Schemas } from 'vs/base/common/network'; -import { originalFSPath } from 'vs/base/common/resources'; -import { isLinux } from 'vs/base/common/platform'; +import { originalFSPath, joinPath } from 'vs/base/common/resources'; +import { isLinux, isMacintosh } from 'vs/base/common/platform'; import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-browser/remoteAuthorityResolverService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; @@ -47,11 +47,13 @@ import { IKeybindingEditingService, KeybindingsEditingService } from 'vs/workben import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { timeout } from 'vs/base/common/async'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { DisposableStore } from 'vs/base/common/lifecycle'; class TestEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(private _appSettingsHome: URI) { - super(TestWindowConfiguration, TestWindowConfiguration.execPath, TestWindowConfiguration.windowId); + super(TestWindowConfiguration, TestWindowConfiguration.execPath); } get appSettingsHome() { return this._appSettingsHome; } @@ -719,6 +721,8 @@ suite('WorkspaceConfigurationService - Folder', () => { let workspaceName = `testWorkspace${uuid.generateUuid()}`, parentResource: string, workspaceDir: string, testObject: IConfigurationService, globalSettingsFile: string, globalTasksFile: string, workspaceService: WorkspaceService; const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); + let fileService: IFileService; + let disposableStore: DisposableStore = new DisposableStore(); suiteSetup(() => { configurationRegistry.registerConfiguration({ @@ -767,15 +771,18 @@ suite('WorkspaceConfigurationService - Folder', () => { const environmentService = new TestEnvironmentService(URI.file(parentDir)); const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {}); instantiationService.stub(IRemoteAgentService, remoteAgentService); - const fileService = new FileService(new NullLogService()); + fileService = new FileService(new NullLogService()); const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService)); - workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService); + workspaceService = disposableStore.add(new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService)); instantiationService.stub(IWorkspaceContextService, workspaceService); instantiationService.stub(IConfigurationService, workspaceService); instantiationService.stub(IEnvironmentService, environmentService); + // Watch workspace configuration directory + disposableStore.add(fileService.watch(joinPath(URI.file(workspaceDir), '.vscode'))); + return workspaceService.initialize(convertToWorkspacePayload(URI.file(folderDir))).then(() => { instantiationService.stub(IFileService, fileService); instantiationService.stub(IKeybindingEditingService, instantiationService.createInstance(KeybindingsEditingService)); @@ -788,9 +795,7 @@ suite('WorkspaceConfigurationService - Folder', () => { }); teardown(() => { - if (testObject) { - (testObject).dispose(); - } + disposableStore.clear(); if (parentResource) { return pfs.rimraf(parentResource, pfs.RimRafMode.MOVE); } @@ -1101,6 +1106,40 @@ suite('WorkspaceConfigurationService - Folder', () => { fs.writeFileSync(globalTasksFile, '{ "version": "1.0.0", "tasks": [{ "taskName": "myTask" }'); return new Promise((c) => testObject.onDidChangeConfiguration(() => c())); }); + + test('creating workspace settings', async () => { + fs.writeFileSync(globalSettingsFile, '{ "configurationService.folder.testSetting": "userValue" }'); + await testObject.reloadConfiguration(); + const workspaceSettingsResource = URI.file(path.join(workspaceDir, '.vscode', 'settings.json')); + await new Promise(async (c) => { + const disposable = testObject.onDidChangeConfiguration(e => { + assert.ok(e.affectsConfiguration('configurationService.folder.testSetting')); + assert.equal(testObject.getValue('configurationService.folder.testSetting'), 'workspaceValue'); + disposable.dispose(); + c(); + }); + await fileService.writeFile(workspaceSettingsResource, VSBuffer.fromString('{ "configurationService.folder.testSetting": "workspaceValue" }')); + }); + }); + + test('deleting workspace settings', async () => { + if (!isMacintosh) { + return; + } + fs.writeFileSync(globalSettingsFile, '{ "configurationService.folder.testSetting": "userValue" }'); + const workspaceSettingsResource = URI.file(path.join(workspaceDir, '.vscode', 'settings.json')); + await fileService.writeFile(workspaceSettingsResource, VSBuffer.fromString('{ "configurationService.folder.testSetting": "workspaceValue" }')); + await testObject.reloadConfiguration(); + await new Promise(async (c) => { + const disposable = testObject.onDidChangeConfiguration(e => { + assert.ok(e.affectsConfiguration('configurationService.folder.testSetting')); + assert.equal(testObject.getValue('configurationService.folder.testSetting'), 'userValue'); + disposable.dispose(); + c(); + }); + await fileService.del(workspaceSettingsResource); + }); + }); }); suite('WorkspaceConfigurationService-Multiroot', () => { diff --git a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts index e86e00ffc4e..290c68e8bde 100644 --- a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts @@ -11,7 +11,7 @@ import { Schemas } from 'vs/base/common/network'; import { toResource } from 'vs/workbench/common/editor'; import { IStringDictionary, forEach, fromMap } from 'vs/base/common/collections'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IWorkspaceFolder, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -62,10 +62,10 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR return path.normalize(fileResource.fsPath); }, getSelectedText: (): string | undefined => { - const activeTextEditorWidget = editorService.activeTextEditorWidget; - if (isCodeEditor(activeTextEditorWidget)) { - const editorModel = activeTextEditorWidget.getModel(); - const editorSelection = activeTextEditorWidget.getSelection(); + const activeTextEditorControl = editorService.activeTextEditorControl; + if (isCodeEditor(activeTextEditorControl)) { + const editorModel = activeTextEditorControl.getModel(); + const editorSelection = activeTextEditorControl.getSelection(); if (editorModel && editorSelection) { return editorModel.getValueInRange(editorSelection); } @@ -73,9 +73,9 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR return undefined; }, getLineNumber: (): string | undefined => { - const activeTextEditorWidget = editorService.activeTextEditorWidget; - if (isCodeEditor(activeTextEditorWidget)) { - const selection = activeTextEditorWidget.getSelection(); + const activeTextEditorControl = editorService.activeTextEditorControl; + if (isCodeEditor(activeTextEditorControl)) { + const selection = activeTextEditorControl.getSelection(); if (selection) { const lineNumber = selection.positionLineNumber; return String(lineNumber); @@ -86,12 +86,12 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR }, envVariables); } - public async resolveWithInteractionReplace(folder: IWorkspaceFolder | undefined, config: any, section?: string, variables?: IStringDictionary): Promise { + public async resolveWithInteractionReplace(folder: IWorkspaceFolder | undefined, config: any, section?: string, variables?: IStringDictionary, target?: ConfigurationTarget): Promise { // resolve any non-interactive variables and any contributed variables config = this.resolveAny(folder, config); // resolve input variables in the order in which they are encountered - return this.resolveWithInteraction(folder, config, section, variables).then(mapping => { + return this.resolveWithInteraction(folder, config, section, variables, target).then(mapping => { // finally substitute evaluated command variables (if there are any) if (!mapping) { return null; @@ -103,14 +103,14 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR }); } - public async resolveWithInteraction(folder: IWorkspaceFolder | undefined, config: any, section?: string, variables?: IStringDictionary): Promise | undefined> { + public async resolveWithInteraction(folder: IWorkspaceFolder | undefined, config: any, section?: string, variables?: IStringDictionary, target?: ConfigurationTarget): Promise | undefined> { // resolve any non-interactive variables and any contributed variables const resolved = await this.resolveAnyMap(folder, config); config = resolved.newConfig; const allVariableMapping: Map = resolved.resolvedVariables; // resolve input and command variables in the order in which they are encountered - return this.resolveWithInputAndCommands(folder, config, variables, section).then(inputOrCommandMapping => { + return this.resolveWithInputAndCommands(folder, config, variables, section, target).then(inputOrCommandMapping => { if (this.updateMapping(inputOrCommandMapping, allVariableMapping)) { return allVariableMapping; } @@ -139,7 +139,7 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR * * @param variableToCommandMap Aliases for commands */ - private async resolveWithInputAndCommands(folder: IWorkspaceFolder | undefined, configuration: any, variableToCommandMap?: IStringDictionary, section?: string): Promise | undefined> { + private async resolveWithInputAndCommands(folder: IWorkspaceFolder | undefined, configuration: any, variableToCommandMap?: IStringDictionary, section?: string, target?: ConfigurationTarget): Promise | undefined> { if (!configuration) { return Promise.resolve(undefined); @@ -148,9 +148,18 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR // get all "inputs" let inputs: ConfiguredInput[] = []; if (folder && this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY && section) { - let result = this.configurationService.getValue(section, { resource: folder.uri }); - if (result) { - inputs = result.inputs; + let result = this.configurationService.inspect(section, { resource: folder.uri }); + if (result && (result.userValue || result.workspaceValue || result.workspaceFolderValue)) { + switch (target) { + case ConfigurationTarget.USER: inputs = (result.userValue)?.inputs; break; + case ConfigurationTarget.WORKSPACE: inputs = (result.workspaceValue)?.inputs; break; + default: inputs = (result.workspaceFolderValue)?.inputs; + } + } else { + const valueResult = this.configurationService.getValue(section, { resource: folder.uri }); + if (valueResult) { + inputs = valueResult.inputs; + } } } @@ -340,7 +349,7 @@ export class ConfigurationResolverService extends BaseConfigurationResolverServi @IWorkspaceContextService workspaceContextService: IWorkspaceContextService, @IQuickInputService quickInputService: IQuickInputService ) { - super(environmentService.configuration.userEnv, editorService, environmentService, configurationService, commandService, workspaceContextService, quickInputService); + super(Object.create(null), editorService, environmentService, configurationService, commandService, workspaceContextService, quickInputService); } } diff --git a/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts b/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts index 8c3a5694be0..33a6c5d05da 100644 --- a/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts +++ b/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts @@ -6,6 +6,7 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; export const IConfigurationResolverService = createDecorator('configurationResolverService'); @@ -29,13 +30,13 @@ export interface IConfigurationResolverService { * @param section For example, 'tasks' or 'debug'. Used for resolving inputs. * @param variables Aliases for commands. */ - resolveWithInteractionReplace(folder: IWorkspaceFolder | undefined, config: any, section?: string, variables?: IStringDictionary): Promise; + resolveWithInteractionReplace(folder: IWorkspaceFolder | undefined, config: any, section?: string, variables?: IStringDictionary, target?: ConfigurationTarget): Promise; /** * Similar to resolveWithInteractionReplace, except without the replace. Returns a map of variables and their resolution. * Keys in the map will be of the format input:variableName or command:variableName. */ - resolveWithInteraction(folder: IWorkspaceFolder | undefined, config: any, section?: string, variables?: IStringDictionary): Promise | undefined>; + resolveWithInteraction(folder: IWorkspaceFolder | undefined, config: any, section?: string, variables?: IStringDictionary, target?: ConfigurationTarget): Promise | undefined>; /** * Contributes a variable that can be resolved later. Consumers that use resolveAny, resolveWithInteraction, diff --git a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts index 5f4b1cbb52a..c3eb73e6be3 100644 --- a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts +++ b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts @@ -4,14 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { Event } from 'vs/base/common/event'; import { URI as uri } from 'vs/base/common/uri'; import * as platform from 'vs/base/common/platform'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; -import { ConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; +import { BaseConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { TestEditorService, TestContextService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEditorService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestWindowConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -21,11 +22,11 @@ import * as Types from 'vs/base/common/types'; import { EditorType } from 'vs/editor/common/editorCommon'; import { Selection } from 'vs/editor/common/core/selection'; import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; const mockLineNumber = 10; class TestEditorServiceWithActiveEditor extends TestEditorService { - get activeTextEditorWidget(): any { + get activeTextEditorControl(): any { return { getEditorType() { return EditorType.ICodeEditor; @@ -37,10 +38,14 @@ class TestEditorServiceWithActiveEditor extends TestEditorService { } } +class TestConfigurationResolverService extends BaseConfigurationResolverService { + +} + suite('Configuration Resolver Service', () => { let configurationResolverService: IConfigurationResolverService | null; let envVariables: { [key: string]: string } = { key1: 'Value for key1', key2: 'Value for key2' }; - let environmentService: IWorkbenchEnvironmentService; + let environmentService: MockWorkbenchEnvironmentService; let mockCommandService: MockCommandService; let editorService: TestEditorServiceWithActiveEditor; let workspace: IWorkspaceFolder; @@ -57,7 +62,7 @@ suite('Configuration Resolver Service', () => { index: 0, toResource: (path: string) => uri.file(path) }; - configurationResolverService = new ConfigurationResolverService(editorService, environmentService, new MockInputsConfigurationService(), mockCommandService, new TestContextService(), quickInputService); + configurationResolverService = new TestConfigurationResolverService(environmentService.userEnv, editorService, environmentService, new MockInputsConfigurationService(), mockCommandService, new TestContextService(), quickInputService); }); teardown(() => { @@ -136,7 +141,7 @@ suite('Configuration Resolver Service', () => { } }); - let service = new ConfigurationResolverService(new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); + let service = new TestConfigurationResolverService(environmentService.userEnv, new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} xyz'), 'abc foo xyz'); }); @@ -153,7 +158,7 @@ suite('Configuration Resolver Service', () => { } }); - let service = new ConfigurationResolverService(new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); + let service = new TestConfigurationResolverService(environmentService.userEnv, new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} ${config:terminal.integrated.fontFamily} xyz'), 'abc foo bar xyz'); }); @@ -170,7 +175,7 @@ suite('Configuration Resolver Service', () => { } }); - let service = new ConfigurationResolverService(new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); + let service = new TestConfigurationResolverService(environmentService.userEnv, new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); if (platform.isWindows) { assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} ${workspaceFolder} ${env:key1} xyz'), 'abc foo \\VSCode\\workspaceLocation Value for key1 xyz'); } else { @@ -191,7 +196,7 @@ suite('Configuration Resolver Service', () => { } }); - let service = new ConfigurationResolverService(new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); + let service = new TestConfigurationResolverService(environmentService.userEnv, new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); if (platform.isWindows) { assert.strictEqual(service.resolve(workspace, '${config:editor.fontFamily} ${config:terminal.integrated.fontFamily} ${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), 'foo bar \\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation Value for key1 - Value for key2'); } else { @@ -225,7 +230,7 @@ suite('Configuration Resolver Service', () => { } }); - let service = new ConfigurationResolverService(new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); + let service = new TestConfigurationResolverService(environmentService.userEnv, new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} ${config:editor.lineNumbers} ${config:editor.insertSpaces} xyz'), 'abc foo 123 false xyz'); }); @@ -235,7 +240,7 @@ suite('Configuration Resolver Service', () => { editor: {} }); - let service = new ConfigurationResolverService(new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); + let service = new TestConfigurationResolverService(environmentService.userEnv, new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); assert.strictEqual(service.resolve(workspace, 'abc ${unknownVariable} xyz'), 'abc ${unknownVariable} xyz'); assert.strictEqual(service.resolve(workspace, 'abc ${env:unknownVariable} xyz'), 'abc xyz'); }); @@ -248,7 +253,7 @@ suite('Configuration Resolver Service', () => { } }); - let service = new ConfigurationResolverService(new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); + let service = new TestConfigurationResolverService(environmentService.userEnv, new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); assert.throws(() => service.resolve(workspace, 'abc ${env} xyz')); assert.throws(() => service.resolve(workspace, 'abc ${env:} xyz')); @@ -527,6 +532,11 @@ class MockCommandService implements ICommandService { class MockQuickInputService implements IQuickInputService { _serviceBrand: undefined; + readonly onShow = Event.None; + readonly onHide = Event.None; + + readonly quickAccess = undefined!; + public pick(picks: Promise[]> | QuickPickInput[], options?: IPickOptions & { canPickMany: true }, token?: CancellationToken): Promise; public pick(picks: Promise[]> | QuickPickInput[], options?: IPickOptions & { canPickMany: false }, token?: CancellationToken): Promise; public pick(picks: Promise[]> | QuickPickInput[], options?: Omit, 'canPickMany'>, token?: CancellationToken): Promise { @@ -619,7 +629,7 @@ class MockInputsConfigurationService extends TestConfigurationService { class MockWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService { - constructor(userEnv: platform.IProcessEnvironment) { - super({ ...TestWindowConfiguration, userEnv }, TestWindowConfiguration.execPath, TestWindowConfiguration.windowId); + constructor(public userEnv: platform.IProcessEnvironment) { + super({ ...TestWindowConfiguration, userEnv }, TestWindowConfiguration.execPath); } } diff --git a/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts index e607464f902..304c098c291 100644 --- a/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts @@ -25,6 +25,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { ContextMenuService as HTMLContextMenuService } from 'vs/platform/contextview/browser/contextMenuService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { stripCodicons } from 'vs/base/common/codicons'; export class ContextMenuService extends Disposable implements IContextMenuService { @@ -138,7 +139,7 @@ class NativeContextMenuService extends Disposable implements IContextMenuService // Submenu if (entry instanceof ContextSubMenu) { return { - label: unmnemonicLabel(entry.label), + label: unmnemonicLabel(stripCodicons(entry.label)).trim(), submenu: this.createMenu(delegate, entry.entries, onHide) }; } @@ -155,7 +156,7 @@ class NativeContextMenuService extends Disposable implements IContextMenuService } const item: IContextMenuItem = { - label: unmnemonicLabel(entry.label), + label: unmnemonicLabel(stripCodicons(entry.label)).trim(), checked: !!entry.checked, type, enabled: !!entry.enabled, diff --git a/src/vs/workbench/services/decorations/browser/decorationsService.ts b/src/vs/workbench/services/decorations/browser/decorationsService.ts index 6991989939f..208caf240a7 100644 --- a/src/vs/workbench/services/decorations/browser/decorationsService.ts +++ b/src/vs/workbench/services/decorations/browser/decorationsService.ts @@ -11,7 +11,7 @@ import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifec import { isThenable } from 'vs/base/common/async'; import { LinkedList } from 'vs/base/common/linkedList'; import { createStyleSheet, createCSSRule, removeCSSRulesContainingSelector } from 'vs/base/browser/dom'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { localize } from 'vs/nls'; import { isPromiseCanceledError } from 'vs/base/common/errors'; @@ -56,7 +56,7 @@ class DecorationRule { return --this._refCounter === 0; } - appendCSSRules(element: HTMLStyleElement, theme: ITheme): void { + appendCSSRules(element: HTMLStyleElement, theme: IColorTheme): void { if (!Array.isArray(this.data)) { this._appendForOne(this.data, element, theme); } else { @@ -64,7 +64,7 @@ class DecorationRule { } } - private _appendForOne(data: IDecorationData, element: HTMLStyleElement, theme: ITheme): void { + private _appendForOne(data: IDecorationData, element: HTMLStyleElement, theme: IColorTheme): void { const { color, letter } = data; // label createCSSRule(`.${this.itemColorClassName}`, `color: ${getColor(theme, color)};`, element); @@ -74,7 +74,7 @@ class DecorationRule { } } - private _appendForMany(data: IDecorationData[], element: HTMLStyleElement, theme: ITheme): void { + private _appendForMany(data: IDecorationData[], element: HTMLStyleElement, theme: IColorTheme): void { // label const { color } = data[0]; createCSSRule(`.${this.itemColorClassName}`, `color: ${getColor(theme, color)};`, element); @@ -110,7 +110,7 @@ class DecorationStyles { constructor( private _themeService: IThemeService, ) { - this._themeService.onThemeChange(this._onThemeChange, this, this._dispoables); + this._themeService.onDidColorThemeChange(this._onThemeChange, this, this._dispoables); } dispose(): void { @@ -130,7 +130,7 @@ class DecorationStyles { // new css rule rule = new DecorationRule(data, key); this._decorationRules.set(key, rule); - rule.appendCSSRules(this._styleElement, this._themeService.getTheme()); + rule.appendCSSRules(this._styleElement, this._themeService.getColorTheme()); } rule.acquire(); @@ -162,7 +162,7 @@ class DecorationStyles { private _onThemeChange(): void { this._decorationRules.forEach(rule => { rule.removeCSSRules(this._styleElement); - rule.appendCSSRules(this._styleElement, this._themeService.getTheme()); + rule.appendCSSRules(this._styleElement, this._themeService.getColorTheme()); }); } } @@ -364,23 +364,21 @@ export class DecorationsService implements IDecorationsService { getDecoration(uri: URI, includeChildren: boolean): IDecoration | undefined { let data: IDecorationData[] = []; let containsChildren: boolean = false; - for (let iter = this._data.iterator(), next = iter.next(); !next.done; next = iter.next()) { - const { label } = next.value.provider; - next.value.getOrRetrieve(uri, includeChildren, (deco, isChild) => { + for (let wrapper of this._data) { + wrapper.getOrRetrieve(uri, includeChildren, (deco, isChild) => { if (!isChild || deco.bubble) { data.push(deco); containsChildren = isChild || containsChildren; - this._logService.trace('DecorationsService#getDecoration#getOrRetrieve', label, deco, isChild, uri); + this._logService.trace('DecorationsService#getDecoration#getOrRetrieve', wrapper.provider.label, deco, isChild, uri); } }); } - return data.length === 0 ? undefined : this._decorationStyles.asDecoration(data, containsChildren); } } -function getColor(theme: ITheme, color: string | undefined) { +function getColor(theme: IColorTheme, color: string | undefined) { if (color) { const foundColor = theme.getColor(color); if (foundColor) { diff --git a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts index bdcff69df4f..7dc7647ffbf 100644 --- a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts @@ -88,8 +88,8 @@ export abstract class AbstractFileDialogService implements IFileDialogService { } async showSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise { - if (this.environmentService.isExtensionDevelopment) { - return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests) + if (this.environmentService.isExtensionDevelopment && this.environmentService.extensionTestsLocationURI) { + return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev testing mode because we cannot assume we run interactive } return this.doShowSaveConfirm(fileNamesOrResources); diff --git a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts index a3f45c88cdb..ea01678e848 100644 --- a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts @@ -54,9 +54,9 @@ export namespace SaveLocalFileCommand { export function handler(): ICommandHandler { return accessor => { const editorService = accessor.get(IEditorService); - const activeControl = editorService.activeControl; - if (activeControl) { - return editorService.save({ groupId: activeControl.group.id, editor: activeControl.input }, { saveAs: true, availableFileSystems: [Schemas.file], reason: SaveReason.EXPLICIT }); + const activeEditorPane = editorService.activeEditorPane; + if (activeEditorPane) { + return editorService.save({ groupId: activeEditorPane.group.id, editor: activeEditorPane.input }, { saveAs: true, availableFileSystems: [Schemas.file], reason: SaveReason.EXPLICIT }); } return Promise.resolve(undefined); diff --git a/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts index 36e903296cb..a7c6d54baa4 100644 --- a/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts @@ -6,7 +6,7 @@ import { SaveDialogOptions, OpenDialogOptions } from 'electron'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, IDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; +import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -190,16 +190,6 @@ export class FileDialogService extends AbstractFileDialogService implements IFil // Don't allow untitled schema through. return schema === Schemas.untitled ? [Schemas.file] : (schema !== Schemas.file ? [schema, Schemas.file] : [schema]); } - - async showSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise { - if (this.environmentService.isExtensionDevelopment) { - if (!this.environmentService.args['extension-development-confirm-save']) { - return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests) - } - } - - return super.doShowSaveConfirm(fileNamesOrResources); - } } registerSingleton(IFileDialogService, FileDialogService, true); diff --git a/src/vs/workbench/services/editor/browser/codeEditorService.ts b/src/vs/workbench/services/editor/browser/codeEditorService.ts index 27cf3bbdf59..b1b93a22314 100644 --- a/src/vs/workbench/services/editor/browser/codeEditorService.ts +++ b/src/vs/workbench/services/editor/browser/codeEditorService.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, isCodeEditor, isDiffEditor, isCompositeEditor } from 'vs/editor/browser/editorBrowser'; import { CodeEditorServiceImpl } from 'vs/editor/browser/services/codeEditorServiceImpl'; import { ScrollType } from 'vs/editor/common/editorCommon'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TextEditorOptions } from 'vs/workbench/common/editor'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; @@ -23,52 +23,60 @@ export class CodeEditorService extends CodeEditorServiceImpl { } getActiveCodeEditor(): ICodeEditor | null { - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (isCodeEditor(activeTextEditorWidget)) { - return activeTextEditorWidget; + const activeTextEditorControl = this.editorService.activeTextEditorControl; + if (isCodeEditor(activeTextEditorControl)) { + return activeTextEditorControl; } - if (isDiffEditor(activeTextEditorWidget)) { - return activeTextEditorWidget.getModifiedEditor(); + if (isDiffEditor(activeTextEditorControl)) { + return activeTextEditorControl.getModifiedEditor(); + } + + const activeControl = this.editorService.activeEditorPane?.getControl(); + if (isCompositeEditor(activeControl) && isCodeEditor(activeControl.activeCodeEditor)) { + return activeControl.activeCodeEditor; } return null; } - openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { + async openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { // Special case: If the active editor is a diff editor and the request to open originates and // targets the modified side of it, we just apply the request there to prevent opening the modified // side as separate editor. - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; + const activeTextEditorControl = this.editorService.activeTextEditorControl; if ( !sideBySide && // we need the current active group to be the taret - isDiffEditor(activeTextEditorWidget) && // we only support this for active text diff editors + isDiffEditor(activeTextEditorControl) && // we only support this for active text diff editors input.options && // we need options to apply input.resource && // we need a request resource to compare with - activeTextEditorWidget.getModel() && // we need a target model to compare with - source === activeTextEditorWidget.getModifiedEditor() && // we need the source of this request to be the modified side of the diff editor - input.resource.toString() === activeTextEditorWidget.getModel()!.modified.uri.toString() // we need the input resources to match with modified side + activeTextEditorControl.getModel() && // we need a target model to compare with + source === activeTextEditorControl.getModifiedEditor() && // we need the source of this request to be the modified side of the diff editor + input.resource.toString() === activeTextEditorControl.getModel()!.modified.uri.toString() // we need the input resources to match with modified side ) { - const targetEditor = activeTextEditorWidget.getModifiedEditor(); + const targetEditor = activeTextEditorControl.getModifiedEditor(); const textOptions = TextEditorOptions.create(input.options); textOptions.apply(targetEditor, ScrollType.Smooth); - return Promise.resolve(targetEditor); + return targetEditor; } // Open using our normal editor service return this.doOpenCodeEditor(input, source, sideBySide); } - private async doOpenCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { + private async doOpenCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { const control = await this.editorService.openEditor(input, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); if (control) { const widget = control.getControl(); if (isCodeEditor(widget)) { return widget; } + if (isCompositeEditor(widget) && isCodeEditor(widget.activeCodeEditor)) { + return widget.activeCodeEditor; + } } return null; diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index 2583458a30d..c7e5a026ec0 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IResourceInput, ITextEditorOptions, IEditorOptions, EditorActivation } from 'vs/platform/editor/common/editor'; -import { SideBySideEditor as SideBySideEditorChoice, IEditorInput, IEditor, GroupIdentifier, IFileEditorInput, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, EditorInput, SideBySideEditorInput, IEditorInputWithOptions, isEditorInputWithOptions, EditorOptions, TextEditorOptions, IEditorIdentifier, IEditorCloseEvent, ITextEditor, ITextDiffEditor, ITextSideBySideEditor, IRevertOptions, SaveReason, EditorsOrder, isTextEditor, IWorkbenchEditorConfiguration, toResource } from 'vs/workbench/common/editor'; +import { IResourceEditorInput, ITextEditorOptions, IEditorOptions, EditorActivation } from 'vs/platform/editor/common/editor'; +import { SideBySideEditor, IEditorInput, IEditorPane, GroupIdentifier, IFileEditorInput, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, EditorInput, SideBySideEditorInput, IEditorInputWithOptions, isEditorInputWithOptions, EditorOptions, TextEditorOptions, IEditorIdentifier, IEditorCloseEvent, ITextEditorPane, ITextDiffEditorPane, IRevertOptions, SaveReason, EditorsOrder, isTextEditorPane, IWorkbenchEditorConfiguration, toResource, IVisibleEditorPane } from 'vs/workbench/common/editor'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { Registry } from 'vs/platform/registry/common/platform'; import { ResourceMap } from 'vs/base/common/map'; @@ -17,11 +17,11 @@ import { URI } from 'vs/base/common/uri'; import { basename, isEqualOrParent, joinPath } from 'vs/base/common/resources'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IResourceEditor, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler, IVisibleEditor, IEditorService, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, ISaveEditorsOptions, ISaveAllEditorsOptions, IRevertAllEditorsOptions, IBaseSaveRevertAllEditorOptions } from 'vs/workbench/services/editor/common/editorService'; +import { IResourceEditorInputType, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler, IEditorService, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, ISaveEditorsOptions, ISaveAllEditorsOptions, IRevertAllEditorsOptions, IBaseSaveRevertAllEditorOptions } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Disposable, IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { coalesce, distinct } from 'vs/base/common/arrays'; -import { isCodeEditor, isDiffEditor, ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { coalesce, distinct, insert } from 'vs/base/common/arrays'; +import { isCodeEditor, isDiffEditor, ICodeEditor, IDiffEditor, isCompositeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorGroupView, IEditorOpeningEvent, EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { ILabelService } from 'vs/platform/label/common/label'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -84,7 +84,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { this.editorGroupService.whenRestored.then(() => this.onEditorsRestored()); this.editorGroupService.onDidActiveGroupChange(group => this.handleActiveEditorChange(group)); this.editorGroupService.onDidAddGroup(group => this.registerGroupListeners(group as IEditorGroupView)); - this.editorsObserver.onDidChange(() => this._onDidMostRecentlyActiveEditorsChange.fire()); + this.editorsObserver.onDidMostRecentlyActiveEditorsChange(() => this._onDidMostRecentlyActiveEditorsChange.fire()); // Out of workspace file watchers this._register(this.onDidVisibleEditorsChange(() => this.handleVisibleEditorsChange())); @@ -177,8 +177,8 @@ export class EditorService extends Disposable implements EditorServiceImpl { for (const editor of this.visibleEditors) { const resources = distinct(coalesce([ - toResource(editor, { supportSideBySide: SideBySideEditorChoice.MASTER }), - toResource(editor, { supportSideBySide: SideBySideEditorChoice.DETAILS }) + toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }), + toResource(editor, { supportSideBySide: SideBySideEditor.DETAILS }) ]), resource => resource.toString()); for (const resource of resources) { @@ -293,7 +293,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { } private closeOnFileDelete: boolean = false; - private fileInputFactory = Registry.as(EditorExtensions.EditorInputFactories).getFileInputFactory(); + private fileEditorInputFactory = Registry.as(EditorExtensions.EditorInputFactories).getFileEditorInputFactory(); private onConfigurationUpdated(configuration: IWorkbenchEditorConfiguration): void { if (typeof configuration.workbench?.editor?.closeOnFileDelete === 'boolean') { this.closeOnFileDelete = configuration.workbench.editor.closeOnFileDelete; @@ -314,7 +314,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { // - the user has not disabled the setting closeOnFileDelete // - the file change is local // - the input is a file that is not resolved (we need to dispose because we cannot restore otherwise since we do not have the contents) - if (this.closeOnFileDelete || !isExternal || (this.fileInputFactory.isFileInput(editor) && !editor.isResolved())) { + if (this.closeOnFileDelete || !isExternal || (this.fileEditorInputFactory.isFileEditorInput(editor) && !editor.isResolved())) { // Do NOT close any opened editor that matches the resource path (either equal or being parent) of the // resource we move to (movedTo). Otherwise we would close a resource that has been renamed to the same @@ -389,16 +389,19 @@ export class EditorService extends Disposable implements EditorServiceImpl { private readonly editorsObserver = this._register(this.instantiationService.createInstance(EditorsObserver)); - get activeControl(): IVisibleEditor | undefined { - return this.editorGroupService.activeGroup?.activeControl; + get activeEditorPane(): IVisibleEditorPane | undefined { + return this.editorGroupService.activeGroup?.activeEditorPane; } - get activeTextEditorWidget(): ICodeEditor | IDiffEditor | undefined { - const activeControl = this.activeControl; - if (activeControl) { - const activeControlWidget = activeControl.getControl(); - if (isCodeEditor(activeControlWidget) || isDiffEditor(activeControlWidget)) { - return activeControlWidget; + get activeTextEditorControl(): ICodeEditor | IDiffEditor | undefined { + const activeEditorPane = this.activeEditorPane; + if (activeEditorPane) { + const activeControl = activeEditorPane.getControl(); + if (isCodeEditor(activeControl) || isDiffEditor(activeControl)) { + return activeControl; + } + if (isCompositeEditor(activeControl) && isCodeEditor(activeControl.activeCodeEditor)) { + return activeControl.activeCodeEditor; } } @@ -408,11 +411,11 @@ export class EditorService extends Disposable implements EditorServiceImpl { get activeTextEditorMode(): string | undefined { let activeCodeEditor: ICodeEditor | undefined = undefined; - const activeTextEditorWidget = this.activeTextEditorWidget; - if (isDiffEditor(activeTextEditorWidget)) { - activeCodeEditor = activeTextEditorWidget.getModifiedEditor(); + const activeTextEditorControl = this.activeTextEditorControl; + if (isDiffEditor(activeTextEditorControl)) { + activeCodeEditor = activeTextEditorControl.getModifiedEditor(); } else { - activeCodeEditor = activeTextEditorWidget; + activeCodeEditor = activeTextEditorControl; } return activeCodeEditor?.getModel()?.getLanguageIdentifier().language; @@ -446,12 +449,20 @@ export class EditorService extends Disposable implements EditorServiceImpl { return activeGroup ? withNullAsUndefined(activeGroup.activeEditor) : undefined; } - get visibleControls(): IVisibleEditor[] { - return coalesce(this.editorGroupService.groups.map(group => group.activeControl)); + get visibleEditorPanes(): IVisibleEditorPane[] { + return coalesce(this.editorGroupService.groups.map(group => group.activeEditorPane)); } - get visibleTextEditorWidgets(): Array { - return this.visibleControls.map(control => control.getControl() as ICodeEditor | IDiffEditor).filter(widget => isCodeEditor(widget) || isDiffEditor(widget)); + get visibleTextEditorControls(): Array { + const visibleTextEditorControls: Array = []; + for (const visibleEditorPane of this.visibleEditorPanes) { + const control = visibleEditorPane.getControl(); + if (isCodeEditor(control) || isDiffEditor(control)) { + visibleTextEditorControls.push(control); + } + } + + return visibleTextEditorControls; } get visibleEditors(): IEditorInput[] { @@ -465,14 +476,9 @@ export class EditorService extends Disposable implements EditorServiceImpl { private readonly openEditorHandlers: IOpenEditorOverrideHandler[] = []; overrideOpenEditor(handler: IOpenEditorOverrideHandler): IDisposable { - this.openEditorHandlers.push(handler); + const remove = insert(this.openEditorHandlers, handler); - return toDisposable(() => { - const index = this.openEditorHandlers.indexOf(handler); - if (index >= 0) { - this.openEditorHandlers.splice(index, 1); - } - }); + return toDisposable(() => remove()); } private onGroupWillOpenEditor(group: IEditorGroup, event: IEditorOpeningEvent): void { @@ -494,11 +500,10 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#region openEditor() - openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: OpenInEditorGroup): Promise; - openEditor(editor: IResourceInput | IUntitledTextResourceInput, group?: OpenInEditorGroup): Promise; - openEditor(editor: IResourceDiffInput, group?: OpenInEditorGroup): Promise; - openEditor(editor: IResourceSideBySideInput, group?: OpenInEditorGroup): Promise; - async openEditor(editor: IEditorInput | IResourceEditor, optionsOrGroup?: IEditorOptions | ITextEditorOptions | OpenInEditorGroup, group?: OpenInEditorGroup): Promise { + openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: OpenInEditorGroup): Promise; + openEditor(editor: IResourceEditorInput | IUntitledTextResourceEditorInput, group?: OpenInEditorGroup): Promise; + openEditor(editor: IResourceDiffEditorInput, group?: OpenInEditorGroup): Promise; + async openEditor(editor: IEditorInput | IResourceEditorInputType, optionsOrGroup?: IEditorOptions | ITextEditorOptions | OpenInEditorGroup, group?: OpenInEditorGroup): Promise { const result = this.doResolveEditorOpenRequest(editor, optionsOrGroup, group); if (result) { const [resolvedGroup, resolvedEditor, resolvedOptions] = result; @@ -509,7 +514,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { return undefined; } - doResolveEditorOpenRequest(editor: IEditorInput | IResourceEditor, optionsOrGroup?: IEditorOptions | ITextEditorOptions | OpenInEditorGroup, group?: OpenInEditorGroup): [IEditorGroup, EditorInput, EditorOptions | undefined] | undefined { + doResolveEditorOpenRequest(editor: IEditorInput | IResourceEditorInputType, optionsOrGroup?: IEditorOptions | ITextEditorOptions | OpenInEditorGroup, group?: OpenInEditorGroup): [IEditorGroup, EditorInput, EditorOptions | undefined] | undefined { let resolvedGroup: IEditorGroup | undefined; let candidateGroup: OpenInEditorGroup | undefined; @@ -527,8 +532,8 @@ export class EditorService extends Disposable implements EditorServiceImpl { // Untyped Text Editor Support else { - const textInput = editor; - typedEditor = this.createInput(textInput); + const textInput = editor; + typedEditor = this.createEditorInput(textInput); if (typedEditor) { typedOptions = TextEditorOptions.from(textInput); @@ -660,9 +665,9 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#region openEditors() - openEditors(editors: IEditorInputWithOptions[], group?: OpenInEditorGroup): Promise; - openEditors(editors: IResourceEditor[], group?: OpenInEditorGroup): Promise; - async openEditors(editors: Array, group?: OpenInEditorGroup): Promise { + openEditors(editors: IEditorInputWithOptions[], group?: OpenInEditorGroup): Promise; + openEditors(editors: IResourceEditorInputType[], group?: OpenInEditorGroup): Promise; + async openEditors(editors: Array, group?: OpenInEditorGroup): Promise { // Convert to typed editors and options const typedEditors: IEditorInputWithOptions[] = []; @@ -670,7 +675,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { if (isEditorInputWithOptions(editor)) { typedEditors.push(editor); } else { - typedEditors.push({ editor: this.createInput(editor), options: TextEditorOptions.from(editor) }); + typedEditors.push({ editor: this.createEditorInput(editor), options: TextEditorOptions.from(editor) }); } }); @@ -693,7 +698,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { } // Open in target groups - const result: Promise[] = []; + const result: Promise[] = []; mapGroupToEditors.forEach((editorsWithOptions, group) => { result.push(group.openEditors(editorsWithOptions)); }); @@ -705,8 +710,18 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#region isOpen() - isOpen(editor: IEditorInput): boolean { - return this.editorGroupService.groups.some(group => group.isOpened(editor)); + isOpen(editor: IEditorInput): boolean; + isOpen(editor: IResourceEditorInput): boolean; + isOpen(editor: IEditorInput | IResourceEditorInput): boolean { + if (editor instanceof EditorInput) { + return this.editorGroupService.groups.some(group => group.isOpened(editor)); + } + + if (editor.resource) { + return this.editorsObserver.hasEditor(editor.resource); + } + + return false; } //#endregion @@ -731,8 +746,8 @@ export class EditorService extends Disposable implements EditorServiceImpl { const replacementArg = replaceEditorArg as IResourceEditorReplacement; typedEditors.push({ - editor: this.createInput(replacementArg.editor), - replacement: this.createInput(replacementArg.replacement), + editor: this.createEditorInput(replacementArg.editor), + replacement: this.createEditorInput(replacementArg.replacement), options: this.toOptions(replacementArg.replacement.options) }); } @@ -751,9 +766,9 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#region invokeWithinEditorContext() invokeWithinEditorContext(fn: (accessor: ServicesAccessor) => T): T { - const activeTextEditorWidget = this.activeTextEditorWidget; - if (isCodeEditor(activeTextEditorWidget)) { - return activeTextEditorWidget.invokeWithinContext(fn); + const activeTextEditorControl = this.activeTextEditorControl; + if (isCodeEditor(activeTextEditorControl)) { + return activeTextEditorControl.invokeWithinContext(fn); } const activeGroup = this.editorGroupService.activeGroup; @@ -766,11 +781,11 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#endregion - //#region createInput() + //#region createEditorInput() private readonly editorInputCache = new ResourceMap(); - createInput(input: IEditorInputWithOptions | IEditorInput | IResourceEditor): EditorInput { + createEditorInput(input: IEditorInputWithOptions | IEditorInput | IResourceEditorInputType): EditorInput { // Typed Editor Input Support (EditorInput) if (input instanceof EditorInput) { @@ -783,25 +798,11 @@ export class EditorService extends Disposable implements EditorServiceImpl { return editorInputWithOptions.editor; } - // Side by Side Support - const resourceSideBySideInput = input as IResourceSideBySideInput; - if (resourceSideBySideInput.masterResource && resourceSideBySideInput.detailResource) { - const masterInput = this.createInput({ resource: resourceSideBySideInput.masterResource, forceFile: resourceSideBySideInput.forceFile }); - const detailInput = this.createInput({ resource: resourceSideBySideInput.detailResource, forceFile: resourceSideBySideInput.forceFile }); - - return new SideBySideEditorInput( - resourceSideBySideInput.label || this.toSideBySideLabel(detailInput, masterInput, '-'), - resourceSideBySideInput.description, - detailInput, - masterInput - ); - } - // Diff Editor Support - const resourceDiffInput = input as IResourceDiffInput; + const resourceDiffInput = input as IResourceDiffEditorInput; if (resourceDiffInput.leftResource && resourceDiffInput.rightResource) { - const leftInput = this.createInput({ resource: resourceDiffInput.leftResource, forceFile: resourceDiffInput.forceFile }); - const rightInput = this.createInput({ resource: resourceDiffInput.rightResource, forceFile: resourceDiffInput.forceFile }); + const leftInput = this.createEditorInput({ resource: resourceDiffInput.leftResource, forceFile: resourceDiffInput.forceFile }); + const rightInput = this.createEditorInput({ resource: resourceDiffInput.rightResource, forceFile: resourceDiffInput.forceFile }); return new DiffEditorInput( resourceDiffInput.label || this.toSideBySideLabel(leftInput, rightInput, '↔'), @@ -812,7 +813,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { } // Untitled file support - const untitledInput = input as IUntitledTextResourceInput; + const untitledInput = input as IUntitledTextResourceEditorInput; if (untitledInput.forceUntitled || !untitledInput.resource || (untitledInput.resource && untitledInput.resource.scheme === Schemas.untitled)) { const untitledOptions = { mode: untitledInput.mode, @@ -848,22 +849,22 @@ export class EditorService extends Disposable implements EditorServiceImpl { } // Resource Editor Support - const resourceInput = input as IResourceInput; - if (resourceInput.resource instanceof URI) { - let label = resourceInput.label; + const resourceEditorInput = input as IResourceEditorInput; + if (resourceEditorInput.resource instanceof URI) { + let label = resourceEditorInput.label; if (!label) { - label = basename(resourceInput.resource); // derive the label from the path + label = basename(resourceEditorInput.resource); // derive the label from the path } - return this.createOrGetCached(resourceInput.resource, () => { + return this.createOrGetCached(resourceEditorInput.resource, () => { // File - if (resourceInput.forceFile /* fix for https://github.com/Microsoft/vscode/issues/48275 */ || this.fileService.canHandleResource(resourceInput.resource)) { - return this.fileInputFactory.createFileInput(resourceInput.resource, resourceInput.encoding, resourceInput.mode, this.instantiationService); + if (resourceEditorInput.forceFile /* fix for https://github.com/Microsoft/vscode/issues/48275 */ || this.fileService.canHandleResource(resourceEditorInput.resource)) { + return this.fileEditorInputFactory.createFileEditorInput(resourceEditorInput.resource, resourceEditorInput.encoding, resourceEditorInput.mode, this.instantiationService); } // Resource - return this.instantiationService.createInstance(ResourceEditorInput, resourceInput.label, resourceInput.description, resourceInput.resource, resourceInput.mode); + return this.instantiationService.createInstance(ResourceEditorInput, resourceEditorInput.label, resourceEditorInput.description, resourceEditorInput.resource, resourceEditorInput.mode); }, cachedInput => { // Untitled @@ -873,12 +874,12 @@ export class EditorService extends Disposable implements EditorServiceImpl { // Files else if (!(cachedInput instanceof ResourceEditorInput)) { - if (resourceInput.encoding) { - cachedInput.setPreferredEncoding(resourceInput.encoding); + if (resourceEditorInput.encoding) { + cachedInput.setPreferredEncoding(resourceEditorInput.encoding); } - if (resourceInput.mode) { - cachedInput.setPreferredMode(resourceInput.mode); + if (resourceEditorInput.mode) { + cachedInput.setPreferredMode(resourceEditorInput.mode); } } @@ -888,12 +889,12 @@ export class EditorService extends Disposable implements EditorServiceImpl { cachedInput.setName(label); } - if (resourceInput.description) { - cachedInput.setDescription(resourceInput.description); + if (resourceEditorInput.description) { + cachedInput.setDescription(resourceEditorInput.description); } - if (resourceInput.mode) { - cachedInput.setPreferredMode(resourceInput.mode); + if (resourceEditorInput.mode) { + cachedInput.setPreferredMode(resourceEditorInput.mode); } } }) as EditorInput; @@ -934,7 +935,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { // If both editors are file inputs, we produce an optimized label // by adding the relative path of both inputs to the label. This // makes it easier to understand a file-based comparison. - if (this.fileInputFactory.isFileInput(leftInput) && this.fileInputFactory.isFileInput(rightInput)) { + if (this.fileEditorInputFactory.isFileEditorInput(leftInput) && this.fileEditorInputFactory.isFileEditorInput(rightInput)) { return `${this.labelService.getUriLabel(leftResource, { relative: true })} ${divider} ${this.labelService.getUriLabel(rightResource, { relative: true })}`; } @@ -953,6 +954,10 @@ export class EditorService extends Disposable implements EditorServiceImpl { editors = [editors]; } + // Make sure to not save the same editor multiple times + // by using the `matches()` method to find duplicates + const uniqueEditors = this.getUniqueEditors(editors); + // Split editors up into a bucket that is saved in parallel // and sequentially. Unless "Save As", all non-untitled editors // can be saved in parallel to speed up the operation. Remaining @@ -961,9 +966,9 @@ export class EditorService extends Disposable implements EditorServiceImpl { const editorsToSaveParallel: IEditorIdentifier[] = []; const editorsToSaveSequentially: IEditorIdentifier[] = []; if (options?.saveAs) { - editorsToSaveSequentially.push(...editors); + editorsToSaveSequentially.push(...uniqueEditors); } else { - for (const { groupId, editor } of editors) { + for (const { groupId, editor } of uniqueEditors) { if (editor.isUntitled()) { editorsToSaveSequentially.push({ groupId, editor }); } else { @@ -973,7 +978,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { } // Editors to save in parallel - await Promise.all(editorsToSaveParallel.map(({ groupId, editor }) => { + const saveResults = await Promise.all(editorsToSaveParallel.map(({ groupId, editor }) => { // Use save as a hint to pin the editor if used explicitly if (options?.reason === SaveReason.EXPLICIT) { @@ -994,14 +999,16 @@ export class EditorService extends Disposable implements EditorServiceImpl { // is untitled or we "Save As". This also allows the user to review // the contents of the editor before making a decision. let viewState: IEditorViewState | undefined = undefined; - const control = await this.openEditor(editor, undefined, groupId); - if (isTextEditor(control)) { - viewState = control.getViewState(); + const editorPane = await this.openEditor(editor, undefined, groupId); + if (isTextEditorPane(editorPane)) { + viewState = editorPane.getViewState(); } const result = options?.saveAs ? await editor.saveAs(groupId, options) : await editor.save(groupId, options); + saveResults.push(result); + if (!result) { - return false; // failed or cancelled, abort + break; // failed or cancelled, abort } // Replace editor preserving viewstate (either across all groups or @@ -1015,35 +1022,34 @@ export class EditorService extends Disposable implements EditorServiceImpl { } } - return true; + return saveResults.every(result => !!result); } saveAll(options?: ISaveAllEditorsOptions): Promise { return this.save(this.getAllDirtyEditors(options), options); } - async revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise { + async revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise { // Convert to array if (!Array.isArray(editors)) { editors = [editors]; } - const result = await Promise.all(editors.map(async ({ groupId, editor }) => { - if (editor.isDisposed()) { - return true; // might have been disposed from from the revert already - } + // Make sure to not revert the same editor multiple times + // by using the `matches()` method to find duplicates + const uniqueEditors = this.getUniqueEditors(editors); + + await Promise.all(uniqueEditors.map(async ({ groupId, editor }) => { // Use revert as a hint to pin the editor this.editorGroupService.getGroup(groupId)?.pinEditor(editor); return editor.revert(groupId, options); })); - - return result.every(success => !!success); } - async revertAll(options?: IRevertAllEditorsOptions): Promise { + async revertAll(options?: IRevertAllEditorsOptions): Promise { return this.revert(this.getAllDirtyEditors(options), options); } @@ -1061,6 +1067,19 @@ export class EditorService extends Disposable implements EditorServiceImpl { return editors; } + private getUniqueEditors(editors: IEditorIdentifier[]): IEditorIdentifier[] { + const uniqueEditors: IEditorIdentifier[] = []; + for (const { editor, groupId } of editors) { + if (uniqueEditors.some(uniqueEditor => uniqueEditor.editor.matches(editor))) { + continue; + } + + uniqueEditors.push({ editor, groupId }); + } + + return uniqueEditors; + } + //#endregion dispose(): void { @@ -1074,11 +1093,11 @@ export class EditorService extends Disposable implements EditorServiceImpl { export interface IEditorOpenHandler { ( - delegate: (group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions) => Promise, + delegate: (group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions) => Promise, group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions - ): Promise; + ): Promise; } /** @@ -1095,25 +1114,24 @@ export class DelegatingEditorService implements IEditorService { @IEditorService private editorService: EditorService ) { } - openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: OpenInEditorGroup): Promise; - openEditor(editor: IResourceInput | IUntitledTextResourceInput, group?: OpenInEditorGroup): Promise; - openEditor(editor: IResourceDiffInput, group?: OpenInEditorGroup): Promise; - openEditor(editor: IResourceSideBySideInput, group?: OpenInEditorGroup): Promise; - async openEditor(editor: IEditorInput | IResourceEditor, optionsOrGroup?: IEditorOptions | ITextEditorOptions | OpenInEditorGroup, group?: OpenInEditorGroup): Promise { + openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: OpenInEditorGroup): Promise; + openEditor(editor: IResourceEditorInput | IUntitledTextResourceEditorInput, group?: OpenInEditorGroup): Promise; + openEditor(editor: IResourceDiffEditorInput, group?: OpenInEditorGroup): Promise; + async openEditor(editor: IEditorInput | IResourceEditorInputType, optionsOrGroup?: IEditorOptions | ITextEditorOptions | OpenInEditorGroup, group?: OpenInEditorGroup): Promise { const result = this.editorService.doResolveEditorOpenRequest(editor, optionsOrGroup, group); if (result) { const [resolvedGroup, resolvedEditor, resolvedOptions] = result; // Pass on to editor open handler - const control = await this.editorOpenHandler( + const editorPane = await this.editorOpenHandler( (group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions) => group.openEditor(editor, options), resolvedGroup, resolvedEditor, resolvedOptions ); - if (control) { - return control; // the opening was handled, so return early + if (editorPane) { + return editorPane; // the opening was handled, so return early } return withNullAsUndefined(await resolvedGroup.openEditor(resolvedEditor, resolvedOptions)); @@ -1128,20 +1146,20 @@ export class DelegatingEditorService implements IEditorService { get onDidVisibleEditorsChange(): Event { return this.editorService.onDidVisibleEditorsChange; } get activeEditor(): IEditorInput | undefined { return this.editorService.activeEditor; } - get activeControl(): IVisibleEditor | undefined { return this.editorService.activeControl; } - get activeTextEditorWidget(): ICodeEditor | IDiffEditor | undefined { return this.editorService.activeTextEditorWidget; } + get activeEditorPane(): IVisibleEditorPane | undefined { return this.editorService.activeEditorPane; } + get activeTextEditorControl(): ICodeEditor | IDiffEditor | undefined { return this.editorService.activeTextEditorControl; } get activeTextEditorMode(): string | undefined { return this.editorService.activeTextEditorMode; } get visibleEditors(): ReadonlyArray { return this.editorService.visibleEditors; } - get visibleControls(): ReadonlyArray { return this.editorService.visibleControls; } - get visibleTextEditorWidgets(): ReadonlyArray { return this.editorService.visibleTextEditorWidgets; } + get visibleEditorPanes(): ReadonlyArray { return this.editorService.visibleEditorPanes; } + get visibleTextEditorControls(): ReadonlyArray { return this.editorService.visibleTextEditorControls; } get editors(): ReadonlyArray { return this.editorService.editors; } get count(): number { return this.editorService.count; } getEditors(order: EditorsOrder): ReadonlyArray { return this.editorService.getEditors(order); } - openEditors(editors: IEditorInputWithOptions[], group?: OpenInEditorGroup): Promise; - openEditors(editors: IResourceEditor[], group?: OpenInEditorGroup): Promise; - openEditors(editors: Array, group?: OpenInEditorGroup): Promise { + openEditors(editors: IEditorInputWithOptions[], group?: OpenInEditorGroup): Promise; + openEditors(editors: IResourceEditorInputType[], group?: OpenInEditorGroup): Promise; + openEditors(editors: Array, group?: OpenInEditorGroup): Promise { return this.editorService.openEditors(editors, group); } @@ -1151,19 +1169,21 @@ export class DelegatingEditorService implements IEditorService { return this.editorService.replaceEditors(editors as IResourceEditorReplacement[] /* TS fail */, group); } - isOpen(editor: IEditorInput): boolean { return this.editorService.isOpen(editor); } + isOpen(editor: IEditorInput): boolean; + isOpen(editor: IResourceEditorInput): boolean; + isOpen(editor: IEditorInput | IResourceEditorInput): boolean { return this.editorService.isOpen(editor as IResourceEditorInput /* TS fail */); } overrideOpenEditor(handler: IOpenEditorOverrideHandler): IDisposable { return this.editorService.overrideOpenEditor(handler); } invokeWithinEditorContext(fn: (accessor: ServicesAccessor) => T): T { return this.editorService.invokeWithinEditorContext(fn); } - createInput(input: IResourceEditor): IEditorInput { return this.editorService.createInput(input); } + createEditorInput(input: IResourceEditorInputType): IEditorInput { return this.editorService.createEditorInput(input); } save(editors: IEditorIdentifier | IEditorIdentifier[], options?: ISaveEditorsOptions): Promise { return this.editorService.save(editors, options); } saveAll(options?: ISaveAllEditorsOptions): Promise { return this.editorService.saveAll(options); } - revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise { return this.editorService.revert(editors, options); } - revertAll(options?: IRevertAllEditorsOptions): Promise { return this.editorService.revertAll(options); } + revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise { return this.editorService.revert(editors, options); } + revertAll(options?: IRevertAllEditorsOptions): Promise { return this.editorService.revertAll(options); } //#endregion } diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index 20e6bfbb1ae..04b360d3cab 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -5,10 +5,9 @@ import { Event } from 'vs/base/common/event'; import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorInput, IEditor, GroupIdentifier, IEditorInputWithOptions, CloseDirection, IEditorPartOptions, IEditorPartOptionsChangeEvent, EditorsOrder } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditorPane, GroupIdentifier, IEditorInputWithOptions, CloseDirection, IEditorPartOptions, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane } from 'vs/workbench/common/editor'; import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IVisibleEditor } from 'vs/workbench/services/editor/common/editorService'; import { IDimension } from 'vs/editor/common/editorCommon'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -403,13 +402,13 @@ export interface IEditorGroup { readonly ariaLabel: string; /** - * The active control is the currently visible control of the group. + * The active editor pane is the currently visible editor pane of the group. */ - readonly activeControl: IVisibleEditor | undefined; + readonly activeEditorPane: IVisibleEditorPane | undefined; /** * The active editor is the currently visible editor of the group - * within the current active control. + * within the current active editor pane. */ readonly activeEditor: IEditorInput | null; @@ -452,7 +451,7 @@ export interface IEditorGroup { * @returns a promise that resolves around an IEditor instance unless * the call failed, or the editor was not opened as active editor. */ - openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions): Promise; + openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions): Promise; /** * Opens editors in this group. @@ -462,7 +461,7 @@ export interface IEditorGroup { * a group can only ever have one active editor, even if many editors are * opened, the result will only be one editor. */ - openEditors(editors: IEditorInputWithOptions[]): Promise; + openEditors(editors: IEditorInputWithOptions[]): Promise; /** * Find out if the provided editor is opened in the group. diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index 5f6bb85eff3..4b401b8acce 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -4,20 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IResourceInput, IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; -import { IEditorInput, IEditor, GroupIdentifier, IEditorInputWithOptions, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, ITextEditor, ITextDiffEditor, ITextSideBySideEditor, IEditorIdentifier, ISaveOptions, IRevertOptions, EditorsOrder } from 'vs/workbench/common/editor'; +import { IResourceEditorInput, IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { IEditorInput, IEditorPane, GroupIdentifier, IEditorInputWithOptions, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, ITextEditorPane, ITextDiffEditorPane, IEditorIdentifier, ISaveOptions, IRevertOptions, EditorsOrder, IVisibleEditorPane } from 'vs/workbench/common/editor'; import { Event } from 'vs/base/common/event'; -import { IEditor as ICodeEditor, IDiffEditor } from 'vs/editor/common/editorCommon'; +import { IEditor, IDiffEditor } from 'vs/editor/common/editorCommon'; import { IEditorGroup, IEditorReplacement } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IDisposable } from 'vs/base/common/lifecycle'; export const IEditorService = createDecorator('editorService'); -export type IResourceEditor = IResourceInput | IUntitledTextResourceInput | IResourceDiffInput | IResourceSideBySideInput; +export type IResourceEditorInputType = IResourceEditorInput | IUntitledTextResourceEditorInput | IResourceDiffEditorInput; export interface IResourceEditorReplacement { - editor: IResourceEditor; - replacement: IResourceEditor; + readonly editor: IResourceEditorInputType; + readonly replacement: IResourceEditorInputType; } export const ACTIVE_GROUP = -1; @@ -36,12 +36,7 @@ export interface IOpenEditorOverride { * If defined, will prevent the opening of an editor and replace the resulting * promise with the provided promise for the openEditor() call. */ - override?: Promise; -} - -export interface IVisibleEditor extends IEditor { - input: IEditorInput; - group: IEditorGroup; + override?: Promise; } export interface ISaveEditorsOptions extends ISaveOptions { @@ -49,7 +44,7 @@ export interface ISaveEditorsOptions extends ISaveOptions { /** * If true, will ask for a location of the editor to save to. */ - saveAs?: boolean; + readonly saveAs?: boolean; } export interface IBaseSaveRevertAllEditorOptions { @@ -57,7 +52,7 @@ export interface IBaseSaveRevertAllEditorOptions { /** * Whether to include untitled editors as well. */ - includeUntitled?: boolean; + readonly includeUntitled?: boolean; } export interface ISaveAllEditorsOptions extends ISaveEditorsOptions, IBaseSaveRevertAllEditorOptions { } @@ -71,17 +66,25 @@ export interface IEditorService { /** * Emitted when the currently active editor changes. * - * @see `IEditorService.activeEditor` + * @see `IEditorService.activeEditorPane` */ readonly onDidActiveEditorChange: Event; /** * Emitted when any of the current visible editors changes. * - * @see `IEditorService.visibleEditors` + * @see `IEditorService.visibleEditorPanes` */ readonly onDidVisibleEditorsChange: Event; + /** + * The currently active editor pane or `undefined` if none. The editor pane is + * the workbench container for editors of any kind. + * + * @see `IEditorService.activeEditor` for access to the active editor input + */ + readonly activeEditorPane: IVisibleEditorPane | undefined; + /** * The currently active editor or `undefined` if none. An editor is active when it is * located in the currently active editor group. It will be `undefined` if the active @@ -90,44 +93,38 @@ export interface IEditorService { readonly activeEditor: IEditorInput | undefined; /** - * The currently active editor control or `undefined` if none. The editor control is - * the workbench container for editors of any kind. - * - * @see `IEditorService.activeEditor` - */ - readonly activeControl: IVisibleEditor | undefined; - - /** - * The currently active text editor widget or `undefined` if there is currently no active + * The currently active text editor control or `undefined` if there is currently no active * editor or the active editor widget is neither a text nor a diff editor. * * @see `IEditorService.activeEditor` */ - readonly activeTextEditorWidget: ICodeEditor | IDiffEditor | undefined; + readonly activeTextEditorControl: IEditor | IDiffEditor | undefined; /** * The currently active text editor mode or `undefined` if there is currently no active - * editor or the active editor widget is neither a text nor a diff editor. If the active + * editor or the active editor control is neither a text nor a diff editor. If the active * editor is a diff editor, the modified side's mode will be taken. */ readonly activeTextEditorMode: string | undefined; + /** + * All editor panes that are currently visible across all editor groups. + * + * @see `IEditorService.visibleEditors` for access to the visible editor inputs + */ + readonly visibleEditorPanes: ReadonlyArray; + /** * All editors that are currently visible. An editor is visible when it is opened in an * editor group and active in that group. Multiple editor groups can be opened at the same time. */ readonly visibleEditors: ReadonlyArray; - /** - * All editor controls that are currently visible across all editor groups. - */ - readonly visibleControls: ReadonlyArray; - /** * All text editor widgets that are currently visible across all editor groups. A text editor * widget is either a text or a diff editor. */ - readonly visibleTextEditorWidgets: ReadonlyArray; + readonly visibleTextEditorControls: ReadonlyArray; /** * All editors that are opened across all editor groups in sequential order @@ -162,10 +159,9 @@ export interface IEditorService { * @returns the editor that opened or `undefined` if the operation failed or the editor was not * opened to be active. */ - openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceInput | IUntitledTextResourceInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceDiffInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceSideBySideInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceEditorInput | IUntitledTextResourceEditorInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceDiffEditorInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; /** * Open editors in an editor group. @@ -178,8 +174,8 @@ export interface IEditorService { * @returns the editors that opened. The array can be empty or have less elements for editors * that failed to open or were instructed to open as inactive. */ - openEditors(editors: IEditorInputWithOptions[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise>; - openEditors(editors: IResourceEditor[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise>; + openEditors(editors: IEditorInputWithOptions[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise>; + openEditors(editors: IResourceEditorInputType[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise>; /** * Replaces editors in an editor group with the provided replacement. @@ -196,7 +192,13 @@ export interface IEditorService { * Find out if the provided editor is opened in any editor group. * * Note: An editor can be opened but not actively visible. + * + * @param editor the editor to check for being opened. If a + * `IResourceEditorInput` is passed in, the resource is checked on + * all opened editors. In case of a side by side editor, the + * right hand side resource is considered only. */ + isOpen(editor: IResourceEditorInput): boolean; isOpen(editor: IEditorInput): boolean; /** @@ -214,25 +216,29 @@ export interface IEditorService { /** * Converts a lightweight input to a workbench editor input. */ - createInput(input: IResourceEditor): IEditorInput; + createEditorInput(input: IResourceEditorInputType): IEditorInput; /** * Save the provided list of editors. + * + * @returns `true` if all editors saved and `false` otherwise. */ save(editors: IEditorIdentifier | IEditorIdentifier[], options?: ISaveEditorsOptions): Promise; /** * Save all editors. + * + * @returns `true` if all editors saved and `false` otherwise. */ saveAll(options?: ISaveAllEditorsOptions): Promise; /** * Reverts the provided list of editors. */ - revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise; + revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise; /** * Reverts all editors. */ - revertAll(options?: IRevertAllEditorsOptions): Promise; + revertAll(options?: IRevertAllEditorsOptions): Promise; } diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts index 558339f6357..6a015f24b33 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -123,7 +123,7 @@ suite('EditorGroupsService', () => { assert.equal(groupAddedCounter, 2); assert.equal(part.groups.length, 3); assert.ok(part.activeGroup === rightGroup); - assert.ok(!downGroup.activeControl); + assert.ok(!downGroup.activeEditorPane); assert.equal(rootGroup.label, 'Group 1'); assert.equal(rightGroup.label, 'Group 2'); assert.equal(downGroup.label, 'Group 3'); @@ -425,7 +425,7 @@ suite('EditorGroupsService', () => { assert.ok(!group.previewEditor); assert.equal(group.activeEditor, input); - assert.equal(group.activeControl?.getId(), TEST_EDITOR_ID); + assert.equal(group.activeEditorPane?.getId(), TEST_EDITOR_ID); assert.equal(group.count, 2); const mru = group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE); diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index 26d6709a157..90f3c5fee04 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorInput, EditorsOrder, SideBySideEditorInput } from 'vs/workbench/common/editor'; -import { workbenchInstantiationService, TestStorageService, TestServiceAccessor, registerTestEditor, TestFileEditorInput } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor, registerTestEditor, TestFileEditorInput } from 'vs/workbench/test/browser/workbenchTestServices'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { EditorService, DelegatingEditorService } from 'vs/workbench/services/editor/browser/editorService'; @@ -27,6 +27,7 @@ import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { UntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; import { NullFileSystemProvider } from 'vs/platform/files/test/common/nullFileSystemProvider'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; +import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; const TEST_EDITOR_ID = 'MyTestEditorForEditorService'; const TEST_EDITOR_INPUT_ID = 'testEditorInputForEditorService'; @@ -94,17 +95,18 @@ suite('EditorService', () => { let editor = await service.openEditor(input, { pinned: true }); assert.equal(editor?.getId(), TEST_EDITOR_ID); - assert.equal(editor, service.activeControl); + assert.equal(editor, service.activeEditorPane); assert.equal(1, service.count); assert.equal(input, service.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[0].editor); assert.equal(input, service.getEditors(EditorsOrder.SEQUENTIAL)[0].editor); assert.equal(input, service.activeEditor); - assert.equal(service.visibleControls.length, 1); - assert.equal(service.visibleControls[0], editor); - assert.ok(!service.activeTextEditorWidget); + assert.equal(service.visibleEditorPanes.length, 1); + assert.equal(service.visibleEditorPanes[0], editor); + assert.ok(!service.activeTextEditorControl); assert.ok(!service.activeTextEditorMode); - assert.equal(service.visibleTextEditorWidgets.length, 0); + assert.equal(service.visibleTextEditorControls.length, 0); assert.equal(service.isOpen(input), true); + assert.equal(service.isOpen({ resource: input.resource }), true); assert.equal(activeEditorChangeEventCounter, 1); assert.equal(visibleEditorChangeEventCounter, 1); @@ -135,9 +137,11 @@ suite('EditorService', () => { assert.equal(input, service.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[1].editor); assert.equal(input, service.getEditors(EditorsOrder.SEQUENTIAL)[0].editor); assert.equal(otherInput, service.getEditors(EditorsOrder.SEQUENTIAL)[1].editor); - assert.equal(service.visibleControls.length, 1); + assert.equal(service.visibleEditorPanes.length, 1); assert.equal(service.isOpen(input), true); + assert.equal(service.isOpen({ resource: input.resource }), true); assert.equal(service.isOpen(otherInput), true); + assert.equal(service.isOpen({ resource: otherInput.resource }), true); assert.equal(activeEditorChangeEventCounter, 4); assert.equal(visibleEditorChangeEventCounter, 4); @@ -149,6 +153,53 @@ suite('EditorService', () => { part.dispose(); }); + test('isOpen() with side by side editor', async () => { + const [part, service] = createEditorService(); + + const input = new TestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID); + const otherInput = new TestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID); + const sideBySideInput = new SideBySideEditorInput('sideBySide', '', input, otherInput); + + await part.whenRestored; + + const editor1 = await service.openEditor(sideBySideInput, { pinned: true }); + assert.equal(part.activeGroup.count, 1); + + assert.equal(service.isOpen(input), false); + assert.equal(service.isOpen(otherInput), false); + assert.equal(service.isOpen(sideBySideInput), true); + assert.equal(service.isOpen({ resource: input.resource }), false); + assert.equal(service.isOpen({ resource: otherInput.resource }), true); + + const editor2 = await service.openEditor(input, { pinned: true }); + assert.equal(part.activeGroup.count, 2); + + assert.equal(service.isOpen(input), true); + assert.equal(service.isOpen(otherInput), false); + assert.equal(service.isOpen(sideBySideInput), true); + assert.equal(service.isOpen({ resource: input.resource }), true); + assert.equal(service.isOpen({ resource: otherInput.resource }), true); + + await editor2?.group?.closeEditor(input); + assert.equal(part.activeGroup.count, 1); + + assert.equal(service.isOpen(input), false); + assert.equal(service.isOpen(otherInput), false); + assert.equal(service.isOpen(sideBySideInput), true); + assert.equal(service.isOpen({ resource: input.resource }), false); + assert.equal(service.isOpen({ resource: otherInput.resource }), true); + + await editor1?.group?.closeEditor(sideBySideInput); + + assert.equal(service.isOpen(input), false); + assert.equal(service.isOpen(otherInput), false); + assert.equal(service.isOpen(sideBySideInput), false); + assert.equal(service.isOpen({ resource: input.resource }), false); + assert.equal(service.isOpen({ resource: otherInput.resource }), false); + + part.dispose(); + }); + test('openEditors() / replaceEditors()', async () => { const [part, service] = createEditorService(); @@ -176,50 +227,50 @@ suite('EditorService', () => { // Cached Input (Files) const fileResource1 = toResource.call(this, '/foo/bar/cache1.js'); - const fileInput1 = service.createInput({ resource: fileResource1 }); - assert.ok(fileInput1); + const fileEditorInput1 = service.createEditorInput({ resource: fileResource1 }); + assert.ok(fileEditorInput1); const fileResource2 = toResource.call(this, '/foo/bar/cache2.js'); - const fileInput2 = service.createInput({ resource: fileResource2 }); - assert.ok(fileInput2); + const fileEditorInput2 = service.createEditorInput({ resource: fileResource2 }); + assert.ok(fileEditorInput2); - assert.notEqual(fileInput1, fileInput2); + assert.notEqual(fileEditorInput1, fileEditorInput2); - const fileInput1Again = service.createInput({ resource: fileResource1 }); - assert.equal(fileInput1Again, fileInput1); + const fileEditorInput1Again = service.createEditorInput({ resource: fileResource1 }); + assert.equal(fileEditorInput1Again, fileEditorInput1); - fileInput1Again!.dispose(); + fileEditorInput1Again!.dispose(); - assert.ok(fileInput1!.isDisposed()); + assert.ok(fileEditorInput1!.isDisposed()); - const fileInput1AgainAndAgain = service.createInput({ resource: fileResource1 }); - assert.notEqual(fileInput1AgainAndAgain, fileInput1); - assert.ok(!fileInput1AgainAndAgain!.isDisposed()); + const fileEditorInput1AgainAndAgain = service.createEditorInput({ resource: fileResource1 }); + assert.notEqual(fileEditorInput1AgainAndAgain, fileEditorInput1); + assert.ok(!fileEditorInput1AgainAndAgain!.isDisposed()); // Cached Input (Resource) const resource1 = URI.from({ scheme: 'custom', path: '/foo/bar/cache1.js' }); - const input1 = service.createInput({ resource: resource1 }); + const input1 = service.createEditorInput({ resource: resource1 }); assert.ok(input1); const resource2 = URI.from({ scheme: 'custom', path: '/foo/bar/cache2.js' }); - const input2 = service.createInput({ resource: resource2 }); + const input2 = service.createEditorInput({ resource: resource2 }); assert.ok(input2); assert.notEqual(input1, input2); - const input1Again = service.createInput({ resource: resource1 }); + const input1Again = service.createEditorInput({ resource: resource1 }); assert.equal(input1Again, input1); input1Again!.dispose(); assert.ok(input1!.isDisposed()); - const input1AgainAndAgain = service.createInput({ resource: resource1 }); + const input1AgainAndAgain = service.createEditorInput({ resource: resource1 }); assert.notEqual(input1AgainAndAgain, input1); assert.ok(!input1AgainAndAgain!.isDisposed()); }); - test('createInput', async function () { + test('createEditorInput', async function () { const instantiationService = workbenchInstantiationService(); const service = instantiationService.createInstance(EditorService); @@ -229,81 +280,74 @@ suite('EditorService', () => { }); // Untyped Input (file) - let input = service.createInput({ resource: toResource.call(this, '/index.html'), options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + let input = service.createEditorInput({ resource: toResource.call(this, '/index.html'), options: { selection: { startLineNumber: 1, startColumn: 1 } } }); assert(input instanceof FileEditorInput); let contentInput = input; assert.strictEqual(contentInput.resource.fsPath, toResource.call(this, '/index.html').fsPath); // Typed Input - assert.equal(service.createInput(input), input); - assert.equal(service.createInput({ editor: input }), input); + assert.equal(service.createEditorInput(input), input); + assert.equal(service.createEditorInput({ editor: input }), input); // Untyped Input (file, encoding) - input = service.createInput({ resource: toResource.call(this, '/index.html'), encoding: 'utf16le', options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + input = service.createEditorInput({ resource: toResource.call(this, '/index.html'), encoding: 'utf16le', options: { selection: { startLineNumber: 1, startColumn: 1 } } }); assert(input instanceof FileEditorInput); contentInput = input; assert.equal(contentInput.getPreferredEncoding(), 'utf16le'); // Untyped Input (file, mode) - input = service.createInput({ resource: toResource.call(this, '/index.html'), mode }); + input = service.createEditorInput({ resource: toResource.call(this, '/index.html'), mode }); assert(input instanceof FileEditorInput); contentInput = input; assert.equal(contentInput.getPreferredMode(), mode); // Untyped Input (file, different mode) - input = service.createInput({ resource: toResource.call(this, '/index.html'), mode: 'text' }); + input = service.createEditorInput({ resource: toResource.call(this, '/index.html'), mode: 'text' }); assert(input instanceof FileEditorInput); contentInput = input; assert.equal(contentInput.getPreferredMode(), 'text'); // Untyped Input (untitled) - input = service.createInput({ options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + input = service.createEditorInput({ options: { selection: { startLineNumber: 1, startColumn: 1 } } }); assert(input instanceof UntitledTextEditorInput); // Untyped Input (untitled with contents) - input = service.createInput({ contents: 'Hello Untitled', options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + input = service.createEditorInput({ contents: 'Hello Untitled', options: { selection: { startLineNumber: 1, startColumn: 1 } } }); assert(input instanceof UntitledTextEditorInput); let model = await input.resolve() as UntitledTextEditorModel; assert.equal(model.textEditorModel!.getValue(), 'Hello Untitled'); // Untyped Input (untitled with mode) - input = service.createInput({ mode, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + input = service.createEditorInput({ mode, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); assert(input instanceof UntitledTextEditorInput); model = await input.resolve() as UntitledTextEditorModel; assert.equal(model.getMode(), mode); // Untyped Input (untitled with file path) - input = service.createInput({ resource: URI.file('/some/path.txt'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + input = service.createEditorInput({ resource: URI.file('/some/path.txt'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); assert(input instanceof UntitledTextEditorInput); assert.ok((input as UntitledTextEditorInput).model.hasAssociatedFilePath); // Untyped Input (untitled with untitled resource) - input = service.createInput({ resource: URI.parse('untitled://Untitled-1'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + input = service.createEditorInput({ resource: URI.parse('untitled://Untitled-1'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); assert(input instanceof UntitledTextEditorInput); assert.ok(!(input as UntitledTextEditorInput).model.hasAssociatedFilePath); // Untyped Input (untitled with custom resource) const provider = instantiationService.createInstance(FileServiceProvider, 'untitled-custom'); - input = service.createInput({ resource: URI.parse('untitled-custom://some/path'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + input = service.createEditorInput({ resource: URI.parse('untitled-custom://some/path'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); assert(input instanceof UntitledTextEditorInput); assert.ok((input as UntitledTextEditorInput).model.hasAssociatedFilePath); provider.dispose(); // Untyped Input (resource) - input = service.createInput({ resource: URI.parse('custom:resource') }); + input = service.createEditorInput({ resource: URI.parse('custom:resource') }); assert(input instanceof ResourceEditorInput); - // Untyped Input (side by side) - input = service.createInput({ - masterResource: toResource.call(this, '/master.html'), - detailResource: toResource.call(this, '/detail.html') - }); - assert(input instanceof SideBySideEditorInput); - // Untyped Input (diff) - input = service.createInput({ + input = service.createEditorInput({ leftResource: toResource.call(this, '/master.html'), rightResource: toResource.call(this, '/detail.html') }); @@ -689,7 +733,7 @@ suite('EditorService', () => { part.dispose(); }); - test('activeTextEditorWidget / activeTextEditorMode', async () => { + test('activeTextEditorControl / activeTextEditorMode', async () => { const [part, service] = createEditorService(); await part.whenRestored; @@ -697,8 +741,8 @@ suite('EditorService', () => { // Open untitled input let editor = await service.openEditor({}); - assert.equal(service.activeControl, editor); - assert.equal(service.activeTextEditorWidget, editor?.getControl()); + assert.equal(service.activeEditorPane, editor); + assert.equal(service.activeTextEditorControl, editor?.getControl()); assert.equal(service.activeTextEditorMode, 'plaintext'); part.dispose(); @@ -733,6 +777,8 @@ suite('EditorService', () => { input1.dirty = true; const input2 = new TestFileEditorInput(URI.parse('my://resource2'), TEST_EDITOR_INPUT_ID); input2.dirty = true; + const sameInput1 = new TestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); + sameInput1.dirty = true; const rootGroup = part.activeGroup; @@ -740,24 +786,50 @@ suite('EditorService', () => { await service.openEditor(input1, { pinned: true }); await service.openEditor(input2, { pinned: true }); + await service.openEditor(sameInput1, { pinned: true }, SIDE_GROUP); await service.save({ groupId: rootGroup.id, editor: input1 }); assert.equal(input1.gotSaved, true); + input1.gotSaved = false; + input1.gotSavedAs = false; + input1.gotReverted = false; + await service.save({ groupId: rootGroup.id, editor: input1 }, { saveAs: true }); assert.equal(input1.gotSavedAs, true); + input1.gotSaved = false; + input1.gotSavedAs = false; + input1.gotReverted = false; + await service.revertAll(); assert.equal(input1.gotReverted, true); + input1.gotSaved = false; + input1.gotSavedAs = false; + input1.gotReverted = false; + await service.saveAll(); assert.equal(input1.gotSaved, true); assert.equal(input2.gotSaved, true); + input1.gotSaved = false; + input1.gotSavedAs = false; + input1.gotReverted = false; + input2.gotSaved = false; + input2.gotSavedAs = false; + input2.gotReverted = false; + await service.saveAll({ saveAs: true }); + assert.equal(input1.gotSavedAs, true); assert.equal(input2.gotSavedAs, true); + // services dedupes inputs automatically + assert.equal(sameInput1.gotSaved, false); + assert.equal(sameInput1.gotSavedAs, false); + assert.equal(sameInput1.gotReverted, false); + part.dispose(); }); diff --git a/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts b/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts index 2f25fec289d..c0d0d905221 100644 --- a/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { EditorOptions, IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; -import { workbenchInstantiationService, TestStorageService, TestFileEditorInput, registerTestEditor } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestFileEditorInput, registerTestEditor, TestEditorPart } from 'vs/workbench/test/browser/workbenchTestServices'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -16,6 +16,7 @@ import { WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { EditorsObserver } from 'vs/workbench/browser/parts/editor/editorsObserver'; import { timeout } from 'vs/base/common/async'; +import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; const TEST_EDITOR_ID = 'MyTestEditorForEditorsObserver'; const TEST_EDITOR_INPUT_ID = 'testEditorInputForEditorsObserver'; @@ -34,11 +35,11 @@ suite('EditorsObserver', function () { disposables = []; }); - async function createPart(): Promise { + async function createPart(): Promise { const instantiationService = workbenchInstantiationService(); instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); - const part = instantiationService.createInstance(EditorPart); + const part = instantiationService.createInstance(TestEditorPart); part.create(document.createElement('div')); part.layout(400, 300); @@ -58,14 +59,14 @@ suite('EditorsObserver', function () { test('basics (single group)', async () => { const [part, observer] = await createEditorObserver(); - let observerChangeListenerCalled = false; - const listener = observer.onDidChange(() => { - observerChangeListenerCalled = true; + let onDidMostRecentlyActiveEditorsChangeCalled = false; + const listener = observer.onDidMostRecentlyActiveEditorsChange(() => { + onDidMostRecentlyActiveEditorsChangeCalled = true; }); let currentEditorsMRU = observer.editors; assert.equal(currentEditorsMRU.length, 0); - assert.equal(observerChangeListenerCalled, false); + assert.equal(onDidMostRecentlyActiveEditorsChangeCalled, false); const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); @@ -75,7 +76,8 @@ suite('EditorsObserver', function () { assert.equal(currentEditorsMRU.length, 1); assert.equal(currentEditorsMRU[0].groupId, part.activeGroup.id); assert.equal(currentEditorsMRU[0].editor, input1); - assert.equal(observerChangeListenerCalled, true); + assert.equal(onDidMostRecentlyActiveEditorsChangeCalled, true); + assert.equal(observer.hasEditor(input1.resource), true); const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); @@ -91,6 +93,8 @@ suite('EditorsObserver', function () { assert.equal(currentEditorsMRU[1].editor, input2); assert.equal(currentEditorsMRU[2].groupId, part.activeGroup.id); assert.equal(currentEditorsMRU[2].editor, input1); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), true); await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); @@ -102,8 +106,11 @@ suite('EditorsObserver', function () { assert.equal(currentEditorsMRU[1].editor, input3); assert.equal(currentEditorsMRU[2].groupId, part.activeGroup.id); assert.equal(currentEditorsMRU[2].editor, input1); + assert.equal(observer.hasEditor(input1.resource), true); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), true); - observerChangeListenerCalled = false; + onDidMostRecentlyActiveEditorsChangeCalled = false; await part.activeGroup.closeEditor(input1); currentEditorsMRU = observer.editors; @@ -112,11 +119,17 @@ suite('EditorsObserver', function () { assert.equal(currentEditorsMRU[0].editor, input2); assert.equal(currentEditorsMRU[1].groupId, part.activeGroup.id); assert.equal(currentEditorsMRU[1].editor, input3); - assert.equal(observerChangeListenerCalled, true); + assert.equal(onDidMostRecentlyActiveEditorsChangeCalled, true); + assert.equal(observer.hasEditor(input1.resource), false); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), true); await part.activeGroup.closeAllEditors(); currentEditorsMRU = observer.editors; assert.equal(currentEditorsMRU.length, 0); + assert.equal(observer.hasEditor(input1.resource), false); + assert.equal(observer.hasEditor(input2.resource), false); + assert.equal(observer.hasEditor(input3.resource), false); part.dispose(); listener.dispose(); @@ -143,6 +156,7 @@ suite('EditorsObserver', function () { assert.equal(currentEditorsMRU[0].editor, input1); assert.equal(currentEditorsMRU[1].groupId, rootGroup.id); assert.equal(currentEditorsMRU[1].editor, input1); + assert.equal(observer.hasEditor(input1.resource), true); await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE })); @@ -152,6 +166,7 @@ suite('EditorsObserver', function () { assert.equal(currentEditorsMRU[0].editor, input1); assert.equal(currentEditorsMRU[1].groupId, sideGroup.id); assert.equal(currentEditorsMRU[1].editor, input1); + assert.equal(observer.hasEditor(input1.resource), true); // Opening an editor inactive should not change // the most recent editor, but rather put it behind @@ -167,6 +182,8 @@ suite('EditorsObserver', function () { assert.equal(currentEditorsMRU[1].editor, input2); assert.equal(currentEditorsMRU[2].groupId, sideGroup.id); assert.equal(currentEditorsMRU[2].editor, input1); + assert.equal(observer.hasEditor(input1.resource), true); + assert.equal(observer.hasEditor(input2.resource), true); await rootGroup.closeAllEditors(); @@ -174,11 +191,15 @@ suite('EditorsObserver', function () { assert.equal(currentEditorsMRU.length, 1); assert.equal(currentEditorsMRU[0].groupId, sideGroup.id); assert.equal(currentEditorsMRU[0].editor, input1); + assert.equal(observer.hasEditor(input1.resource), true); + assert.equal(observer.hasEditor(input2.resource), false); await sideGroup.closeAllEditors(); currentEditorsMRU = observer.editors; assert.equal(currentEditorsMRU.length, 0); + assert.equal(observer.hasEditor(input1.resource), false); + assert.equal(observer.hasEditor(input2.resource), false); part.dispose(); }); @@ -204,6 +225,9 @@ suite('EditorsObserver', function () { assert.equal(currentEditorsMRU[1].editor, input2); assert.equal(currentEditorsMRU[2].groupId, rootGroup.id); assert.equal(currentEditorsMRU[2].editor, input1); + assert.equal(observer.hasEditor(input1.resource), true); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), true); const copiedGroup = part.copyGroup(rootGroup, rootGroup, GroupDirection.RIGHT); copiedGroup.setActive(true); @@ -222,6 +246,21 @@ suite('EditorsObserver', function () { assert.equal(currentEditorsMRU[4].editor, input2); assert.equal(currentEditorsMRU[5].groupId, rootGroup.id); assert.equal(currentEditorsMRU[5].editor, input1); + assert.equal(observer.hasEditor(input1.resource), true); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), true); + + await rootGroup.closeAllEditors(); + + assert.equal(observer.hasEditor(input1.resource), true); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), true); + + await copiedGroup.closeAllEditors(); + + assert.equal(observer.hasEditor(input1.resource), false); + assert.equal(observer.hasEditor(input2.resource), false); + assert.equal(observer.hasEditor(input3.resource), false); part.dispose(); }); @@ -251,6 +290,9 @@ suite('EditorsObserver', function () { assert.equal(currentEditorsMRU[1].editor, input2); assert.equal(currentEditorsMRU[2].groupId, rootGroup.id); assert.equal(currentEditorsMRU[2].editor, input1); + assert.equal(observer.hasEditor(input1.resource), true); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), true); storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); @@ -265,7 +307,11 @@ suite('EditorsObserver', function () { assert.equal(currentEditorsMRU[1].editor, input2); assert.equal(currentEditorsMRU[2].groupId, rootGroup.id); assert.equal(currentEditorsMRU[2].editor, input1); + assert.equal(observer.hasEditor(input1.resource), true); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), true); + part.clearState(); part.dispose(); }); @@ -296,6 +342,9 @@ suite('EditorsObserver', function () { assert.equal(currentEditorsMRU[1].editor, input2); assert.equal(currentEditorsMRU[2].groupId, rootGroup.id); assert.equal(currentEditorsMRU[2].editor, input1); + assert.equal(observer.hasEditor(input1.resource), true); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), true); storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); @@ -310,7 +359,11 @@ suite('EditorsObserver', function () { assert.equal(currentEditorsMRU[1].editor, input2); assert.equal(currentEditorsMRU[2].groupId, rootGroup.id); assert.equal(currentEditorsMRU[2].editor, input1); + assert.equal(restoredObserver.hasEditor(input1.resource), true); + assert.equal(restoredObserver.hasEditor(input2.resource), true); + assert.equal(restoredObserver.hasEditor(input3.resource), true); + part.clearState(); part.dispose(); }); @@ -331,6 +384,7 @@ suite('EditorsObserver', function () { assert.equal(currentEditorsMRU.length, 1); assert.equal(currentEditorsMRU[0].groupId, rootGroup.id); assert.equal(currentEditorsMRU[0].editor, input1); + assert.equal(observer.hasEditor(input1.resource), true); storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); @@ -339,7 +393,9 @@ suite('EditorsObserver', function () { currentEditorsMRU = restoredObserver.editors; assert.equal(currentEditorsMRU.length, 0); + assert.equal(restoredObserver.hasEditor(input1.resource), false); + part.clearState(); part.dispose(); }); @@ -368,6 +424,10 @@ suite('EditorsObserver', function () { assert.equal(rootGroup.isOpened(input2), true); assert.equal(rootGroup.isOpened(input3), true); assert.equal(rootGroup.isOpened(input4), true); + assert.equal(observer.hasEditor(input1.resource), false); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), true); + assert.equal(observer.hasEditor(input4.resource), true); input2.setDirty(); part.enforcePartOptions({ limit: { enabled: true, value: 1 } }); @@ -379,6 +439,10 @@ suite('EditorsObserver', function () { assert.equal(rootGroup.isOpened(input2), true); // dirty assert.equal(rootGroup.isOpened(input3), false); assert.equal(rootGroup.isOpened(input4), true); + assert.equal(observer.hasEditor(input1.resource), false); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), false); + assert.equal(observer.hasEditor(input4.resource), true); const input5 = new TestFileEditorInput(URI.parse('foo://bar5'), TEST_EDITOR_INPUT_ID); await sideGroup.openEditor(input5, EditorOptions.create({ pinned: true })); @@ -388,8 +452,12 @@ suite('EditorsObserver', function () { assert.equal(rootGroup.isOpened(input2), true); // dirty assert.equal(rootGroup.isOpened(input3), false); assert.equal(rootGroup.isOpened(input4), false); - assert.equal(sideGroup.isOpened(input5), true); + assert.equal(observer.hasEditor(input1.resource), false); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), false); + assert.equal(observer.hasEditor(input4.resource), false); + assert.equal(observer.hasEditor(input5.resource), true); observer.dispose(); part.dispose(); @@ -415,11 +483,15 @@ suite('EditorsObserver', function () { await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true })); await rootGroup.openEditor(input4, EditorOptions.create({ pinned: true })); - assert.equal(rootGroup.count, 3); + assert.equal(rootGroup.count, 3); // 1 editor got closed due to our limit! assert.equal(rootGroup.isOpened(input1), false); assert.equal(rootGroup.isOpened(input2), true); assert.equal(rootGroup.isOpened(input3), true); assert.equal(rootGroup.isOpened(input4), true); + assert.equal(observer.hasEditor(input1.resource), false); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), true); + assert.equal(observer.hasEditor(input4.resource), true); await sideGroup.openEditor(input1, EditorOptions.create({ pinned: true })); await sideGroup.openEditor(input2, EditorOptions.create({ pinned: true })); @@ -431,6 +503,10 @@ suite('EditorsObserver', function () { assert.equal(sideGroup.isOpened(input2), true); assert.equal(sideGroup.isOpened(input3), true); assert.equal(sideGroup.isOpened(input4), true); + assert.equal(observer.hasEditor(input1.resource), false); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), true); + assert.equal(observer.hasEditor(input4.resource), true); part.enforcePartOptions({ limit: { enabled: true, value: 1, perEditorGroup: true } }); @@ -448,6 +524,11 @@ suite('EditorsObserver', function () { assert.equal(sideGroup.isOpened(input3), false); assert.equal(sideGroup.isOpened(input4), true); + assert.equal(observer.hasEditor(input1.resource), false); + assert.equal(observer.hasEditor(input2.resource), false); + assert.equal(observer.hasEditor(input3.resource), false); + assert.equal(observer.hasEditor(input4.resource), true); + observer.dispose(); part.dispose(); }); diff --git a/src/vs/workbench/services/electron/electron-browser/electronEnvironmentService.ts b/src/vs/workbench/services/electron/electron-browser/electronEnvironmentService.ts deleted file mode 100644 index 9902c63e3ec..00000000000 --- a/src/vs/workbench/services/electron/electron-browser/electronEnvironmentService.ts +++ /dev/null @@ -1,37 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { URI } from 'vs/base/common/uri'; -import { memoize } from 'vs/base/common/decorators'; -import { join } from 'vs/base/common/path'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; - -export const IElectronEnvironmentService = createDecorator('electronEnvironmentService'); - -export interface IElectronEnvironmentService { - - _serviceBrand: undefined; - - readonly windowId: number; - - readonly sharedIPCHandle: string; - - readonly extHostLogsPath: URI; -} - -export class ElectronEnvironmentService implements IElectronEnvironmentService { - - _serviceBrand: undefined; - - constructor( - public readonly windowId: number, - public readonly sharedIPCHandle: string, - private readonly environmentService: IEnvironmentService - ) { } - - @memoize - get extHostLogsPath(): URI { return URI.file(join(this.environmentService.logsPath, `exthost${this.windowId}`)); } -} diff --git a/src/vs/workbench/services/electron/electron-browser/electronService.ts b/src/vs/workbench/services/electron/electron-browser/electronService.ts index eebb9bace06..ba25ebd672d 100644 --- a/src/vs/workbench/services/electron/electron-browser/electronService.ts +++ b/src/vs/workbench/services/electron/electron-browser/electronService.ts @@ -6,8 +6,9 @@ import { IElectronService } from 'vs/platform/electron/node/electron'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { createChannelSender } from 'vs/base/parts/ipc/node/ipc'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export class ElectronService { @@ -15,9 +16,9 @@ export class ElectronService { constructor( @IMainProcessService mainProcessService: IMainProcessService, - @IElectronEnvironmentService electronEnvironmentService: IElectronEnvironmentService + @IWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService ) { - return createChannelSender(mainProcessService.getChannel('electron'), { context: electronEnvironmentService.windowId }); + return createChannelSender(mainProcessService.getChannel('electron'), { context: environmentService.configuration.windowId }); } } diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 80873b34704..f878c3de3d0 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -4,22 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import { Schemas } from 'vs/base/common/network'; -import { ExportData } from 'vs/base/common/performance'; -import { IProcessEnvironment } from 'vs/base/common/platform'; import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { BACKUPS, IExtensionHostDebugParams } from 'vs/platform/environment/common/environment'; -import { LogLevel } from 'vs/platform/log/common/log'; -import { IPath, IPathsToWaitFor, IWindowConfiguration } from 'vs/platform/windows/common/windows'; -import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IPath, IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; import product from 'vs/platform/product/common/product'; -import { serializableToMap } from 'vs/base/common/map'; import { memoize } from 'vs/base/common/decorators'; -// TODO@ben remove properties that are node/electron only export class BrowserWindowConfiguration implements IWindowConfiguration { constructor( @@ -28,8 +22,6 @@ export class BrowserWindowConfiguration implements IWindowConfiguration { private readonly environment: IWorkbenchEnvironmentService ) { } - //#region PROPERLY CONFIGURED IN DESKTOP + WEB - @memoize get sessionId(): string { return generateUuid(); } @@ -54,44 +46,10 @@ export class BrowserWindowConfiguration implements IWindowConfiguration { return undefined; } - // Currently unsupported in web + // Currently unsupported in web but should look into support get filesToDiff(): IPath[] | undefined { return undefined; } - - //#endregion - - - //#region TODO MOVE TO NODE LAYER - - _!: string[]; - - windowId!: number; - mainPid!: number; - - logLevel!: LogLevel; - - appRoot!: string; - execPath!: string; - backupPath?: string; - nodeCachedDataDir?: string; - - userEnv!: IProcessEnvironment; - - workspace?: IWorkspaceIdentifier; - folderUri?: ISingleFolderWorkspaceIdentifier; - - zoomLevel?: number; - fullscreen?: boolean; - maximized?: boolean; - highContrast?: boolean; - accessibilitySupport?: boolean; - partsSplashPath?: string; - - isInitialStartup?: boolean; - perfEntries!: ExportData; - - filesToWait?: IPathsToWaitFor; - - //#endregion + highContrast = false; + _ = []; private getCookieValue(name: string): string | undefined { const m = document.cookie.match('(^|[^;]+)\\s*' + name + '\\s*=\\s*([^;]+)'); // See https://stackoverflow.com/a/25490531 @@ -110,13 +68,21 @@ interface IExtensionHostDebugEnvironment { isExtensionDevelopment: boolean; extensionDevelopmentLocationURI?: URI[]; extensionTestsLocationURI?: URI; + extensionEnabledProposedApi?: string[]; } export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironmentService { _serviceBrand: undefined; - //#region PROPERLY CONFIGURED IN DESKTOP + WEB + private _configuration: IWindowConfiguration | undefined = undefined; + get configuration(): IWindowConfiguration { + if (!this._configuration) { + this._configuration = new BrowserWindowConfiguration(this.options, this.payload, this); + } + + return this._configuration; + } @memoize get isBuilt(): boolean { return !!product.commit; } @@ -136,15 +102,12 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment @memoize get argvResource(): URI { return joinPath(this.userRoamingDataHome, 'argv.json'); } + @memoize + get snippetsHome(): URI { return joinPath(this.userRoamingDataHome, 'snippets'); } + @memoize get userDataSyncHome(): URI { return joinPath(this.userRoamingDataHome, 'sync'); } - @memoize - get settingsSyncPreviewResource(): URI { return joinPath(this.userDataSyncHome, 'settings.json'); } - - @memoize - get keybindingsSyncPreviewResource(): URI { return joinPath(this.userDataSyncHome, 'keybindings.json'); } - @memoize get userDataSyncLogResource(): URI { return joinPath(this.options.logsPath, 'userDataSync.log'); } @@ -193,6 +156,14 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment return this._extensionHostDebugEnvironment.extensionTestsLocationURI; } + get extensionEnabledProposedApi(): string[] | undefined { + if (!this._extensionHostDebugEnvironment) { + this._extensionHostDebugEnvironment = this.resolveExtensionHostDebugEnvironment(); + } + + return this._extensionHostDebugEnvironment.extensionEnabledProposedApi; + } + @memoize get webviewExternalEndpoint(): string { // TODO: get fallback from product.json @@ -209,76 +180,19 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment return this.webviewExternalEndpoint.replace('{{uuid}}', '*'); } - // Currently not configurable in web + // Currently unsupported in web but should look into support get disableExtensions() { return false; } get extensionsPath(): string | undefined { return undefined; } get verbose(): boolean { return false; } get disableUpdates(): boolean { return false; } get logExtensionHostCommunication(): boolean { return false; } - - //#endregion - - - //#region TODO MOVE TO NODE LAYER - - private _configuration: IWindowConfiguration | undefined = undefined; - get configuration(): IWindowConfiguration { - if (!this._configuration) { - this._configuration = new BrowserWindowConfiguration(this.options, this.payload, this); - } - - return this._configuration; - } - - args = { _: [] }; - - wait!: boolean; - status!: boolean; - log?: string; - - mainIPCHandle!: string; - sharedIPCHandle!: string; - - nodeCachedDataDir?: string; - - disableCrashReporter!: boolean; - - driverHandle?: string; - driverVerbose!: boolean; - - installSourcePath!: string; - - builtinExtensionsPath!: string; - - globalStorageHome!: string; - workspaceStorageHome!: string; - - backupWorkspacesPath!: string; - - machineSettingsHome!: URI; - machineSettingsResource!: URI; - - userHome!: string; - userDataPath!: string; - appRoot!: string; - appSettingsHome!: URI; - execPath!: string; - cliPath!: string; - - //#endregion - - - //#region TODO ENABLE IN WEB - galleryMachineIdResource?: URI; - //#endregion - private payload: Map | undefined; constructor(readonly options: IBrowserWorkbenchEnvironmentConstructionOptions) { if (options.workspaceProvider && Array.isArray(options.workspaceProvider.payload)) { - this.payload = serializableToMap(options.workspaceProvider.payload); + this.payload = new Map(options.workspaceProvider.payload); } } @@ -310,10 +224,44 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment extensionHostDebugEnvironment.params.port = parseInt(value); extensionHostDebugEnvironment.params.break = true; break; + case 'enableProposedApi': + extensionHostDebugEnvironment.extensionEnabledProposedApi = []; + break; } } } return extensionHostDebugEnvironment; } + + //#region TODO MOVE TO NODE LAYER + + args = { _: [] }; + + mainIPCHandle!: string; + sharedIPCHandle!: string; + + nodeCachedDataDir?: string; + + driverHandle?: string; + driverVerbose!: boolean; + + installSourcePath!: string; + + builtinExtensionsPath!: string; + + globalStorageHome!: string; + workspaceStorageHome!: string; + + backupWorkspacesPath!: string; + + machineSettingsResource!: URI; + + userHome!: string; + userDataPath!: string; + appRoot!: string; + appSettingsHome!: URI; + execPath!: string; + + //#endregion } diff --git a/src/vs/workbench/services/environment/electron-browser/environmentService.ts b/src/vs/workbench/services/environment/electron-browser/environmentService.ts index 8d32aff4ee6..7c01fc8975a 100644 --- a/src/vs/workbench/services/environment/electron-browser/environmentService.ts +++ b/src/vs/workbench/services/environment/electron-browser/environmentService.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; -import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { memoize } from 'vs/base/common/decorators'; import { URI } from 'vs/base/common/uri'; @@ -12,8 +11,21 @@ import { Schemas } from 'vs/base/common/network'; import { toBackupWorkspaceResource } from 'vs/workbench/services/backup/electron-browser/backup'; import { join } from 'vs/base/common/path'; import product from 'vs/platform/product/common/product'; +import { INativeWindowConfiguration } from 'vs/platform/windows/node/window'; -export class NativeWorkbenchEnvironmentService extends EnvironmentService implements IWorkbenchEnvironmentService { +export interface INativeWorkbenchEnvironmentService extends IWorkbenchEnvironmentService { + + readonly configuration: INativeWindowConfiguration; + + readonly disableCrashReporter: boolean; + + readonly cliPath: string; + + readonly log?: string; + readonly extHostLogsPath: URI; +} + +export class NativeWorkbenchEnvironmentService extends EnvironmentService implements INativeWorkbenchEnvironmentService { _serviceBrand: undefined; @@ -34,12 +46,14 @@ export class NativeWorkbenchEnvironmentService extends EnvironmentService implem get userRoamingDataHome(): URI { return this.appSettingsHome.with({ scheme: Schemas.userData }); } @memoize - get logFile(): URI { return URI.file(join(this.logsPath, `renderer${this.windowId}.log`)); } + get logFile(): URI { return URI.file(join(this.logsPath, `renderer${this.configuration.windowId}.log`)); } + + @memoize + get extHostLogsPath(): URI { return URI.file(join(this.logsPath, `exthost${this.configuration.windowId}`)); } constructor( - readonly configuration: IWindowConfiguration, - execPath: string, - private readonly windowId: number + readonly configuration: INativeWindowConfiguration, + execPath: string ) { super(configuration, execPath); diff --git a/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts index d64573ec6dc..cfac383e8af 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts @@ -146,6 +146,12 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench return false; } } + if (extensionKind === 'web') { + // Web extensions are not yet supported to be disabled by kind. Enable them always on web. + if (this.extensionManagementServerService.localExtensionManagementServer === null) { + return false; + } + } } return true; } diff --git a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts index 7a4ee7ed3cc..66bdd7eefae 100644 --- a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts +++ b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts @@ -490,6 +490,38 @@ suite('ExtensionEnablementService Test', () => { assert.equal(testObject.canChangeEnablement(localWorkspaceExtension), true); }); + test('test web extension on local server is disabled by kind', async () => { + instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`) }); + testObject = new TestExtensionEnablementService(instantiationService); + assert.ok(!testObject.isEnabled(localWorkspaceExtension)); + assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); + }); + + test('test web extension on remote server is not disabled by kind when there is no local server', async () => { + instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(null, anExtensionManagementServer('vscode-remote', instantiationService))); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + testObject = new TestExtensionEnablementService(instantiationService); + assert.ok(testObject.isEnabled(localWorkspaceExtension)); + assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); + }); + + test('test web extension with no server is not disabled by kind when there is no local server', async () => { + instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(null, anExtensionManagementServer('vscode-remote', instantiationService))); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.https }) }); + testObject = new TestExtensionEnablementService(instantiationService); + assert.ok(testObject.isEnabled(localWorkspaceExtension)); + assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); + }); + + test('test web extension with no server is not disabled by kind when there is no local and remote server', async () => { + instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(null, null)); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.https }) }); + testObject = new TestExtensionEnablementService(instantiationService); + assert.ok(testObject.isEnabled(localWorkspaceExtension)); + assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); + }); + }); function anExtensionManagementServer(authority: string, instantiationService: TestInstantiationService): IExtensionManagementServer { diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index 9d9e4e4d320..c5f788650eb 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -457,12 +457,12 @@ class ProposedApiController { @IProductService productService: IProductService ) { // Make enabled proposed API be lowercase for case insensitive comparison - this.enableProposedApiFor = (environmentService.args['enable-proposed-api'] || []).map(id => id.toLowerCase()); + this.enableProposedApiFor = (environmentService.extensionEnabledProposedApi || []).map(id => id.toLowerCase()); this.enableProposedApiForAll = !environmentService.isBuilt || // always allow proposed API when running out of sources (!!environmentService.extensionDevelopmentLocationURI && productService.quality !== 'stable') || // do not allow proposed API against stable builds when developing an extension - (this.enableProposedApiFor.length === 0 && 'enable-proposed-api' in environmentService.args); // always allow proposed API if --enable-proposed-api is provided without extension ID + (this.enableProposedApiFor.length === 0 && Array.isArray(environmentService.extensionEnabledProposedApi)); // always allow proposed API if --enable-proposed-api is provided without extension ID this.productAllowProposedApi = new Set(); if (isNonEmptyArray(productService.extensionAllowedProposedApi)) { diff --git a/src/vs/workbench/services/extensions/common/extensionHostMain.ts b/src/vs/workbench/services/extensions/common/extensionHostMain.ts index caebe7634f3..93d9a1d107d 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostMain.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostMain.ts @@ -76,7 +76,7 @@ export class ExtensionHostMain { // error forwarding and stack trace scanning Error.stackTraceLimit = 100; // increase number of stack frames (from 10, https://github.com/v8/v8/wiki/Stack-Trace-API) - const extensionErrors = new WeakMap(); + const extensionErrors = new WeakMap(); this._extensionService.getExtensionPathIndex().then(map => { (Error).prepareStackTrace = (error: Error, stackTrace: errors.V8CallSite[]) => { let stackTraceMessage = ''; diff --git a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts index 70282328684..32c51b41e3a 100644 --- a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts @@ -289,6 +289,11 @@ export const schema: IJSONSchema = { body: 'onUri', description: nls.localize('vscode.extension.activationEvents.onUri', 'An activation event emitted whenever a system-wide Uri directed towards this extension is open.'), }, + { + label: 'onCustomEditor', + body: 'onCustomEditor:${9:viewType}', + description: nls.localize('vscode.extension.activationEvents.onCustomEditor', 'An activation event emitted whenever the specified custom editor becomes visible.'), + }, { label: '*', description: nls.localize('vscode.extension.activationEvents.star', 'An activation event emitted on VS Code startup. To ensure a great end user experience, please use this activation event in your extension only when no other activation events combination works in your use-case.'), diff --git a/src/vs/workbench/services/extensions/common/proxyIdentifier.ts b/src/vs/workbench/services/extensions/common/proxyIdentifier.ts index 311d9e7cd07..e0e9999a62f 100644 --- a/src/vs/workbench/services/extensions/common/proxyIdentifier.ts +++ b/src/vs/workbench/services/extensions/common/proxyIdentifier.ts @@ -20,7 +20,6 @@ export interface IRPCProtocol { assertRegistered(identifiers: ProxyIdentifier[]): void; } -// @ts-ignore export class ProxyIdentifier { public static count = 0; _proxyIdentifierBrand: void; diff --git a/src/vs/workbench/services/extensions/common/rpcProtocol.ts b/src/vs/workbench/services/extensions/common/rpcProtocol.ts index c7d7600c6df..022fd1f4c41 100644 --- a/src/vs/workbench/services/extensions/common/rpcProtocol.ts +++ b/src/vs/workbench/services/extensions/common/rpcProtocol.ts @@ -27,6 +27,10 @@ function safeStringify(obj: any, replacer: JSONStringifyReplacer | null): string } } +function stringify(obj: any, replacer: JSONStringifyReplacer | null): string { + return JSON.stringify(obj, <(key: string, value: any) => any>replacer); +} + function createURIReplacer(transformer: IURITransformer | null): JSONStringifyReplacer | null { if (!transformer) { return null; @@ -412,6 +416,8 @@ export class RPCProtocol extends Disposable implements IRPCProtocol { return Promise.reject(errors.canceled()); } + const serializedRequestArguments = MessageIO.serializeRequestArguments(args, this._uriReplacer); + const req = ++this._lastMessageId; const callId = String(req); const result = new LazyPromise(); @@ -428,7 +434,7 @@ export class RPCProtocol extends Disposable implements IRPCProtocol { this._pendingRPCReplies[callId] = result; this._onWillSendRequest(req); - const msg = MessageIO.serializeRequest(req, rpcId, methodName, args, !!cancellationToken, this._uriReplacer); + const msg = MessageIO.serializeRequest(req, rpcId, methodName, serializedRequestArguments, !!cancellationToken); if (this._logger) { this._logger.logOutgoing(msg.byteLength, req, RequestInitiator.LocalSide, `request: ${getStringIdentifierForProxy(rpcId)}.${methodName}(`, args); } @@ -600,6 +606,8 @@ class MessageBuffer { } } +type SerializedRequestArguments = { type: 'mixed'; args: VSBuffer[]; argsType: ArgType[]; } | { type: 'simple'; args: string; }; + class MessageIO { private static _arrayContainsBufferOrUndefined(arr: any[]): boolean { @@ -614,7 +622,7 @@ class MessageIO { return false; } - public static serializeRequest(req: number, rpcId: number, method: string, args: any[], usesCancellationToken: boolean, replacer: JSONStringifyReplacer | null): VSBuffer { + public static serializeRequestArguments(args: any[], replacer: JSONStringifyReplacer | null): SerializedRequestArguments { if (this._arrayContainsBufferOrUndefined(args)) { let massagedArgs: VSBuffer[] = []; let massagedArgsType: ArgType[] = []; @@ -627,13 +635,27 @@ class MessageIO { massagedArgs[i] = VSBuffer.alloc(0); massagedArgsType[i] = ArgType.Undefined; } else { - massagedArgs[i] = VSBuffer.fromString(safeStringify(arg, replacer)); + massagedArgs[i] = VSBuffer.fromString(stringify(arg, replacer)); massagedArgsType[i] = ArgType.String; } } - return this._requestMixedArgs(req, rpcId, method, massagedArgs, massagedArgsType, usesCancellationToken); + return { + type: 'mixed', + args: massagedArgs, + argsType: massagedArgsType + }; } - return this._requestJSONArgs(req, rpcId, method, safeStringify(args, replacer), usesCancellationToken); + return { + type: 'simple', + args: stringify(args, replacer) + }; + } + + public static serializeRequest(req: number, rpcId: number, method: string, serializedArgs: SerializedRequestArguments, usesCancellationToken: boolean): VSBuffer { + if (serializedArgs.type === 'mixed') { + return this._requestMixedArgs(req, rpcId, method, serializedArgs.args, serializedArgs.argsType, usesCancellationToken); + } + return this._requestJSONArgs(req, rpcId, method, serializedArgs.args, usesCancellationToken); } private static _requestJSONArgs(req: number, rpcId: number, method: string, args: string, usesCancellationToken: boolean): VSBuffer { diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index c45f7e5c3e3..b823db0ce9b 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -42,6 +42,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { joinPath } from 'vs/base/common/resources'; import { Registry } from 'vs/platform/registry/common/platform'; import { IOutputChannelRegistry, Extensions } from 'vs/workbench/services/output/common/output'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; export class ExtensionHostProcessWorker implements IExtensionHostStarter { @@ -78,7 +79,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { @INotificationService private readonly _notificationService: INotificationService, @IElectronService private readonly _electronService: IElectronService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, - @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService private readonly _environmentService: INativeWorkbenchEnvironmentService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @ILogService private readonly _logService: ILogService, @ILabelService private readonly _labelService: ILabelService, diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index b4a88b0a363..74120ad40c3 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -35,7 +35,7 @@ import { Logger } from 'vs/workbench/services/extensions/common/extensionPoints' import { flatten } from 'vs/base/common/arrays'; import { IStaticExtensionsService } from 'vs/workbench/services/extensions/common/staticExtensions'; import { IElectronService } from 'vs/platform/electron/node/electron'; -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { Action } from 'vs/base/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; @@ -60,7 +60,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten constructor( @IInstantiationService instantiationService: IInstantiationService, @INotificationService notificationService: INotificationService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService protected readonly _environmentService: INativeWorkbenchEnvironmentService, @ITelemetryService telemetryService: ITelemetryService, @IWorkbenchExtensionEnablementService extensionEnablementService: IWorkbenchExtensionEnablementService, @IFileService fileService: IFileService, @@ -73,14 +73,13 @@ export class ExtensionService extends AbstractExtensionService implements IExten @IStaticExtensionsService private readonly _staticExtensions: IStaticExtensionsService, @IElectronService private readonly _electronService: IElectronService, @IHostService private readonly _hostService: IHostService, - @IElectronEnvironmentService private readonly _electronEnvironmentService: IElectronEnvironmentService, @IRemoteExplorerService private readonly _remoteExplorerService: IRemoteExplorerService, @IExtensionGalleryService private readonly _extensionGalleryService: IExtensionGalleryService, ) { super( instantiationService, notificationService, - environmentService, + _environmentService, telemetryService, extensionEnablementService, fileService, @@ -361,7 +360,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten const result: ExtensionHostProcessManager[] = []; - const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, autoStart, extensions, this._electronEnvironmentService.extHostLogsPath); + const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, autoStart, extensions, this._environmentService.extHostLogsPath); const extHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, true, extHostProcessWorker, null, initialActivationEvents); result.push(extHostProcessManager); @@ -459,16 +458,13 @@ export class ExtensionService extends AbstractExtensionService implements IExten } catch (err) { const remoteName = getRemoteName(remoteAuthority); if (RemoteAuthorityResolverError.isNoResolverFound(err)) { - this._handleNoResolverFound(remoteName, allExtensions); + err.isHandled = await this._handleNoResolverFound(remoteName, allExtensions); } else { console.log(err); - if (RemoteAuthorityResolverError.isHandledNotAvailable(err)) { - console.log(`Not showing a notification for the error`); - } else { - this._notificationService.notify({ severity: Severity.Error, message: nls.localize('resolveAuthorityFailure', "Resolving the authority `{0}` failed", remoteName) }); + if (RemoteAuthorityResolverError.isHandled(err)) { + console.log(`Error handled: Not showing a notification for the error`); } } - this._remoteAuthorityResolverService.setResolvedAuthorityError(remoteAuthority, err); // Proceed with the local extension host @@ -584,10 +580,10 @@ export class ExtensionService extends AbstractExtensionService implements IExten } } - private async _handleNoResolverFound(remoteName: string, allExtensions: IExtensionDescription[]): Promise { + private async _handleNoResolverFound(remoteName: string, allExtensions: IExtensionDescription[]): Promise { const recommendation = this._productService.remoteExtensionTips?.[remoteName]; if (!recommendation) { - return; + return false; } const sendTelemetry = (userReaction: 'install' | 'enable' | 'cancel') => { /* __GDPR__ @@ -603,7 +599,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten const extension = allExtensions.filter(e => e.identifier.value === resolverExtensionId)[0]; if (extension) { if (this._isDisabled(extension)) { - const message = nls.localize('enableResolver', "Extension '{0}' is required to open the remote window.\nOk to enable?", recommendation.friendlyName); + const message = nls.localize('enableResolver', "Extension '{0}' is required to open the remote window.\nOK to enable?", recommendation.friendlyName); this._notificationService.prompt(Severity.Info, message, [{ label: nls.localize('enable', 'Enable and Reload'), @@ -618,7 +614,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten } } else { // Install the Extension and reload the window to handle. - const message = nls.localize('installResolver', "Extension '{0}' is required to open the remote window.\nOk to install?", recommendation.friendlyName); + const message = nls.localize('installResolver', "Extension '{0}' is required to open the remote window.\nnOK to install?", recommendation.friendlyName); this._notificationService.prompt(Severity.Info, message, [{ label: nls.localize('install', 'Install and Reload'), @@ -641,6 +637,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten ); } + return true; } } @@ -669,7 +666,7 @@ registerSingleton(IExtensionService, ExtensionService); class RestartExtensionHostAction extends Action { public static readonly ID = 'workbench.action.restartExtensionHost'; - public static readonly LABEL = nls.localize('restartExtensionHost', "Developer: Restart Extension Host"); + public static readonly LABEL = nls.localize('restartExtensionHost', "Restart Extension Host"); constructor( id: string, @@ -685,4 +682,4 @@ class RestartExtensionHostAction extends Action { } const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(SyncActionDescriptor.create(RestartExtensionHostAction, RestartExtensionHostAction.ID, RestartExtensionHostAction.LABEL), 'Developer: Restart Extension Host'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(RestartExtensionHostAction, RestartExtensionHostAction.ID, RestartExtensionHostAction.LABEL), 'Developer: Restart Extension Host', nls.localize('developer', "Developer")); diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index 0f35c544319..79dd77aeb26 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -5,7 +5,7 @@ import * as nativeWatchdog from 'native-watchdog'; import * as net from 'net'; -import * as minimist from 'vscode-minimist'; +import * as minimist from 'minimist'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; @@ -25,6 +25,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; interface ParsedExtHostArgs { uriTransformerPath?: string; + useHostProxy?: string; } // workaround for https://github.com/microsoft/vscode/issues/85490 @@ -40,7 +41,8 @@ interface ParsedExtHostArgs { const args = minimist(process.argv.slice(2), { string: [ - 'uriTransformerPath' + 'uriTransformerPath', + 'useHostProxy' ] }) as ParsedExtHostArgs; @@ -293,6 +295,7 @@ export async function startExtensionHostProcess(): Promise { const { initData } = renderer; // setup things patchProcess(!!initData.environment.extensionTestsLocationURI); // to support other test frameworks like Jasmin that use process.exit (https://github.com/Microsoft/vscode/issues/37708) + initData.environment.useHostProxy = args.useHostProxy !== undefined ? args.useHostProxy !== 'false' : undefined; // host abstraction const hostUtils = new class NodeHost implements IHostUtils { diff --git a/src/vs/workbench/services/extensions/node/proxyResolver.ts b/src/vs/workbench/services/extensions/node/proxyResolver.ts index 4c72df591f5..9c1fa07fd8e 100644 --- a/src/vs/workbench/services/extensions/node/proxyResolver.ts +++ b/src/vs/workbench/services/extensions/node/proxyResolver.ts @@ -16,12 +16,13 @@ import { endsWith } from 'vs/base/common/strings'; import { IExtHostWorkspaceProvider } from 'vs/workbench/api/common/extHostWorkspace'; import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; import { ProxyAgent } from 'vscode-proxy-agent'; -import { MainThreadTelemetryShape } from 'vs/workbench/api/common/extHost.protocol'; +import { MainThreadTelemetryShape, IInitData } from 'vs/workbench/api/common/extHost.protocol'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; import { URI } from 'vs/base/common/uri'; import { promisify } from 'util'; import { ILogService } from 'vs/platform/log/common/log'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; interface ConnectionResult { proxy: string; @@ -35,9 +36,10 @@ export function connectProxyResolver( configProvider: ExtHostConfigProvider, extensionService: ExtHostExtensionService, extHostLogService: ILogService, - mainThreadTelemetry: MainThreadTelemetryShape + mainThreadTelemetry: MainThreadTelemetryShape, + initData: IInitData, ) { - const resolveProxy = setupProxyResolution(extHostWorkspace, configProvider, extHostLogService, mainThreadTelemetry); + const resolveProxy = setupProxyResolution(extHostWorkspace, configProvider, extHostLogService, mainThreadTelemetry, initData); const lookup = createPatchedModules(configProvider, resolveProxy); return configureModuleLoading(extensionService, lookup); } @@ -48,7 +50,8 @@ function setupProxyResolution( extHostWorkspace: IExtHostWorkspaceProvider, configProvider: ExtHostConfigProvider, extHostLogService: ILogService, - mainThreadTelemetry: MainThreadTelemetryShape + mainThreadTelemetry: MainThreadTelemetryShape, + initData: IInitData, ) { const env = process.env; @@ -139,12 +142,14 @@ function setupProxyResolution( timeout = setTimeout(logEvent, 10 * 60 * 1000); } + const useHostProxy = initData.environment.useHostProxy; + const doUseHostProxy = typeof useHostProxy === 'boolean' ? useHostProxy : !initData.remote.isRemote; useSystemCertificates(extHostLogService, flags.useSystemCertificates, opts, () => { - useProxySettings(flags.useProxySettings, req, opts, url, callback); + useProxySettings(doUseHostProxy, flags.useProxySettings, req, opts, url, callback); }); } - function useProxySettings(useProxySettings: boolean, req: http.ClientRequest, opts: http.RequestOptions, url: string, callback: (proxy?: string) => void) { + function useProxySettings(useHostProxy: boolean, useProxySettings: boolean, req: http.ClientRequest, opts: http.RequestOptions, url: string, callback: (proxy?: string) => void) { if (!useProxySettings) { callback('DIRECT'); @@ -192,6 +197,12 @@ function setupProxyResolution( return; } + if (!useHostProxy) { + callback('DIRECT'); + extHostLogService.trace('ProxyResolver#resolveProxy unconfigured', url, 'DIRECT'); + return; + } + const start = Date.now(); extHostWorkspace.resolveProxy(url) // Use full URL to ensure it is an actually used one. .then(proxy => { @@ -308,14 +319,14 @@ function createPatchedModules(configProvider: ExtHostConfigProvider, resolveProx override: assign({}, http, patches(http, resolveProxy, { config: 'override' }, certSetting, true)), onRequest: assign({}, http, patches(http, resolveProxy, proxySetting, certSetting, true)), default: assign(http, patches(http, resolveProxy, proxySetting, certSetting, false)) // run last - }, + } as Record, https: { off: assign({}, https, patches(https, resolveProxy, { config: 'off' }, certSetting, true)), on: assign({}, https, patches(https, resolveProxy, { config: 'on' }, certSetting, true)), override: assign({}, https, patches(https, resolveProxy, { config: 'override' }, certSetting, true)), onRequest: assign({}, https, patches(https, resolveProxy, proxySetting, certSetting, true)), default: assign(https, patches(https, resolveProxy, proxySetting, certSetting, false)) // run last - }, + } as Record, tls: assign(tls, tlsPatches(tls)) }; } @@ -401,6 +412,7 @@ function tlsPatches(originals: typeof tls) { } } +const modulesCache = new Map(); function configureModuleLoading(extensionService: ExtHostExtensionService, lookup: ReturnType): Promise { return extensionService.getExtensionPathIndex() .then(extensionPaths => { @@ -417,10 +429,18 @@ function configureModuleLoading(extensionService: ExtHostExtensionService, looku const modules = lookup[request]; const ext = extensionPaths.findSubstr(URI.file(parent.filename).fsPath); - if (ext && ext.enableProposedApi) { - return (modules as any)[(ext).proxySupport] || modules.onRequest; + let cache = modulesCache.get(ext); + if (!cache) { + modulesCache.set(ext, cache = {}); } - return modules.default; + if (!cache[request]) { + let mod = modules.default; + if (ext && ext.enableProposedApi) { + mod = (modules as any)[(ext).proxySupport] || modules.onRequest; + } + cache[request] = { ...mod }; // Copy to work around #93167. + } + return cache[request]; }; }); } diff --git a/src/vs/workbench/services/extensions/test/common/rpcProtocol.test.ts b/src/vs/workbench/services/extensions/test/common/rpcProtocol.test.ts index 6f6687a10d8..637a617c76c 100644 --- a/src/vs/workbench/services/extensions/test/common/rpcProtocol.test.ts +++ b/src/vs/workbench/services/extensions/test/common/rpcProtocol.test.ts @@ -212,4 +212,13 @@ suite('RPCProtocol', () => { assert.equal(res, 7); }); }); + + test('issue #81424: SerializeRequest should throw if an argument can not be serialized', () => { + let badObject = {}; + (badObject).loop = badObject; + + assert.throws(() => { + bProxy.$m(badObject, '2'); + }); + }); }); diff --git a/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts b/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts index 7a9048cba8d..b40e932de44 100644 --- a/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts +++ b/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts @@ -11,7 +11,6 @@ import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/cont import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IFilesConfiguration, AutoSaveConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files'; import { isUndefinedOrNull } from 'vs/base/common/types'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { equals } from 'vs/base/common/objects'; import { URI } from 'vs/base/common/uri'; import { isWeb } from 'vs/base/common/platform'; @@ -83,8 +82,7 @@ export class FilesConfigurationService extends Disposable implements IFilesConfi constructor( @IContextKeyService contextKeyService: IContextKeyService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(); @@ -203,7 +201,7 @@ export class FilesConfigurationService extends Disposable implements IFilesConfi } get isHotExitEnabled(): boolean { - return !this.environmentService.isExtensionDevelopment && this.currentHotExitConfig !== HotExitConfiguration.OFF; + return this.currentHotExitConfig !== HotExitConfiguration.OFF; } get hotExitConfiguration(): string { diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index 8f081c5e22e..c1faf9e49f2 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -5,25 +5,23 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { IEditor } from 'vs/editor/common/editorCommon'; -import { ITextEditorOptions, IResourceInput, TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; -import { IEditorInput, IEditor as IBaseEditor, Extensions as EditorExtensions, EditorInput, IEditorCloseEvent, IEditorInputFactoryRegistry, toResource, IEditorIdentifier, GroupIdentifier, EditorsOrder } from 'vs/workbench/common/editor'; +import { ITextEditorOptions, IResourceEditorInput, TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; +import { IEditorInput, IEditorPane, Extensions as EditorExtensions, EditorInput, IEditorCloseEvent, IEditorInputFactoryRegistry, toResource, IEditorIdentifier, GroupIdentifier, EditorsOrder } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { FileChangesEvent, IFileService, FileChangeType, FILES_EXCLUDE_CONFIG } from 'vs/platform/files/common/files'; +import { FileChangesEvent, IFileService, FileChangeType } from 'vs/platform/files/common/files'; import { Selection } from 'vs/editor/common/core/selection'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { Registry } from 'vs/platform/registry/common/platform'; import { Event } from 'vs/base/common/event'; -import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { getCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { getExcludes, ISearchConfiguration } from 'vs/workbench/services/search/common/search'; -import { IExpression } from 'vs/base/common/glob'; +import { createResourceExcludeMatcher } from 'vs/workbench/services/search/common/search'; import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ResourceGlobMatcher } from 'vs/workbench/common/resources'; import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -82,7 +80,7 @@ interface ISerializedEditorHistoryEntry { } interface IStackEntry { - input: IEditorInput | IResourceInput; + input: IEditorInput | IResourceEditorInput; selection?: Selection; } @@ -98,8 +96,8 @@ export class HistoryService extends Disposable implements IHistoryService { private readonly activeEditorListeners = this._register(new DisposableStore()); private lastActiveEditor?: IEditorIdentifier; - private readonly editorHistoryListeners: Map = new Map(); - private readonly editorStackListeners: Map = new Map(); + private readonly editorHistoryListeners = new Map(); + private readonly editorStackListeners = new Map(); constructor( @IEditorService private readonly editorService: EditorServiceImpl, @@ -124,13 +122,13 @@ export class HistoryService extends Disposable implements IHistoryService { this._register(this.editorService.onDidCloseEditor(event => this.onEditorClosed(event))); this._register(this.storageService.onWillSaveState(() => this.saveState())); this._register(this.fileService.onDidFilesChange(event => this.onDidFilesChange(event))); - this._register(this.resourceFilter.onExpressionChange(() => this.removeExcludedFromHistory())); + this._register(this.resourceExcludeMatcher.onExpressionChange(() => this.removeExcludedFromHistory())); this._register(this.editorService.onDidMostRecentlyActiveEditorsChange(() => this.handleEditorEventInRecentEditorsStack())); // if the service is created late enough that an editor is already opened // make sure to trigger the onActiveEditorChanged() to track the editor // properly (fixes https://github.com/Microsoft/vscode/issues/59908) - if (this.editorService.activeControl) { + if (this.editorService.activeEditorPane) { this.onActiveEditorChanged(); } @@ -140,7 +138,7 @@ export class HistoryService extends Disposable implements IHistoryService { mouseBackForwardSupportListener.clear(); if (this.configurationService.getValue('workbench.editor.mouseBackForwardToNavigate')) { - mouseBackForwardSupportListener.add(addDisposableListener(this.layoutService.getWorkbenchElement(), EventType.MOUSE_DOWN, e => this.onMouseDown(e))); + mouseBackForwardSupportListener.add(addDisposableListener(this.layoutService.container, EventType.MOUSE_DOWN, e => this.onMouseDown(e))); } }; @@ -169,44 +167,44 @@ export class HistoryService extends Disposable implements IHistoryService { } private onActiveEditorChanged(): void { - const activeControl = this.editorService.activeControl; - if (this.lastActiveEditor && this.matchesEditor(this.lastActiveEditor, activeControl)) { + const activeEditorPane = this.editorService.activeEditorPane; + if (this.lastActiveEditor && this.matchesEditor(this.lastActiveEditor, activeEditorPane)) { return; // return if the active editor is still the same } // Remember as last active editor (can be undefined if none opened) - this.lastActiveEditor = activeControl?.input && activeControl.group ? { editor: activeControl.input, groupId: activeControl.group.id } : undefined; + this.lastActiveEditor = activeEditorPane?.input && activeEditorPane.group ? { editor: activeEditorPane.input, groupId: activeEditorPane.group.id } : undefined; // Dispose old listeners this.activeEditorListeners.clear(); // Propagate to history - this.handleActiveEditorChange(activeControl); + this.handleActiveEditorChange(activeEditorPane); // Apply listener for selection changes if this is a text editor - const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget); + const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl); const activeEditor = this.editorService.activeEditor; - if (activeTextEditorWidget) { + if (activeTextEditorControl) { // Debounce the event with a timeout of 0ms so that multiple calls to // editor.setSelection() are folded into one. We do not want to record // subsequent history navigations for such API calls. - this.activeEditorListeners.add(Event.debounce(activeTextEditorWidget.onDidChangeCursorPosition, (last, event) => event, 0)((event => { - this.handleEditorSelectionChangeEvent(activeControl, event); + this.activeEditorListeners.add(Event.debounce(activeTextEditorControl.onDidChangeCursorPosition, (last, event) => event, 0)((event => { + this.handleEditorSelectionChangeEvent(activeEditorPane, event); }))); // Track the last edit location by tracking model content change events // Use a debouncer to make sure to capture the correct cursor position // after the model content has changed. - this.activeEditorListeners.add(Event.debounce(activeTextEditorWidget.onDidChangeModelContent, (last, event) => event, 0)((event => { + this.activeEditorListeners.add(Event.debounce(activeTextEditorControl.onDidChangeModelContent, (last, event) => event, 0)((event => { if (activeEditor) { - this.rememberLastEditLocation(activeEditor, activeTextEditorWidget); + this.rememberLastEditLocation(activeEditor, activeTextEditorControl); } }))); } } - private matchesEditor(identifier: IEditorIdentifier, editor?: IBaseEditor): boolean { + private matchesEditor(identifier: IEditorIdentifier, editor?: IEditorPane): boolean { if (!editor || !editor.group) { return false; } @@ -224,11 +222,11 @@ export class HistoryService extends Disposable implements IHistoryService { } } - private handleEditorSelectionChangeEvent(editor?: IBaseEditor, event?: ICursorPositionChangedEvent): void { + private handleEditorSelectionChangeEvent(editor?: IEditorPane, event?: ICursorPositionChangedEvent): void { this.handleEditorEventInNavigationStack(editor, event); } - private handleActiveEditorChange(editor?: IBaseEditor): void { + private handleActiveEditorChange(editor?: IEditorPane): void { this.handleEditorEventInHistory(editor); this.handleEditorEventInNavigationStack(editor); } @@ -245,7 +243,7 @@ export class HistoryService extends Disposable implements IHistoryService { disposables.add(toDispose); } - private clearOnEditorDispose(editor: IEditorInput | IResourceInput | FileChangesEvent, mapEditorToDispose: Map): void { + private clearOnEditorDispose(editor: IEditorInput | IResourceEditorInput | FileChangesEvent, mapEditorToDispose: Map): void { if (editor instanceof EditorInput) { const disposables = mapEditorToDispose.get(editor); if (disposables) { @@ -255,21 +253,21 @@ export class HistoryService extends Disposable implements IHistoryService { } } - remove(input: IEditorInput | IResourceInput): void; + remove(input: IEditorInput | IResourceEditorInput): void; remove(input: FileChangesEvent): void; - remove(arg1: IEditorInput | IResourceInput | FileChangesEvent): void { + remove(arg1: IEditorInput | IResourceEditorInput | FileChangesEvent): void { this.removeFromHistory(arg1); this.removeFromNavigationStack(arg1); this.removeFromRecentlyClosedFiles(arg1); this.removeFromRecentlyOpened(arg1); } - private removeFromRecentlyOpened(arg1: IEditorInput | IResourceInput | FileChangesEvent): void { + private removeFromRecentlyOpened(arg1: IEditorInput | IResourceEditorInput | FileChangesEvent): void { if (arg1 instanceof EditorInput || arg1 instanceof FileChangesEvent) { return; // for now do not delete from file events since recently open are likely out of workspace files for which there are no delete events } - const input = arg1 as IResourceInput; + const input = arg1 as IResourceEditorInput; this.workspacesService.removeRecentlyOpened([input.resource]); } @@ -341,10 +339,10 @@ export class HistoryService extends Disposable implements IHistoryService { const navigateToStackEntry = this.navigationStack[this.navigationStackIndex]; - this.doNavigate(navigateToStackEntry).finally(() => this.navigatingInStack = false); + this.doNavigate(navigateToStackEntry).finally(() => { this.navigatingInStack = false; }); } - private doNavigate(location: IStackEntry): Promise { + private doNavigate(location: IStackEntry): Promise { const options: ITextEditorOptions = { revealIfOpened: true, // support to navigate across editor groups, selection: location.selection, @@ -355,10 +353,10 @@ export class HistoryService extends Disposable implements IHistoryService { return this.editorService.openEditor(location.input, options); } - return this.editorService.openEditor({ resource: (location.input as IResourceInput).resource, options }); + return this.editorService.openEditor({ resource: (location.input as IResourceEditorInput).resource, options }); } - private handleEditorEventInNavigationStack(control: IBaseEditor | undefined, event?: ICursorPositionChangedEvent): void { + private handleEditorEventInNavigationStack(control: IEditorPane | undefined, event?: ICursorPositionChangedEvent): void { const codeEditor = control ? getCodeEditor(control.getControl()) : undefined; // treat editor changes that happen as part of stack navigation specially @@ -392,7 +390,7 @@ export class HistoryService extends Disposable implements IHistoryService { } } - private handleTextEditorEventInNavigationStack(editor: IBaseEditor, editorControl: IEditor, event?: ICursorPositionChangedEvent): void { + private handleTextEditorEventInNavigationStack(editor: IEditorPane, editorControl: IEditor, event?: ICursorPositionChangedEvent): void { if (!editor.input) { return; } @@ -413,7 +411,7 @@ export class HistoryService extends Disposable implements IHistoryService { this.currentTextEditorState = stateCandidate; } - private handleNonTextEditorEventInNavigationStack(editor: IBaseEditor): void { + private handleNonTextEditorEventInNavigationStack(editor: IEditorPane): void { if (!editor.input) { return; } @@ -458,8 +456,8 @@ export class HistoryService extends Disposable implements IHistoryService { } } - const stackInput = this.preferResourceInput(input); - const entry = { input: stackInput, selection }; + const stackEditorInput = this.preferResourceEditorInput(input); + const entry = { input: stackEditorInput, selection }; // Replace at current position let removedEntries: IStackEntry[] = []; @@ -499,15 +497,15 @@ export class HistoryService extends Disposable implements IHistoryService { // Remove this from the stack unless the stack input is a resource // that can easily be restored even when the input gets disposed - if (stackInput instanceof EditorInput) { - this.onEditorDispose(stackInput, () => this.removeFromNavigationStack(stackInput), this.editorStackListeners); + if (stackEditorInput instanceof EditorInput) { + this.onEditorDispose(stackEditorInput, () => this.removeFromNavigationStack(stackEditorInput), this.editorStackListeners); } // Context Keys this.updateContextKeys(); } - private preferResourceInput(input: IEditorInput): IEditorInput | IResourceInput { + private preferResourceEditorInput(input: IEditorInput): IEditorInput | IResourceEditorInput { const resource = input.resource; if (resource && (resource.scheme === Schemas.file || resource.scheme === Schemas.vscodeRemote || resource.scheme === Schemas.userData)) { // for now, only prefer well known schemes that we control to prevent @@ -530,7 +528,7 @@ export class HistoryService extends Disposable implements IHistoryService { return selectionA.startLineNumber === selectionB.startLineNumber; // we consider the history entry same if we are on the same line } - private removeFromNavigationStack(arg1: IEditorInput | IResourceInput | FileChangesEvent): void { + private removeFromNavigationStack(arg1: IEditorInput | IResourceEditorInput | FileChangesEvent): void { this.navigationStack = this.navigationStack.filter(e => { const matches = this.matches(arg1, e.input); @@ -548,15 +546,15 @@ export class HistoryService extends Disposable implements IHistoryService { this.updateContextKeys(); } - private matches(arg1: IEditorInput | IResourceInput | FileChangesEvent, inputB: IEditorInput | IResourceInput): boolean { + private matches(arg1: IEditorInput | IResourceEditorInput | FileChangesEvent, inputB: IEditorInput | IResourceEditorInput): boolean { if (arg1 instanceof FileChangesEvent) { if (inputB instanceof EditorInput) { - return false; // we only support this for IResourceInput + return false; // we only support this for IResourceEditorInput } - const resourceInputB = inputB as IResourceInput; + const resourceEditorInputB = inputB as IResourceEditorInput; - return arg1.contains(resourceInputB.resource, FileChangeType.DELETED); + return arg1.contains(resourceEditorInputB.resource, FileChangeType.DELETED); } if (arg1 instanceof EditorInput && inputB instanceof EditorInput) { @@ -564,20 +562,20 @@ export class HistoryService extends Disposable implements IHistoryService { } if (arg1 instanceof EditorInput) { - return this.matchesFile((inputB as IResourceInput).resource, arg1); + return this.matchesFile((inputB as IResourceEditorInput).resource, arg1); } if (inputB instanceof EditorInput) { - return this.matchesFile((arg1 as IResourceInput).resource, inputB); + return this.matchesFile((arg1 as IResourceEditorInput).resource, inputB); } - const resourceInputA = arg1 as IResourceInput; - const resourceInputB = inputB as IResourceInput; + const resourceEditorInputA = arg1 as IResourceEditorInput; + const resourceEditorInputB = inputB as IResourceEditorInput; - return resourceInputA && resourceInputB && resourceInputA.resource.toString() === resourceInputB.resource.toString(); + return resourceEditorInputA && resourceEditorInputB && resourceEditorInputA.resource.toString() === resourceEditorInputB.resource.toString(); } - private matchesFile(resource: URI, arg2: IEditorInput | IResourceInput | FileChangesEvent): boolean { + private matchesFile(resource: URI, arg2: IEditorInput | IResourceEditorInput | FileChangesEvent): boolean { if (arg2 instanceof FileChangesEvent) { return arg2.contains(resource, FileChangeType.DELETED); } @@ -595,9 +593,9 @@ export class HistoryService extends Disposable implements IHistoryService { return inputResource.toString() === resource.toString(); } - const resourceInput = arg2 as IResourceInput; + const resourceEditorInput = arg2 as IResourceEditorInput; - return resourceInput?.resource.toString() === resource.toString(); + return resourceEditorInput?.resource.toString() === resource.toString(); } //#endregion @@ -666,7 +664,7 @@ export class HistoryService extends Disposable implements IHistoryService { return false; } - private removeFromRecentlyClosedFiles(arg1: IEditorInput | IResourceInput | FileChangesEvent): void { + private removeFromRecentlyClosedFiles(arg1: IEditorInput | IResourceEditorInput | FileChangesEvent): void { this.recentlyClosedFiles = this.recentlyClosedFiles.filter(e => !this.matchesFile(e.resource, arg1)); this.canReopenClosedEditorContextKey.set(this.recentlyClosedFiles.length > 0); } @@ -677,11 +675,11 @@ export class HistoryService extends Disposable implements IHistoryService { private lastEditLocation: IStackEntry | undefined; - private rememberLastEditLocation(activeEditor: IEditorInput, activeTextEditorWidget: ICodeEditor): void { + private rememberLastEditLocation(activeEditor: IEditorInput, activeTextEditorControl: ICodeEditor): void { this.lastEditLocation = { input: activeEditor }; this.canNavigateToLastEditLocationContextKey.set(true); - const position = activeTextEditorWidget.getPosition(); + const position = activeTextEditorControl.getPosition(); if (position) { this.lastEditLocation.selection = new Selection(position.lineNumber, position.column, position.lineNumber, position.column); } @@ -716,21 +714,11 @@ export class HistoryService extends Disposable implements IHistoryService { private static readonly MAX_HISTORY_ITEMS = 200; private static readonly HISTORY_STORAGE_KEY = 'history.entries'; - private history: Array | undefined = undefined; + private history: Array | undefined = undefined; - private readonly resourceFilter = this._register(this.instantiationService.createInstance( - ResourceGlobMatcher, - (root?: URI) => this.getExcludes(root), - (event: IConfigurationChangeEvent) => event.affectsConfiguration(FILES_EXCLUDE_CONFIG) || event.affectsConfiguration('search.exclude') - )); + private readonly resourceExcludeMatcher = this._register(createResourceExcludeMatcher(this.instantiationService, this.configurationService)); - private getExcludes(root?: URI): IExpression { - const scope = root ? { resource: root } : undefined; - - return getExcludes(scope ? this.configurationService.getValue(scope) : this.configurationService.getValue())!; - } - - private handleEditorEventInHistory(editor?: IBaseEditor): void { + private handleEditorEventInHistory(editor?: IEditorPane): void { // Ensure we have not configured to exclude input and don't track invalid inputs const input = editor?.input; @@ -738,7 +726,7 @@ export class HistoryService extends Disposable implements IHistoryService { return; } - const historyInput = this.preferResourceInput(input); + const historyInput = this.preferResourceEditorInput(input); // Remove any existing entry and add to the beginning this.ensureHistoryLoaded(this.history); @@ -757,14 +745,14 @@ export class HistoryService extends Disposable implements IHistoryService { } } - private include(input: IEditorInput | IResourceInput): boolean { + private include(input: IEditorInput | IResourceEditorInput): boolean { if (input instanceof EditorInput) { return true; // include any non files } - const resourceInput = input as IResourceInput; + const resourceEditorInput = input as IResourceEditorInput; - return !this.resourceFilter.matches(resourceInput.resource); + return !this.resourceExcludeMatcher.matches(resourceEditorInput.resource); } private removeExcludedFromHistory(): void { @@ -782,7 +770,7 @@ export class HistoryService extends Disposable implements IHistoryService { }); } - private removeFromHistory(arg1: IEditorInput | IResourceInput | FileChangesEvent): void { + private removeFromHistory(arg1: IEditorInput | IResourceEditorInput | FileChangesEvent): void { this.ensureHistoryLoaded(this.history); this.history = this.history.filter(e => { @@ -804,19 +792,19 @@ export class HistoryService extends Disposable implements IHistoryService { this.editorHistoryListeners.clear(); } - getHistory(): ReadonlyArray { + getHistory(): ReadonlyArray { this.ensureHistoryLoaded(this.history); return this.history.slice(0); } - private ensureHistoryLoaded(history: Array | undefined): asserts history { + private ensureHistoryLoaded(history: Array | undefined): asserts history { if (!this.history) { this.history = this.loadHistory(); } } - private loadHistory(): Array { + private loadHistory(): Array { let entries: ISerializedEditorHistoryEntry[] = []; const entriesRaw = this.storageService.get(HistoryService.HISTORY_STORAGE_KEY, StorageScope.WORKSPACE); @@ -835,7 +823,7 @@ export class HistoryService extends Disposable implements IHistoryService { })); } - private safeLoadHistoryEntry(registry: IEditorInputFactoryRegistry, entry: ISerializedEditorHistoryEntry): IEditorInput | IResourceInput | undefined { + private safeLoadHistoryEntry(registry: IEditorInputFactoryRegistry, entry: ISerializedEditorHistoryEntry): IEditorInput | IResourceEditorInput | undefined { const serializedEditorHistoryEntry = entry; // File resource: via URI.revive() @@ -882,7 +870,7 @@ export class HistoryService extends Disposable implements IHistoryService { // File resource: via URI.toJSON() else { - return { resourceJSON: (input as IResourceInput).resource.toJSON() }; + return { resourceJSON: (input as IResourceEditorInput).resource.toJSON() }; } return undefined; @@ -919,12 +907,12 @@ export class HistoryService extends Disposable implements IHistoryService { continue; } - const resourceInput = input as IResourceInput; - if (schemeFilter && resourceInput.resource.scheme !== schemeFilter) { + const resourceEditorInput = input as IResourceEditorInput; + if (schemeFilter && resourceEditorInput.resource.scheme !== schemeFilter) { continue; } - const resourceWorkspace = this.contextService.getWorkspaceFolder(resourceInput.resource); + const resourceWorkspace = this.contextService.getWorkspaceFolder(resourceEditorInput.resource); if (resourceWorkspace) { return resourceWorkspace.uri; } @@ -947,7 +935,7 @@ export class HistoryService extends Disposable implements IHistoryService { if (input instanceof EditorInput) { resource = toResource(input, { filterByScheme }); } else { - resource = (input as IResourceInput).resource; + resource = (input as IResourceEditorInput).resource; } if (resource?.scheme === filterByScheme) { diff --git a/src/vs/workbench/services/history/common/history.ts b/src/vs/workbench/services/history/common/history.ts index 598a446f9df..04349f87efe 100644 --- a/src/vs/workbench/services/history/common/history.ts +++ b/src/vs/workbench/services/history/common/history.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { IEditorInput, GroupIdentifier } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; @@ -42,7 +42,7 @@ export interface IHistoryService { /** * Removes an entry from history. */ - remove(input: IEditorInput | IResourceInput): void; + remove(input: IEditorInput | IResourceEditorInput): void; /** * Clears all history. @@ -57,7 +57,7 @@ export interface IHistoryService { /** * Get the entire history of editors that were opened. */ - getHistory(): ReadonlyArray; + getHistory(): ReadonlyArray; /** * Looking at the editor history, returns the workspace root of the last file that was diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index 85de4203e54..8d8846a27cd 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -6,8 +6,8 @@ import { Event } from 'vs/base/common/event'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { IResourceEditor, IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; +import { IResourceEditorInputType, IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWindowSettings, IWindowOpenable, IOpenWindowOptions, isFolderToOpen, isWorkspaceToOpen, isFileToOpen, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; import { pathsToEditors } from 'vs/workbench/common/editor'; @@ -17,7 +17,6 @@ import { trackFocus } from 'vs/base/browser/dom'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { mapToSerializable } from 'vs/base/common/map'; /** * A workspace to open in the workbench can either be: @@ -59,7 +58,7 @@ export class BrowserHostService extends Disposable implements IHostService { private workspaceProvider: IWorkspaceProvider; constructor( - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @ILayoutService private readonly layoutService: ILayoutService, @IEditorService private readonly editorService: IEditorService, @IConfigurationService private readonly configurationService: IConfigurationService, @IFileService private readonly fileService: IFileService, @@ -133,7 +132,7 @@ export class BrowserHostService extends Disposable implements IHostService { // Same Window: open via editor service in current window if (this.shouldReuse(options, true /* file */)) { - const inputs: IResourceEditor[] = await pathsToEditors([openable], this.fileService); + const inputs: IResourceEditorInputType[] = await pathsToEditors([openable], this.fileService); this.editorService.openEditors(inputs); } @@ -142,7 +141,7 @@ export class BrowserHostService extends Disposable implements IHostService { const environment = new Map(); environment.set('openFile', openable.fileUri.toString()); - this.workspaceProvider.open(undefined, { payload: mapToSerializable(environment) }); + this.workspaceProvider.open(undefined, { payload: Array.from(environment.entries()) }); } } } @@ -177,7 +176,7 @@ export class BrowserHostService extends Disposable implements IHostService { } async toggleFullScreen(): Promise { - const target = this.layoutService.getWorkbenchElement(); + const target = this.layoutService.container; // Chromium if (document.fullscreen !== undefined) { diff --git a/src/vs/workbench/services/host/electron-browser/desktopHostService.ts b/src/vs/workbench/services/host/electron-browser/desktopHostService.ts index b009d228026..6341b276f03 100644 --- a/src/vs/workbench/services/host/electron-browser/desktopHostService.ts +++ b/src/vs/workbench/services/host/electron-browser/desktopHostService.ts @@ -11,7 +11,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWindowOpenable, IOpenWindowOptions, isFolderToOpen, isWorkspaceToOpen, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; export class DesktopHostService extends Disposable implements IHostService { @@ -20,16 +20,15 @@ export class DesktopHostService extends Disposable implements IHostService { constructor( @IElectronService private readonly electronService: IElectronService, @ILabelService private readonly labelService: ILabelService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IElectronEnvironmentService private readonly electronEnvironmentService: IElectronEnvironmentService + @IWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService ) { super(); } get onDidChangeFocus(): Event { return this._onDidChangeFocus; } private _onDidChangeFocus: Event = Event.any( - Event.map(Event.filter(this.electronService.onWindowFocus, id => id === this.electronEnvironmentService.windowId), () => this.hasFocus), - Event.map(Event.filter(this.electronService.onWindowBlur, id => id === this.electronEnvironmentService.windowId), () => this.hasFocus) + Event.map(Event.filter(this.electronService.onWindowFocus, id => id === this.environmentService.configuration.windowId), () => this.hasFocus), + Event.map(Event.filter(this.electronService.onWindowBlur, id => id === this.environmentService.configuration.windowId), () => this.hasFocus) ); get hasFocus(): boolean { @@ -43,7 +42,7 @@ export class DesktopHostService extends Disposable implements IHostService { return false; } - return activeWindowId === this.electronEnvironmentService.windowId; + return activeWindowId === this.environmentService.configuration.windowId; } openWindow(options?: IOpenEmptyWindowOptions): Promise; diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 42dfc300992..1bd4e2d252e 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -11,7 +11,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { Keybinding, ResolvedKeybinding, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; -import { OS, OperatingSystem } from 'vs/base/common/platform'; +import { OS, OperatingSystem, isMacintosh } from 'vs/base/common/platform'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Extensions as ConfigExtensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; @@ -161,6 +161,18 @@ const NUMPAD_PRINTABLE_SCANCODES = [ ScanCode.NumpadDecimal ]; +const otherMacNumpadMapping = new Map(); +otherMacNumpadMapping.set(ScanCode.Numpad1, KeyCode.KEY_1); +otherMacNumpadMapping.set(ScanCode.Numpad2, KeyCode.KEY_2); +otherMacNumpadMapping.set(ScanCode.Numpad3, KeyCode.KEY_3); +otherMacNumpadMapping.set(ScanCode.Numpad4, KeyCode.KEY_4); +otherMacNumpadMapping.set(ScanCode.Numpad5, KeyCode.KEY_5); +otherMacNumpadMapping.set(ScanCode.Numpad6, KeyCode.KEY_6); +otherMacNumpadMapping.set(ScanCode.Numpad7, KeyCode.KEY_7); +otherMacNumpadMapping.set(ScanCode.Numpad8, KeyCode.KEY_8); +otherMacNumpadMapping.set(ScanCode.Numpad9, KeyCode.KEY_9); +otherMacNumpadMapping.set(ScanCode.Numpad0, KeyCode.KEY_0); + export class WorkbenchKeybindingService extends AbstractKeybindingService { private _keyboardMapper: IKeyboardMapper; @@ -589,6 +601,10 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { // NumLock is on or this is /, *, -, + on the numpad return true; } + if (isMacintosh && event.keyCode === otherMacNumpadMapping.get(code)) { + // on macOS, the numpad keys can also map to keys 1 - 0. + return true; + } return false; } diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/browserKeyboardMapper.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/browserKeyboardMapper.test.ts index 93aec7997a5..0405c4f0556 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/browserKeyboardMapper.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/browserKeyboardMapper.test.ts @@ -12,8 +12,8 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { INotificationService } from 'vs/platform/notification/common/notification'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { TestStorageService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; +import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; class TestKeyboardMapperFactory extends BrowserKeyboardMapperFactoryBase { constructor(notificationService: INotificationService, storageService: IStorageService, commandService: ICommandService) { diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts index 5c52f1a73b1..1e2b68241c2 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts @@ -38,7 +38,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { KeybindingsEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; -import { TestBackupFileService, TestContextService, TestEditorGroupsService, TestEditorService, TestLifecycleService, TestTextResourcePropertiesService, TestWorkingCopyService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestBackupFileService, TestEditorGroupsService, TestEditorService, TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices'; import { FileService } from 'vs/platform/files/common/fileService'; import { Schemas } from 'vs/base/common/network'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; @@ -54,11 +54,14 @@ import { IFilesConfigurationService, FilesConfigurationService } from 'vs/workbe import { WorkingCopyFileService, IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; +import { TestTextResourcePropertiesService, TestContextService, TestWorkingCopyService } from 'vs/workbench/test/common/workbenchTestServices'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; class TestEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(private _appSettingsHome: URI) { - super(TestWindowConfiguration, TestWindowConfiguration.execPath, TestWindowConfiguration.windowId); + super(TestWindowConfiguration, TestWindowConfiguration.execPath); } get appSettingsHome() { return this._appSettingsHome; } @@ -106,6 +109,7 @@ suite('KeybindingsEditing', () => { instantiationService.stub(IFilesConfigurationService, instantiationService.createInstance(FilesConfigurationService)); instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(instantiationService.get(IConfigurationService))); instantiationService.stub(IUndoRedoService, instantiationService.createInstance(UndoRedoService)); + instantiationService.stub(IThemeService, new TestThemeService()); instantiationService.stub(IModelService, instantiationService.createInstance(ModelServiceImpl)); const fileService = new FileService(new NullLogService()); const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); diff --git a/src/vs/workbench/services/label/common/labelService.ts b/src/vs/workbench/services/label/common/labelService.ts index 8bc3fa0db09..f01b1b1a2af 100644 --- a/src/vs/workbench/services/label/common/labelService.ts +++ b/src/vs/workbench/services/label/common/labelService.ts @@ -5,18 +5,18 @@ import { localize } from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import * as paths from 'vs/base/common/path'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceContextService, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { isEqual, basenameOrAuthority, basename, joinPath, dirname } from 'vs/base/common/resources'; import { tildify, getPathLabel } from 'vs/base/common/labels'; -import { ltrim, endsWith } from 'vs/base/common/strings'; +import { ltrim } from 'vs/base/common/strings'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, WORKSPACE_EXTENSION, toWorkspaceIdentifier, isWorkspaceIdentifier, isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces'; -import { ILabelService, ResourceLabelFormatter, ResourceLabelFormatting } from 'vs/platform/label/common/label'; +import { ILabelService, ResourceLabelFormatter, ResourceLabelFormatting, IFormatterChangeEvent } from 'vs/platform/label/common/label'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { match } from 'vs/base/common/glob'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; @@ -89,19 +89,20 @@ class ResourceLabelFormattersHandler implements IWorkbenchContribution { } Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ResourceLabelFormattersHandler, LifecyclePhase.Restored); -export class LabelService implements ILabelService { +export class LabelService extends Disposable implements ILabelService { + _serviceBrand: undefined; private formatters: ResourceLabelFormatter[] = []; - private readonly _onDidChangeFormatters = new Emitter(); + + private readonly _onDidChangeFormatters = this._register(new Emitter()); + readonly onDidChangeFormatters = this._onDidChangeFormatters.event; constructor( @IEnvironmentService private readonly environmentService: IEnvironmentService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - ) { } - - get onDidChangeFormatters(): Event { - return this._onDidChangeFormatters.event; + ) { + super(); } findFormatting(resource: URI): ResourceLabelFormatting | undefined { @@ -199,7 +200,7 @@ export class LabelService implements ILabelService { // Workspace: Saved let filename = basename(workspace.configPath); - if (endsWith(filename, WORKSPACE_EXTENSION)) { + if (filename.endsWith(WORKSPACE_EXTENSION)) { filename = filename.substr(0, filename.length - WORKSPACE_EXTENSION.length - 1); } let label; @@ -226,12 +227,12 @@ export class LabelService implements ILabelService { registerFormatter(formatter: ResourceLabelFormatter): IDisposable { this.formatters.push(formatter); - this._onDidChangeFormatters.fire(); + this._onDidChangeFormatters.fire({ scheme: formatter.scheme }); return { dispose: () => { this.formatters = this.formatters.filter(f => f !== formatter); - this._onDidChangeFormatters.fire(); + this._onDidChangeFormatters.fire({ scheme: formatter.scheme }); } }; } @@ -274,7 +275,7 @@ export class LabelService implements ILabelService { private appendSeparatorIfMissing(label: string, formatting: ResourceLabelFormatting): string { let appendedLabel = label; - if (!endsWith(label, formatting.separator)) { + if (!label.endsWith(formatting.separator)) { appendedLabel += formatting.separator; } return appendedLabel; diff --git a/src/vs/workbench/services/label/test/browser/label.test.ts b/src/vs/workbench/services/label/test/browser/label.test.ts index b9afd8221e9..897da308f9a 100644 --- a/src/vs/workbench/services/label/test/browser/label.test.ts +++ b/src/vs/workbench/services/label/test/browser/label.test.ts @@ -4,12 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { TestEnvironmentService, TestContextService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { URI } from 'vs/base/common/uri'; import { sep } from 'vs/base/common/path'; import { isWindows } from 'vs/base/common/platform'; import { LabelService } from 'vs/workbench/services/label/common/labelService'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; suite('URI Label', () => { diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index 47943473c37..f952fa8852c 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -113,11 +113,6 @@ export interface IWorkbenchLayoutService extends ILayoutService { */ setActivityBarHidden(hidden: boolean): void; - /** - * Number of pixels (adjusted for zooming) that the title bar (if visible) pushes down the workbench contents. - */ - getTitleBarOffset(): number; - /** * * Set editor area hidden or not @@ -185,11 +180,6 @@ export interface IWorkbenchLayoutService extends ILayoutService { */ getWorkbenchContainer(): HTMLElement; - /** - * Returns the element that contains the workbench. - */ - getWorkbenchElement(): HTMLElement; - /** * Toggles the workbench in and out of zen mode - parts get hidden and window goes fullscreen. */ @@ -215,7 +205,6 @@ export interface IWorkbenchLayoutService extends ILayoutService { */ registerPart(part: Part): void; - /** * Returns whether the window is maximized. */ diff --git a/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts b/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts index 41e6b4e34c4..0df88daa425 100644 --- a/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts @@ -28,6 +28,9 @@ export class BrowserLifecycleService extends AbstractLifecycleService { } private onBeforeUnload(): string | null { + const logService = this.logService; + logService.info('[lifecycle] onBeforeUnload triggered'); + let veto = false; // Before Shutdown @@ -36,7 +39,7 @@ export class BrowserLifecycleService extends AbstractLifecycleService { if (value === true) { veto = true; } else if (value instanceof Promise && !veto) { - console.warn(new Error('Long running onBeforeShutdown currently not supported in the web')); + logService.error('[lifecycle] Long running onBeforeShutdown currently not supported in the web'); veto = true; } }, @@ -51,7 +54,7 @@ export class BrowserLifecycleService extends AbstractLifecycleService { // No Veto: continue with Will Shutdown this._onWillShutdown.fire({ join() { - console.warn(new Error('Long running onWillShutdown currently not supported in the web')); + logService.error('[lifecycle] Long running onWillShutdown currently not supported in the web'); }, reason: ShutdownReason.QUIT }); diff --git a/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts b/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts index ba08b6e7b79..9fe1d298205 100644 --- a/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts @@ -7,7 +7,6 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import { ShutdownReason, StartupKind, handleVetos, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IStorageService, StorageScope, WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { ipcRenderer as ipc } from 'electron'; -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -15,6 +14,8 @@ import { AbstractLifecycleService } from 'vs/platform/lifecycle/common/lifecycle import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import Severity from 'vs/base/common/severity'; import { localize } from 'vs/nls'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; export class NativeLifecycleService extends AbstractLifecycleService { @@ -26,7 +27,7 @@ export class NativeLifecycleService extends AbstractLifecycleService { constructor( @INotificationService private readonly notificationService: INotificationService, - @IElectronEnvironmentService private readonly electronEnvironmentService: IElectronEnvironmentService, + @IWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService, @IStorageService readonly storageService: IStorageService, @ILogService readonly logService: ILogService ) { @@ -56,7 +57,7 @@ export class NativeLifecycleService extends AbstractLifecycleService { } private registerListeners(): void { - const windowId = this.electronEnvironmentService.windowId; + const windowId = this.environmentService.configuration.windowId; // Main side indicates that window is about to unload, check for vetos ipc.on('vscode:onBeforeUnload', (_event: unknown, reply: { okChannel: string, cancelChannel: string, reason: ShutdownReason }) => { diff --git a/src/vs/workbench/services/log/common/keyValueLogProvider.ts b/src/vs/workbench/services/log/common/keyValueLogProvider.ts index 0db6b00da32..da0fdb3f167 100644 --- a/src/vs/workbench/services/log/common/keyValueLogProvider.ts +++ b/src/vs/workbench/services/log/common/keyValueLogProvider.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { IFileSystemProviderWithFileReadWriteCapability, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileOverwriteOptions, FileType, FileDeleteOptions, FileWriteOptions, FileChangeType, FileSystemProviderErrorCode } from 'vs/platform/files/common/files'; +import { IFileSystemProviderWithFileReadWriteCapability, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileOverwriteOptions, FileType, FileDeleteOptions, FileWriteOptions, FileChangeType, createFileSystemProviderError, FileSystemProviderErrorCode } from 'vs/platform/files/common/files'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import { VSBuffer } from 'vs/base/common/buffer'; -import { FileSystemError } from 'vs/workbench/api/common/extHostTypes'; import { isEqualOrParent, joinPath, relativePath } from 'vs/base/common/resources'; import { values } from 'vs/base/common/map'; +import { localize } from 'vs/nls'; export abstract class KeyValueLogProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability { @@ -53,13 +53,13 @@ export abstract class KeyValueLogProvider extends Disposable implements IFileSys size: 0 }; } - return Promise.reject(new FileSystemError(resource, FileSystemProviderErrorCode.FileNotFound)); + throw createFileSystemProviderError(localize('fileNotExists', "File does not exist"), FileSystemProviderErrorCode.FileNotFound); } async readdir(resource: URI): Promise<[string, FileType][]> { const hasKey = await this.hasKey(resource.path); if (hasKey) { - return Promise.reject(new FileSystemError(resource, FileSystemProviderErrorCode.FileNotADirectory)); + throw createFileSystemProviderError(localize('fileNotDirectory', "File is not a directory"), FileSystemProviderErrorCode.FileNotADirectory); } const keys = await this.getAllKeys(); const files: Map = new Map(); @@ -79,7 +79,7 @@ export abstract class KeyValueLogProvider extends Disposable implements IFileSys async readFile(resource: URI): Promise { const hasKey = await this.hasKey(resource.path); if (!hasKey) { - return Promise.reject(new FileSystemError(resource, FileSystemProviderErrorCode.FileNotFound)); + throw createFileSystemProviderError(localize('fileNotFound', "File not found"), FileSystemProviderErrorCode.FileNotFound); } const value = await this.getValue(resource.path); return VSBuffer.fromString(value).buffer; @@ -90,7 +90,7 @@ export abstract class KeyValueLogProvider extends Disposable implements IFileSys if (!hasKey) { const files = await this.readdir(resource); if (files.length) { - return Promise.reject(new FileSystemError(resource, FileSystemProviderErrorCode.FileIsADirectory)); + throw createFileSystemProviderError(localize('fileIsDirectory', "File is Directory"), FileSystemProviderErrorCode.FileIsADirectory); } } await this.setValue(resource.path, VSBuffer.wrap(content).toString()); diff --git a/src/vs/workbench/services/notification/common/notificationService.ts b/src/vs/workbench/services/notification/common/notificationService.ts index 80a451c1589..9d86d1af265 100644 --- a/src/vs/workbench/services/notification/common/notificationService.ts +++ b/src/vs/workbench/services/notification/common/notificationService.ts @@ -19,7 +19,9 @@ export class NotificationService extends Disposable implements INotificationServ private _model: INotificationsModel = this._register(new NotificationsModel()); get model(): INotificationsModel { return this._model; } - constructor(@IStorageService private readonly storageService: IStorageService) { + constructor( + @IStorageService private readonly storageService: IStorageService + ) { super(); } @@ -64,10 +66,10 @@ export class NotificationService extends Disposable implements INotificationServ let handle: INotificationHandle; if (notification.neverShowAgain) { const scope = notification.neverShowAgain.scope === NeverShowAgainScope.WORKSPACE ? StorageScope.WORKSPACE : StorageScope.GLOBAL; + const id = notification.neverShowAgain.id; // If the user already picked to not show the notification // again, we return with a no-op notification here - const id = notification.neverShowAgain.id; if (this.storageService.getBoolean(id, scope)) { return new NoOpNotification(); } @@ -87,11 +89,14 @@ export class NotificationService extends Disposable implements INotificationServ })); // Insert as primary or secondary action - const actions = notification.actions || { primary: [], secondary: [] }; + const actions = { + primary: notification.actions?.primary || [], + secondary: notification.actions?.secondary || [] + }; if (!notification.neverShowAgain.isSecondary) { - actions.primary = [neverShowAgainAction, ...(actions.primary || [])]; // action comes first + actions.primary = [neverShowAgainAction, ...actions.primary]; // action comes first } else { - actions.secondary = [...(actions.secondary || []), neverShowAgainAction]; // actions comes last + actions.secondary = [...actions.secondary, neverShowAgainAction]; // actions comes last } notification.actions = actions; @@ -112,10 +117,10 @@ export class NotificationService extends Disposable implements INotificationServ // Handle neverShowAgain option accordingly if (options?.neverShowAgain) { const scope = options.neverShowAgain.scope === NeverShowAgainScope.WORKSPACE ? StorageScope.WORKSPACE : StorageScope.GLOBAL; + const id = options.neverShowAgain.id; // If the user already picked to not show the notification // again, we return with a no-op notification here - const id = options.neverShowAgain.id; if (this.storageService.getBoolean(id, scope)) { return new NoOpNotification(); } diff --git a/src/vs/workbench/services/output/electron-browser/outputChannelModelService.ts b/src/vs/workbench/services/output/electron-browser/outputChannelModelService.ts index a35755c108a..c4322d81a9c 100644 --- a/src/vs/workbench/services/output/electron-browser/outputChannelModelService.ts +++ b/src/vs/workbench/services/output/electron-browser/outputChannelModelService.ts @@ -21,7 +21,7 @@ import { toLocalISOString } from 'vs/base/common/date'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Emitter, Event } from 'vs/base/common/event'; -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; class OutputChannelBackedByFile extends AbstractFileOutputChannelModel implements IOutputChannelModel { @@ -203,8 +203,7 @@ export class OutputChannelModelService extends AsbtractOutputChannelModelService constructor( @IInstantiationService instantiationService: IInstantiationService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IElectronEnvironmentService private readonly electronEnvironmentService: IElectronEnvironmentService, + @IWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService, @IFileService private readonly fileService: IFileService ) { super(instantiationService); @@ -218,7 +217,7 @@ export class OutputChannelModelService extends AsbtractOutputChannelModelService private _outputDir: Promise | null = null; private get outputDir(): Promise { if (!this._outputDir) { - const outputDir = URI.file(join(this.environmentService.logsPath, `output_${this.electronEnvironmentService.windowId}_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`)); + const outputDir = URI.file(join(this.environmentService.logsPath, `output_${this.environmentService.configuration.windowId}_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`)); this._outputDir = this.fileService.createFolder(outputDir).then(() => outputDir); } return this._outputDir; diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index d0441ff196b..bc872a3633e 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -8,11 +8,9 @@ import { parse } from 'vs/base/common/json'; import { Disposable } from 'vs/base/common/lifecycle'; import * as network from 'vs/base/common/network'; import { assign } from 'vs/base/common/objects'; -import * as strings from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { getCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditOperation } from 'vs/editor/common/core/editOperation'; -import { IPosition, Position } from 'vs/editor/common/core/position'; +import { IPosition } from 'vs/editor/common/core/position'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; @@ -28,7 +26,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { EditorInput, IEditor } from 'vs/workbench/common/editor'; +import { EditorInput, IEditorPane } from 'vs/workbench/common/editor'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { GroupDirection, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -39,6 +37,10 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { getDefaultValue, IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; const emptyEditableSettingsContent = '{\n}'; @@ -73,7 +75,8 @@ export class PreferencesService extends Disposable implements IPreferencesServic @IJSONEditingService private readonly jsonEditingService: IJSONEditingService, @IModeService private readonly modeService: IModeService, @ILabelService private readonly labelService: ILabelService, - @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService + @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, + @ICommandService private readonly commandService: ICommandService, ) { super(); // The default keybindings.json updates based on keyboard layouts, so here we make sure @@ -189,15 +192,15 @@ export class PreferencesService extends Disposable implements IPreferencesServic return null; } - openRawDefaultSettings(): Promise { + openRawDefaultSettings(): Promise { return this.editorService.openEditor({ resource: this.defaultSettingsRawResource }); } - openRawUserSettings(): Promise { + openRawUserSettings(): Promise { return this.editorService.openEditor({ resource: this.userSettingsResource }); } - openSettings(jsonEditor: boolean | undefined, query: string | undefined): Promise { + openSettings(jsonEditor: boolean | undefined, query: string | undefined): Promise { jsonEditor = typeof jsonEditor === 'undefined' ? this.configurationService.getValue('workbench.settings.editor') === 'json' : jsonEditor; @@ -212,13 +215,13 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.openOrSwitchSettings(target, resource, { query: query }); } - private openSettings2(options?: ISettingsEditorOptions): Promise { + private openSettings2(options?: ISettingsEditorOptions): Promise { const input = this.settingsEditor2Input; return this.editorService.openEditor(input, options ? SettingsEditorOptions.create(options) : undefined) - .then(() => this.editorGroupService.activeGroup.activeControl!); + .then(() => this.editorGroupService.activeGroup.activeEditorPane!); } - openGlobalSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { + openGlobalSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { jsonEditor = typeof jsonEditor === 'undefined' ? this.configurationService.getValue('workbench.settings.editor') === 'json' : jsonEditor; @@ -228,7 +231,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic this.openOrSwitchSettings2(ConfigurationTarget.USER_LOCAL, undefined, options, group); } - async openRemoteSettings(): Promise { + async openRemoteSettings(): Promise { const environment = await this.remoteAgentService.getEnvironment(); if (environment) { await this.createIfNotExists(environment.settingsPath, emptyEditableSettingsContent); @@ -237,7 +240,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return undefined; } - openWorkspaceSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { + openWorkspaceSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { jsonEditor = typeof jsonEditor === 'undefined' ? this.configurationService.getValue('workbench.settings.editor') === 'json' : jsonEditor; @@ -252,7 +255,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic this.openOrSwitchSettings2(ConfigurationTarget.WORKSPACE, undefined, options, group); } - async openFolderSettings(folder: URI, jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { + async openFolderSettings(folder: URI, jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { jsonEditor = typeof jsonEditor === 'undefined' ? this.configurationService.getValue('workbench.settings.editor') === 'json' : jsonEditor; @@ -271,9 +274,9 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.doOpenSettings2(target, resource).then(() => undefined); } - const activeControl = this.editorService.activeControl; - if (activeControl && activeControl.input instanceof PreferencesEditorInput) { - return this.doSwitchSettings(target, resource, activeControl.input, activeControl.group).then(() => undefined); + const activeEditorPane = this.editorService.activeEditorPane; + if (activeEditorPane?.input instanceof PreferencesEditorInput) { + return this.doSwitchSettings(target, resource, activeEditorPane.input, activeEditorPane.group).then(() => undefined); } else { return this.doOpenSettings(target, resource).then(() => undefined); } @@ -307,29 +310,11 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.editorService.openEditor(this.instantiationService.createInstance(KeybindingsEditorInput), { pinned: true, revealIfOpened: true }).then(() => undefined); } - openDefaultKeybindingsFile(): Promise { + openDefaultKeybindingsFile(): Promise { return this.editorService.openEditor({ resource: this.defaultKeybindingsResource, label: nls.localize('defaultKeybindings', "Default Keybindings") }); } - configureSettingsForLanguage(language: string): void { - this.openGlobalSettings(true) - .then(editor => this.createPreferencesEditorModel(this.userSettingsResource) - .then((settingsModel: IPreferencesEditorModel | null) => { - const codeEditor = editor ? getCodeEditor(editor.getControl()) : null; - if (codeEditor && settingsModel) { - this.addLanguageOverrideEntry(language, settingsModel, codeEditor) - .then(position => { - if (codeEditor && position) { - codeEditor.setPosition(position); - codeEditor.revealLine(position.lineNumber); - codeEditor.focus(); - } - }); - } - })); - } - - private openOrSwitchSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Promise { + private async openOrSwitchSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Promise { const editorInput = this.getActiveSettingsEditorInput(group); if (editorInput) { const editorInputResource = editorInput.master.resource; @@ -337,14 +322,18 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.doSwitchSettings(configurationTarget, resource, editorInput, group, options); } } - return this.doOpenSettings(configurationTarget, resource, options, group); + const editor = await this.doOpenSettings(configurationTarget, resource, options, group); + if (editor && options?.editSetting) { + await this.editSetting(options?.editSetting, editor, resource); + } + return editor; } - private openOrSwitchSettings2(configurationTarget: ConfigurationTarget, folderUri?: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Promise { + private openOrSwitchSettings2(configurationTarget: ConfigurationTarget, folderUri?: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Promise { return this.doOpenSettings2(configurationTarget, folderUri, options, group); } - private doOpenSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { + private doOpenSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { const openSplitJSON = !!this.configurationService.getValue(USE_SPLIT_JSON_SETTING); if (openSplitJSON) { return this.doOpenSplitJSON(configurationTarget, resource, options, group); @@ -373,7 +362,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic }); } - private doOpenSplitJSON(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { + private doOpenSplitJSON(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { return this.getOrCreateEditableSettingsEditorInput(configurationTarget, resource) .then(editableSettingsEditorInput => { if (!options) { @@ -393,7 +382,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.instantiationService.createInstance(Settings2EditorModel, this.getDefaultSettings(ConfigurationTarget.USER_LOCAL)); } - private doOpenSettings2(target: ConfigurationTarget, folderUri: URI | undefined, options?: IEditorOptions, group?: IEditorGroup): Promise { + private doOpenSettings2(target: ConfigurationTarget, folderUri: URI | undefined, options?: IEditorOptions, group?: IEditorGroup): Promise { const input = this.settingsEditor2Input; const settingsOptions: ISettingsEditorOptions = { ...options, @@ -404,7 +393,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.editorService.openEditor(input, SettingsEditorOptions.create(settingsOptions), group); } - private async doSwitchSettings(target: ConfigurationTarget, resource: URI, input: PreferencesEditorInput, group: IEditorGroup, options?: ISettingsEditorOptions): Promise { + private async doSwitchSettings(target: ConfigurationTarget, resource: URI, input: PreferencesEditorInput, group: IEditorGroup, options?: ISettingsEditorOptions): Promise { const settingsURI = await this.getEditableSettingsURI(target, resource); if (!settingsURI) { return Promise.reject(`Invalid settings URI - ${resource.toString()}`); @@ -420,7 +409,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic options: options ? SettingsEditorOptions.create(options) : undefined }]).then(() => { this.lastOpenedSettingsInput = replaceWith; - return group.activeControl!; + return group.activeEditorPane!; }); }); }); @@ -489,7 +478,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic private getOrCreateEditableSettingsEditorInput(target: ConfigurationTarget, resource: URI): Promise { return this.createSettingsIfNotExists(target, resource) - .then(() => this.editorService.createInput({ resource })); + .then(() => this.editorService.createEditorInput({ resource })); } private createEditableSettingsEditorModel(configurationTarget: ConfigurationTarget, settingsUri: URI): Promise { @@ -593,39 +582,62 @@ export class PreferencesService extends Disposable implements IPreferencesServic ]; } - private addLanguageOverrideEntry(language: string, settingsModel: IPreferencesEditorModel, codeEditor: ICodeEditor): Promise { - const languageKey = `[${language}]`; - let setting = settingsModel.getPreference(languageKey); - const model = codeEditor.getModel(); - if (model) { - const configuration = this.configurationService.getValue<{ editor: { tabSize: number; insertSpaces: boolean } }>(); - const eol = model.getEOL(); - if (setting) { - if (setting.overrides && setting.overrides.length) { - const lastSetting = setting.overrides[setting.overrides.length - 1]; - return Promise.resolve({ lineNumber: lastSetting.valueRange.endLineNumber, column: model.getLineMaxColumn(lastSetting.valueRange.endLineNumber) }); - } - return Promise.resolve({ lineNumber: setting.valueRange.startLineNumber, column: setting.valueRange.startColumn + 1 }); - } - return this.configurationService.updateValue(languageKey, {}, ConfigurationTarget.USER) - .then(() => { - setting = settingsModel.getPreference(languageKey); - if (setting) { - let content = eol + this.spaces(2, configuration.editor) + eol + this.spaces(1, configuration.editor); - let editOperation = EditOperation.insert(new Position(setting.valueRange.endLineNumber, setting.valueRange.endColumn - 1), content); - model.pushEditOperations([], [editOperation], () => []); - let lineNumber = setting.valueRange.endLineNumber + 1; - settingsModel.dispose(); - return { lineNumber, column: model.getLineMaxColumn(lineNumber) }; - } - return null; - }); + private async editSetting(settingKey: string, editor: IEditorPane, settingsResource: URI): Promise { + const codeEditor = editor ? getCodeEditor(editor.getControl()) : null; + if (!codeEditor) { + return; + } + const settingsModel = await this.createPreferencesEditorModel(settingsResource); + if (!settingsModel) { + return; + } + const position = await this.getPositionToEdit(settingKey, settingsModel, codeEditor); + if (position) { + codeEditor.setPosition(position); + codeEditor.revealPositionNearTop(position); + codeEditor.focus(); + await this.commandService.executeCommand('editor.action.triggerSuggest'); } - return Promise.resolve(null); } - private spaces(count: number, { tabSize, insertSpaces }: { tabSize: number; insertSpaces: boolean }): string { - return insertSpaces ? strings.repeat(' ', tabSize * count) : strings.repeat('\t', count); + private async getPositionToEdit(settingKey: string, settingsModel: IPreferencesEditorModel, codeEditor: ICodeEditor): Promise { + const model = codeEditor.getModel(); + if (!model) { + return null; + } + const schema = Registry.as(Extensions.Configuration).getConfigurationProperties()[settingKey]; + if (!schema && !OVERRIDE_PROPERTY_PATTERN.test(settingKey)) { + return null; + } + + let position = null; + const type = schema ? schema.type : 'object' /* Override Identifier */; + let setting = settingsModel.getPreference(settingKey); + if (!setting) { + const defaultValue = type === 'array' ? this.configurationService.inspect(settingKey).defaultValue : getDefaultValue(type); + if (defaultValue !== undefined) { + await this.jsonEditingService.write(settingsModel.uri!, [{ key: settingKey, value: defaultValue }], false); + setting = settingsModel.getPreference(settingKey); + } + } + + if (setting) { + position = { lineNumber: setting.valueRange.startLineNumber, column: setting.valueRange.startColumn + 1 }; + if (type === 'object' || type === 'array') { + codeEditor.setPosition(position); + await CoreEditingCommands.LineBreakInsert.runEditorCommand(null, codeEditor, null); + position = { lineNumber: position.lineNumber + 1, column: model.getLineMaxColumn(position.lineNumber + 1) }; + const firstNonWhiteSpaceColumn = model.getLineFirstNonWhitespaceColumn(position.lineNumber); + if (firstNonWhiteSpaceColumn) { + // Line has some text. Insert another new line. + codeEditor.setPosition({ lineNumber: position.lineNumber, column: firstNonWhiteSpaceColumn }); + await CoreEditingCommands.LineBreakInsert.runEditorCommand(null, codeEditor, null); + position = { lineNumber: position.lineNumber, column: model.getLineMaxColumn(position.lineNumber) }; + } + } + } + + return position; } public dispose(): void { diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index 1c00289e458..093f10ecaa5 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -14,7 +14,7 @@ import { ConfigurationScope, IConfigurationExtensionInfo } from 'vs/platform/con import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { EditorOptions, IEditor } from 'vs/workbench/common/editor'; +import { EditorOptions, IEditorPane } from 'vs/workbench/common/editor'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; @@ -155,6 +155,7 @@ export interface ISettingsEditorOptions extends IEditorOptions { target?: ConfigurationTarget; folderUri?: URI; query?: string; + editSetting?: string; } /** @@ -165,6 +166,7 @@ export class SettingsEditorOptions extends EditorOptions implements ISettingsEdi target?: ConfigurationTarget; folderUri?: URI; query?: string; + editSetting?: string; static create(settings: ISettingsEditorOptions): SettingsEditorOptions { const options = new SettingsEditorOptions(); @@ -173,6 +175,7 @@ export class SettingsEditorOptions extends EditorOptions implements ISettingsEdi options.target = settings.target; options.folderUri = settings.folderUri; options.query = settings.query; + options.editSetting = settings.editSetting; return options; } @@ -194,17 +197,15 @@ export interface IPreferencesService { createPreferencesEditorModel(uri: URI): Promise | null>; createSettings2EditorModel(): Settings2EditorModel; // TODO - openRawDefaultSettings(): Promise; - openSettings(jsonEditor: boolean | undefined, query: string | undefined): Promise; - openGlobalSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; - openRemoteSettings(): Promise; - openWorkspaceSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; - openFolderSettings(folder: URI, jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; + openRawDefaultSettings(): Promise; + openSettings(jsonEditor: boolean | undefined, query: string | undefined): Promise; + openGlobalSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; + openRemoteSettings(): Promise; + openWorkspaceSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; + openFolderSettings(folder: URI, jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; switchSettings(target: ConfigurationTarget, resource: URI, jsonEditor?: boolean): Promise; openGlobalKeybindingSettings(textual: boolean): Promise; - openDefaultKeybindingsFile(): Promise; - - configureSettingsForLanguage(language: string | null): void; + openDefaultKeybindingsFile(): Promise; } export function getSettingsTargetName(target: ConfigurationTarget, resource: URI, workspaceContextService: IWorkspaceContextService): string { diff --git a/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts index 436e469c343..6432e6993f5 100644 --- a/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts +++ b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts @@ -19,6 +19,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IFileService } from 'vs/platform/files/common/files'; import { ILabelService } from 'vs/platform/label/common/label'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { Schemas } from 'vs/base/common/network'; export class PreferencesEditorInput extends SideBySideEditorInput { static readonly ID: string = 'workbench.editorinputs.preferencesEditorInput'; @@ -105,7 +106,7 @@ export class SettingsEditor2Input extends EditorInput { private readonly _settingsModel: Settings2EditorModel; readonly resource: URI = URI.from({ - scheme: 'vscode-settings', + scheme: Schemas.vscodeSettings, path: `settingseditor` }); diff --git a/src/vs/workbench/services/progress/browser/progressIndicator.ts b/src/vs/workbench/services/progress/browser/progressIndicator.ts index dc0b538df55..ebbfd5bc0e4 100644 --- a/src/vs/workbench/services/progress/browser/progressIndicator.ts +++ b/src/vs/workbench/services/progress/browser/progressIndicator.ts @@ -10,6 +10,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IProgressRunner, IProgressIndicator, emptyProgressRunner } from 'vs/platform/progress/common/progress'; import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; +import { IViewsService } from 'vs/workbench/common/views'; export class ProgressBarIndicator extends Disposable implements IProgressIndicator { @@ -153,6 +154,7 @@ export abstract class CompositeScope extends Disposable { constructor( private viewletService: IViewletService, private panelService: IPanelService, + private viewsService: IViewsService, private scopeId: string ) { super(); @@ -161,6 +163,8 @@ export abstract class CompositeScope extends Disposable { } registerListeners(): void { + this._register(this.viewsService.onDidChangeViewVisibility(e => e.visible ? this.onScopeOpened(e.id) : this.onScopeClosed(e.id))); + this._register(this.viewletService.onDidViewletOpen(viewlet => this.onScopeOpened(viewlet.getId()))); this._register(this.panelService.onDidPanelOpen(({ panel }) => this.onScopeOpened(panel.getId()))); @@ -195,9 +199,10 @@ export class CompositeProgressIndicator extends CompositeScope implements IProgr scopeId: string, isActive: boolean, @IViewletService viewletService: IViewletService, - @IPanelService panelService: IPanelService + @IPanelService panelService: IPanelService, + @IViewsService viewsService: IViewsService ) { - super(viewletService, panelService, scopeId); + super(viewletService, panelService, viewsService, scopeId); this.progressbar = progressbar; this.isActive = isActive || isUndefinedOrNull(scopeId); // If service is unscoped, enable by default @@ -205,6 +210,8 @@ export class CompositeProgressIndicator extends CompositeScope implements IProgr onScopeDeactivated(): void { this.isActive = false; + + this.progressbar.stop().hide(); } onScopeActivated(): void { diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index 3cdea6762a5..bf6e3489d6d 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -6,10 +6,10 @@ import 'vs/css!./media/progressService'; import { localize } from 'vs/nls'; -import { IDisposable, dispose, DisposableStore, MutableDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, DisposableStore, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IProgressService, IProgressOptions, IProgressStep, ProgressLocation, IProgress, Progress, IProgressCompositeOptions, IProgressNotificationOptions, IProgressRunner, IProgressIndicator, IProgressWindowOptions } from 'vs/platform/progress/common/progress'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { StatusbarAlignment, IStatusbarService } from 'vs/workbench/services/statusbar/common/statusbar'; +import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; import { timeout } from 'vs/base/common/async'; import { ProgressBadge, IActivityService } from 'vs/workbench/services/activity/common/activity'; import { INotificationService, Severity, INotificationHandle } from 'vs/platform/notification/common/notification'; @@ -25,17 +25,17 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { EventHelper } from 'vs/base/browser/dom'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { parseLinkedText } from 'vs/base/common/linkedText'; +import { IViewsService, IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; export class ProgressService extends Disposable implements IProgressService { _serviceBrand: undefined; - private readonly stack: [IProgressOptions, Progress][] = []; - private readonly globalStatusEntry = this._register(new MutableDisposable()); - constructor( @IActivityService private readonly activityService: IActivityService, @IViewletService private readonly viewletService: IViewletService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, + @IViewsService private readonly viewsService: IViewsService, @IPanelService private readonly panelService: IPanelService, @INotificationService private readonly notificationService: INotificationService, @IStatusbarService private readonly statusbarService: IStatusbarService, @@ -57,6 +57,10 @@ export class ProgressService extends Disposable implements IProgressService { return this.withPanelProgress(location, task, { ...options, location }); } + if (this.viewsService.getProgressIndicator(location)) { + return this.withViewProgress(location, task, { ...options, location }); + } + throw new Error(`Bad progress location: ${location}`); } @@ -64,7 +68,14 @@ export class ProgressService extends Disposable implements IProgressService { case ProgressLocation.Notification: return this.withNotificationProgress({ ...options, location }, task, onDidCancel); case ProgressLocation.Window: - return this.withWindowProgress({ ...options, location }, task); + if ((options as IProgressWindowOptions).command) { + // Window progress with command get's shown in the status bar + return this.withWindowProgress({ ...options, location }, task); + } + // Window progress without command can be shown as silent notification + // which will first appear in the status bar and can then be brought to + // the front when clicking. + return this.withNotificationProgress({ ...options, silent: true, location: ProgressLocation.Notification }, task, onDidCancel); case ProgressLocation.Explorer: return this.withViewletProgress('workbench.view.explorer', task, { ...options, location }); case ProgressLocation.Scm: @@ -78,6 +89,9 @@ export class ProgressService extends Disposable implements IProgressService { } } + private readonly windowProgressStack: [IProgressOptions, Progress][] = []; + private windowProgressStatusEntry: IStatusbarEntryAccessor | undefined = undefined; + private withWindowProgress(options: IProgressWindowOptions, callback: (progress: IProgress<{ message?: string }>) => Promise): Promise { const task: [IProgressWindowOptions, Progress] = [options, new Progress(() => this.updateWindowProgress())]; @@ -85,7 +99,7 @@ export class ProgressService extends Disposable implements IProgressService { let delayHandle: any = setTimeout(() => { delayHandle = undefined; - this.stack.unshift(task); + this.windowProgressStack.unshift(task); this.updateWindowProgress(); // show progress for at least 150ms @@ -93,8 +107,8 @@ export class ProgressService extends Disposable implements IProgressService { timeout(150), promise ]).finally(() => { - const idx = this.stack.indexOf(task); - this.stack.splice(idx, 1); + const idx = this.windowProgressStack.indexOf(task); + this.windowProgressStack.splice(idx, 1); this.updateWindowProgress(); }); }, 150); @@ -104,10 +118,10 @@ export class ProgressService extends Disposable implements IProgressService { } private updateWindowProgress(idx: number = 0) { - this.globalStatusEntry.clear(); - if (idx < this.stack.length) { - const [options, progress] = this.stack[idx]; + // We still have progress to show + if (idx < this.windowProgressStack.length) { + const [options, progress] = this.windowProgressStack[idx]; let progressTitle = options.title; let progressMessage = progress.value && progress.value.message; @@ -136,11 +150,23 @@ export class ProgressService extends Disposable implements IProgressService { return; } - this.globalStatusEntry.value = this.statusbarService.addEntry({ + const statusEntryProperties: IStatusbarEntry = { text: `$(sync~spin) ${text}`, tooltip: title, command: progressCommand - }, 'status.progress', localize('status.progress', "Progress Message"), StatusbarAlignment.LEFT); + }; + + if (this.windowProgressStatusEntry) { + this.windowProgressStatusEntry.update(statusEntryProperties); + } else { + this.windowProgressStatusEntry = this.statusbarService.addEntry(statusEntryProperties, 'status.progress', localize('status.progress', "Progress Message"), StatusbarAlignment.LEFT); + } + } + + // Progress is done so we remove the status entry + else { + this.windowProgressStatusEntry?.dispose(); + this.windowProgressStatusEntry = undefined; } } @@ -232,7 +258,7 @@ export class ProgressService extends Disposable implements IProgressService { return toDisposable(() => promiseResolve()); }; - const createNotification = (message: string, increment?: number): INotificationHandle => { + const createNotification = (message: string, silent: boolean, increment?: number): INotificationHandle => { const notificationDisposables = new DisposableStore(); const primaryActions = options.primaryActions ? Array.from(options.primaryActions) : []; @@ -275,7 +301,8 @@ export class ProgressService extends Disposable implements IProgressService { message, source: options.source, actions: { primary: primaryActions, secondary: secondaryActions }, - progress: typeof increment === 'number' && increment >= 0 ? { total: 100, worked: increment } : { infinite: true } + progress: typeof increment === 'number' && increment >= 0 ? { total: 100, worked: increment } : { infinite: true }, + silent }); // Switch to window based progress once the notification @@ -283,8 +310,7 @@ export class ProgressService extends Disposable implements IProgressService { // Remove that window based progress once the notification // shows again. let windowProgressDisposable: IDisposable | undefined = undefined; - notificationDisposables.add(notification.onDidChangeVisibility(visible => { - + const onVisibilityChange = (visible: boolean) => { // Clear any previous running window progress dispose(windowProgressDisposable); @@ -292,7 +318,11 @@ export class ProgressService extends Disposable implements IProgressService { if (!visible && !progressStateModel.done) { windowProgressDisposable = createWindowProgress(); } - })); + }; + notificationDisposables.add(notification.onDidChangeVisibility(onVisibilityChange)); + if (silent) { + onVisibilityChange(false); + } // Clear upon dispose Event.once(notification.onDidClose)(() => notificationDisposables.dispose()); @@ -327,10 +357,10 @@ export class ProgressService extends Disposable implements IProgressService { // create notification now or after a delay if (typeof options.delay === 'number' && options.delay > 0) { if (typeof notificationTimeout !== 'number') { - notificationTimeout = setTimeout(() => notificationHandle = createNotification(titleAndMessage!, step?.increment), options.delay); + notificationTimeout = setTimeout(() => notificationHandle = createNotification(titleAndMessage!, !!options.silent, step?.increment), options.delay); } } else { - notificationHandle = createNotification(titleAndMessage, step?.increment); + notificationHandle = createNotification(titleAndMessage, !!options.silent, step?.increment); } } @@ -364,18 +394,38 @@ export class ProgressService extends Disposable implements IProgressService { // show in viewlet const promise = this.withCompositeProgress(this.viewletService.getProgressIndicator(viewletId), task, options); - // show activity bar + // show on activity bar + this.showOnActivityBar(viewletId, options, promise); + + return promise; + } + + private withViewProgress

, R = unknown>(viewId: string, task: (progress: IProgress) => P, options: IProgressCompositeOptions): P { + + // show in viewlet + const promise = this.withCompositeProgress(this.viewsService.getProgressIndicator(viewId), task, options); + + const location = this.viewDescriptorService.getViewLocation(viewId); + if (location !== ViewContainerLocation.Sidebar) { + return promise; + } + + const viewletId = this.viewDescriptorService.getViewContainer(viewId)?.id; + if (viewletId === undefined) { + return promise; + } + + // show on activity bar + this.showOnActivityBar(viewletId, options, promise); + + return promise; + } + + private showOnActivityBar

, R = unknown>(viewletId: string, options: IProgressCompositeOptions, promise: P) { let activityProgress: IDisposable; let delayHandle: any = setTimeout(() => { delayHandle = undefined; - - const handle = this.activityService.showActivity( - viewletId, - new ProgressBadge(() => ''), - 'progress-badge', - 100 - ); - + const handle = this.activityService.showActivity(viewletId, new ProgressBadge(() => ''), 'progress-badge', 100); const startTimeVisible = Date.now(); const minTimeVisible = 300; activityProgress = { @@ -391,13 +441,10 @@ export class ProgressService extends Disposable implements IProgressService { } }; }, options.delay || 300); - promise.finally(() => { clearTimeout(delayHandle); dispose(activityProgress); }); - - return promise; } private withPanelProgress

, R = unknown>(panelid: string, task: (progress: IProgress) => P, options: IProgressCompositeOptions): P { diff --git a/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts b/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts index f5f47990d29..6893e8114cd 100644 --- a/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts +++ b/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts @@ -10,9 +10,9 @@ import { CompositeScope, CompositeProgressIndicator } from 'vs/workbench/service import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IViewlet } from 'vs/workbench/common/viewlet'; -import { TestViewletService, TestPanelService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestViewletService, TestPanelService, TestViewsService } from 'vs/workbench/test/browser/workbenchTestServices'; import { Event } from 'vs/base/common/event'; -import { IView, IViewPaneContainer } from 'vs/workbench/common/views'; +import { IView, IViewPaneContainer, IViewsService } from 'vs/workbench/common/views'; class TestViewlet implements IViewlet { @@ -38,8 +38,8 @@ class TestViewlet implements IViewlet { class TestCompositeScope extends CompositeScope { isActive: boolean = false; - constructor(viewletService: IViewletService, panelService: IPanelService, scopeId: string) { - super(viewletService, panelService, scopeId); + constructor(viewletService: IViewletService, panelService: IPanelService, viewsService: IViewsService, scopeId: string) { + super(viewletService, panelService, viewsService, scopeId); } onScopeActivated() { this.isActive = true; } @@ -106,7 +106,8 @@ suite('Progress Indicator', () => { test('CompositeScope', () => { let viewletService = new TestViewletService(); let panelService = new TestPanelService(); - let service = new TestCompositeScope(viewletService, panelService, 'test.scopeId'); + let viewsService = new TestViewsService(); + let service = new TestCompositeScope(viewletService, panelService, viewsService, 'test.scopeId'); const testViewlet = new TestViewlet('test.scopeId'); assert(!service.isActive); @@ -116,13 +117,19 @@ suite('Progress Indicator', () => { viewletService.onDidViewletCloseEmitter.fire(testViewlet); assert(!service.isActive); + viewsService.onDidChangeViewVisibilityEmitter.fire({ id: 'test.scopeId', visible: true }); + assert(service.isActive); + + viewsService.onDidChangeViewVisibilityEmitter.fire({ id: 'test.scopeId', visible: false }); + assert(!service.isActive); }); test('CompositeProgressIndicator', async () => { let testProgressBar = new TestProgressBar(); let viewletService = new TestViewletService(); let panelService = new TestPanelService(); - let service = new CompositeProgressIndicator((testProgressBar), 'test.scopeId', true, viewletService, panelService); + let viewsService = new TestViewsService(); + let service = new CompositeProgressIndicator((testProgressBar), 'test.scopeId', true, viewletService, panelService, viewsService); // Active: Show (Infinite) let fn = service.show(true); @@ -169,5 +176,19 @@ suite('Progress Indicator', () => { assert.strictEqual(true, testProgressBar.fDone); viewletService.onDidViewletOpenEmitter.fire(testViewlet); assert.strictEqual(true, testProgressBar.fDone); + + // Visible view: Show (Infinite) + viewsService.onDidChangeViewVisibilityEmitter.fire({ id: 'test.scopeId', visible: true }); + fn = service.show(true); + assert.strictEqual(true, testProgressBar.fInfinite); + fn.done(); + assert.strictEqual(true, testProgressBar.fDone); + + // Hidden view: Show (Infinite) + viewsService.onDidChangeViewVisibilityEmitter.fire({ id: 'test.scopeId', visible: false }); + service.show(true); + assert.strictEqual(false, !!testProgressBar.fInfinite); + viewsService.onDidChangeViewVisibilityEmitter.fire({ id: 'test.scopeId', visible: true }); + assert.strictEqual(true, testProgressBar.fInfinite); }); }); diff --git a/src/vs/workbench/services/quickinput/browser/quickInputService.ts b/src/vs/workbench/services/quickinput/browser/quickInputService.ts new file mode 100644 index 00000000000..79a0234578c --- /dev/null +++ b/src/vs/workbench/services/quickinput/browser/quickInputService.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { QuickInputController } from 'vs/base/parts/quickinput/browser/quickInput'; +import { QuickInputService as BaseQuickInputService } from 'vs/platform/quickinput/browser/quickInput'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { InQuickPickContextKey } from 'vs/workbench/browser/quickaccess'; + +export class QuickInputService extends BaseQuickInputService { + + private readonly inQuickInputContext = InQuickPickContextKey.bindTo(this.contextKeyService); + + constructor( + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IInstantiationService instantiationService: IInstantiationService, + @IKeybindingService private readonly keybindingService: IKeybindingService, + @IContextKeyService contextKeyService: IContextKeyService, + @IThemeService themeService: IThemeService, + @IAccessibilityService accessibilityService: IAccessibilityService, + @ILayoutService protected readonly layoutService: ILayoutService, + ) { + super(instantiationService, contextKeyService, themeService, accessibilityService, layoutService); + + this.registerListeners(); + } + + private registerListeners(): void { + this._register(this.onShow(() => this.inQuickInputContext.set(true))); + this._register(this.onHide(() => this.inQuickInputContext.set(false))); + } + + protected createController(): QuickInputController { + return super.createController(this.layoutService, { + ignoreFocusOut: () => this.environmentService.args['sticky-quickinput'] || !this.configurationService.getValue('workbench.quickOpen.closeOnFocusLost'), + backKeybindingLabel: () => this.keybindingService.lookupKeybinding('workbench.action.quickInputBack')?.getLabel() || undefined, + }); + } +} + +registerSingleton(IQuickInputService, QuickInputService, true); diff --git a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts index a97aaafe97d..bf8e93ea187 100644 --- a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts @@ -164,7 +164,7 @@ class RemoteConnectionFailureNotificationContribution implements IWorkbenchContr // Let's cover the case where connecting to fetch the remote extension info fails remoteAgentService.getEnvironment(true) .then(undefined, err => { - if (!RemoteAuthorityResolverError.isHandledNotAvailable(err)) { + if (!RemoteAuthorityResolverError.isHandled(err)) { notificationService.error(nls.localize('connectionError', "Failed to connect to the remote extension host server (Error: {0})", err ? err.message : '')); } }); diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index 67fc3030120..893c7ad1cc6 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -29,6 +29,7 @@ export interface ITunnelItem { remoteHost: string; remotePort: number; localAddress?: string; + localPort?: number; name?: string; closeable?: boolean; description?: string; @@ -114,11 +115,9 @@ export class TunnelModel extends Disposable { this._onClosePort.fire(address); } })); - - this.restoreForwarded(); } - private async restoreForwarded() { + async restoreForwarded() { if (this.configurationService.getValue('remote.restoreForwardedPorts')) { const tunnelsString = this.storageService.get(TUNNELS_TO_RESTORE, StorageScope.WORKSPACE); if (tunnelsString) { @@ -181,7 +180,7 @@ export class TunnelModel extends Disposable { this.detected.set(MakeAddress(tunnel.remoteAddress.host, tunnel.remoteAddress.port), { remoteHost: tunnel.remoteAddress.host, remotePort: tunnel.remoteAddress.port, - localAddress: tunnel.localAddress, + localAddress: typeof tunnel.localAddress === 'string' ? tunnel.localAddress : MakeAddress(tunnel.localAddress.host, tunnel.localAddress.port), closeable: false }); }); @@ -238,6 +237,7 @@ export interface IRemoteExplorerService { registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void; setCandidateFilter(filter: ((candidates: { host: string, port: number, detail: string }[]) => Promise<{ host: string, port: number, detail: string }[]>) | undefined): IDisposable; refresh(): Promise; + restore(): Promise; } class RemoteExplorerService implements IRemoteExplorerService { @@ -328,6 +328,10 @@ class RemoteExplorerService implements IRemoteExplorerService { refresh(): Promise { return this.tunnelModel.refresh(); } + + restore(): Promise { + return this.tunnelModel.restoreForwarded(); + } } registerSingleton(IRemoteExplorerService, RemoteExplorerService, true); diff --git a/src/vs/workbench/services/remote/common/tunnelService.ts b/src/vs/workbench/services/remote/common/tunnelService.ts index 967299787d8..a7f8a98cc41 100644 --- a/src/vs/workbench/services/remote/common/tunnelService.ts +++ b/src/vs/workbench/services/remote/common/tunnelService.ts @@ -101,7 +101,7 @@ export abstract class AbstractTunnelService implements ITunnelService { private async tryDisposeTunnel(remoteHost: string, remotePort: number, tunnel: { refcount: number, readonly value: Promise }): Promise { if (tunnel.refcount <= 0) { const disposePromise: Promise = tunnel.value.then(tunnel => { - tunnel.dispose(); + tunnel.dispose(true); this._onTunnelClosed.fire({ host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }); }); if (this._tunnels.has(remoteHost)) { diff --git a/src/vs/workbench/services/remote/node/tunnelService.ts b/src/vs/workbench/services/remote/node/tunnelService.ts index 090457d12db..e43fb3762a6 100644 --- a/src/vs/workbench/services/remote/node/tunnelService.ts +++ b/src/vs/workbench/services/remote/node/tunnelService.ts @@ -145,7 +145,7 @@ export class TunnelService extends AbstractTunnelService { } if (this._tunnelProvider) { - const tunnel = this._tunnelProvider.forwardPort({ remoteAddress: { host: remoteHost, port: remotePort } }); + const tunnel = this._tunnelProvider.forwardPort({ remoteAddress: { host: remoteHost, port: remotePort }, localAddressPort: localPort }); if (tunnel) { this.addTunnelToMap(remoteHost, remotePort, tunnel); } diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index e646364a885..582fb63b3b5 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -9,18 +9,22 @@ import * as glob from 'vs/base/common/glob'; import { IDisposable } from 'vs/base/common/lifecycle'; import * as objects from 'vs/base/common/objects'; import * as extpath from 'vs/base/common/extpath'; -import { getNLines } from 'vs/base/common/strings'; +import { fuzzyContains, getNLines } from 'vs/base/common/strings'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { IFilesConfiguration } from 'vs/platform/files/common/files'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IFilesConfiguration, FILES_EXCLUDE_CONFIG } from 'vs/platform/files/common/files'; +import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { Event } from 'vs/base/common/event'; import { relative } from 'vs/base/common/path'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ResourceGlobMatcher } from 'vs/workbench/common/resources'; export const VIEWLET_ID = 'workbench.view.search'; export const PANEL_ID = 'workbench.panel.search'; export const VIEW_ID = 'workbench.view.search'; +export const SEARCH_EXCLUDE_CONFIG = 'search.exclude'; + export const ISearchService = createDecorator('searchService'); /** @@ -50,6 +54,7 @@ export interface ISearchResultProvider { export interface IFolderQuery { folder: U; + folderName?: string; excludePattern?: glob.IExpression; includePattern?: glob.IExpression; fileEncoding?: string; @@ -371,6 +376,14 @@ export function getExcludes(configuration: ISearchConfiguration, includeSearchEx return allExcludes; } +export function createResourceExcludeMatcher(instantiationService: IInstantiationService, configurationService: IConfigurationService): ResourceGlobMatcher { + return instantiationService.createInstance( + ResourceGlobMatcher, + root => getExcludes(root ? configurationService.getValue({ resource: root }) : configurationService.getValue()) || Object.create(null), + event => event.affectsConfiguration(FILES_EXCLUDE_CONFIG) || event.affectsConfiguration(SEARCH_EXCLUDE_CONFIG) + ); +} + export function pathIncludedInQuery(queryProps: ICommonQueryProps, fsPath: string): boolean { if (queryProps.excludePattern && glob.match(queryProps.excludePattern, fsPath)) { return false; @@ -437,9 +450,19 @@ export interface IRawSearchService { export interface IRawFileMatch { base?: string; + /** + * The path of the file relative to the containing `base` folder. + * This path is exactly as it appears on the filesystem. + */ relativePath: string; - basename: string; - size?: number; + /** + * This path is transformed for search purposes. For example, this could be + * the `relativePath` with the workspace folder name prepended. This way the + * search algorithm would also match against the name of the containing folder. + * + * If not given, the search algorithm should use `relativePath`. + */ + searchPath?: string; } export interface ISearchEngine { @@ -486,6 +509,11 @@ export function isSerializedFileMatch(arg: ISerializedSearchProgressItem): arg i return !!(arg).path; } +export function isFilePatternMatch(candidate: IRawFileMatch, normalizedFilePatternLowercase: string): boolean { + const pathToMatch = candidate.searchPath ? candidate.searchPath : candidate.relativePath; + return fuzzyContains(pathToMatch, normalizedFilePatternLowercase); +} + export interface ISerializedFileMatch { path: string; results?: ITextSearchResult[]; diff --git a/src/vs/workbench/services/search/common/searchExtTypes.ts b/src/vs/workbench/services/search/common/searchExtTypes.ts index 6c818f7fda7..2d31c88eab6 100644 --- a/src/vs/workbench/services/search/common/searchExtTypes.ts +++ b/src/vs/workbench/services/search/common/searchExtTypes.ts @@ -316,12 +316,12 @@ export interface TextSearchContext { export type TextSearchResult = TextSearchMatch | TextSearchContext; /** - * A FileSearchProvider provides search results for files in the given folder that match a query string. It can be invoked by quickopen or other extensions. + * A FileSearchProvider provides search results for files in the given folder that match a query string. It can be invoked by quickaccess or other extensions. * * A FileSearchProvider is the more powerful of two ways to implement file search in VS Code. Use a FileSearchProvider if you wish to search within a folder for * all files that match the user's query. * - * The FileSearchProvider will be invoked on every keypress in quickopen. When `workspace.findFiles` is called, it will be invoked with an empty query string, + * The FileSearchProvider will be invoked on every keypress in quickaccess. When `workspace.findFiles` is called, it will be invoked with an empty query string, * and in that case, every file in the folder should be returned. */ export interface FileSearchProvider { diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index 990d4637b07..2486dc7fd90 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -20,6 +20,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { deserializeSearchError, FileMatch, ICachedSearchStats, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, IProgressMessage, ISearchComplete, ISearchEngineStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, ITextQuery, pathIncludedInQuery, QueryType, SearchError, SearchErrorCode, SearchProviderType, isFileMatch, isProgressMessage } from 'vs/workbench/services/search/common/search'; import { addContextToEditorMatches, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { DeferredPromise } from 'vs/base/test/common/utils'; export class SearchService extends Disposable implements ISearchService { @@ -29,6 +30,9 @@ export class SearchService extends Disposable implements ISearchService { private readonly fileSearchProviders = new Map(); private readonly textSearchProviders = new Map(); + private deferredFileSearchesByScheme = new Map>(); + private deferredTextSearchesByScheme = new Map>(); + constructor( private readonly modelService: IModelService, private readonly editorService: IEditorService, @@ -42,16 +46,24 @@ export class SearchService extends Disposable implements ISearchService { registerSearchResultProvider(scheme: string, type: SearchProviderType, provider: ISearchResultProvider): IDisposable { let list: Map; + let deferredMap: Map>; if (type === SearchProviderType.file) { list = this.fileSearchProviders; + deferredMap = this.deferredFileSearchesByScheme; } else if (type === SearchProviderType.text) { list = this.textSearchProviders; + deferredMap = this.deferredTextSearchesByScheme; } else { throw new Error('Unknown SearchProviderType'); } list.set(scheme, provider); + if (deferredMap.has(scheme)) { + deferredMap.get(scheme)!.complete(provider); + deferredMap.delete(scheme); + } + return toDisposable(() => { list.delete(scheme); }); @@ -62,13 +74,13 @@ export class SearchService extends Disposable implements ISearchService { const localResults = this.getLocalResults(query); if (onProgress) { - arrays.coalesce(localResults.values()).forEach(onProgress); + arrays.coalesce(localResults.results.values()).forEach(onProgress); } const onProviderProgress = (progress: ISearchProgressItem) => { if (isFileMatch(progress)) { // Match - if (!localResults.has(progress.resource) && onProgress) { // don't override local results + if (!localResults.results.has(progress.resource) && onProgress) { // don't override local results onProgress(progress); } } else if (onProgress) { @@ -84,7 +96,10 @@ export class SearchService extends Disposable implements ISearchService { const otherResults = await this.doSearch(query, token, onProviderProgress); return { ...otherResults, - results: [...otherResults.results, ...arrays.coalesce(localResults.values())] + ...{ + limitHit: otherResults.limitHit || localResults.limitHit + }, + results: [...otherResults.results, ...arrays.coalesce(localResults.results.values())] }; } @@ -161,24 +176,41 @@ export class SearchService extends Disposable implements ISearchService { return schemes; } - private searchWithProviders(query: ISearchQuery, onProviderProgress: (progress: ISearchProgressItem) => void, token?: CancellationToken) { + private async waitForProvider(queryType: QueryType, scheme: string): Promise { + let deferredMap: Map> = queryType === QueryType.File ? + this.deferredFileSearchesByScheme : + this.deferredTextSearchesByScheme; + + if (deferredMap.has(scheme)) { + return deferredMap.get(scheme)!.p; + } else { + const deferred = new DeferredPromise(); + deferredMap.set(scheme, deferred); + return deferred.p; + } + } + + private async searchWithProviders(query: ISearchQuery, onProviderProgress: (progress: ISearchProgressItem) => void, token?: CancellationToken) { const e2eSW = StopWatch.create(false); const diskSearchQueries: IFolderQuery[] = []; const searchPs: Promise[] = []; const fqs = this.groupFolderQueriesByScheme(query); - keys(fqs).forEach(scheme => { + await Promise.all(keys(fqs).map(async scheme => { const schemeFQs = fqs.get(scheme)!; - const provider = query.type === QueryType.File ? + let provider = query.type === QueryType.File ? this.fileSearchProviders.get(scheme) : this.textSearchProviders.get(scheme); if (!provider && scheme === 'file') { diskSearchQueries.push(...schemeFQs); - } else if (!provider) { - console.warn('No search provider registered for scheme: ' + scheme); } else { + if (!provider) { + console.warn(`No search provider registered for scheme: ${scheme}, waiting`); + provider = await this.waitForProvider(query.type, scheme); + } + const oneSchemeQuery: ISearchQuery = { ...query, ...{ @@ -190,7 +222,7 @@ export class SearchService extends Disposable implements ISearchService { provider.fileSearch(oneSchemeQuery, token) : provider.textSearch(oneSchemeQuery, onProviderProgress, token)); } - }); + })); const diskSearchExtraFileResources = query.extraFileResources && query.extraFileResources.filter(res => res.scheme === Schemas.file); @@ -378,8 +410,9 @@ export class SearchService extends Disposable implements ISearchService { } } - private getLocalResults(query: ITextQuery): ResourceMap { + private getLocalResults(query: ITextQuery): { results: ResourceMap; limitHit: boolean } { const localResults = new ResourceMap(); + let limitHit = false; if (query.type === QueryType.Text) { const models = this.modelService.getModels(); @@ -389,8 +422,12 @@ export class SearchService extends Disposable implements ISearchService { return; } + if (limitHit) { + return; + } + // Skip files that are not opened as text file - if (!this.editorService.isOpen(this.editorService.createInput({ resource, forceFile: resource.scheme !== Schemas.untitled, forceUntitled: resource.scheme === Schemas.untitled }))) { + if (!this.editorService.isOpen({ resource })) { return; } @@ -415,8 +452,14 @@ export class SearchService extends Disposable implements ISearchService { } // Use editor API to find matches - const matches = model.findMatches(query.contentPattern.pattern, false, !!query.contentPattern.isRegExp, !!query.contentPattern.isCaseSensitive, query.contentPattern.isWordMatch ? query.contentPattern.wordSeparators! : null, false, query.maxResults); + const askMax = typeof query.maxResults === 'number' ? query.maxResults + 1 : undefined; + let matches = model.findMatches(query.contentPattern.pattern, false, !!query.contentPattern.isRegExp, !!query.contentPattern.isCaseSensitive, query.contentPattern.isWordMatch ? query.contentPattern.wordSeparators! : null, false, askMax); if (matches.length) { + if (askMax && matches.length >= askMax) { + limitHit = true; + matches = matches.slice(0, askMax - 1); + } + const fileMatch = new FileMatch(resource); localResults.set(resource, fileMatch); @@ -428,7 +471,10 @@ export class SearchService extends Disposable implements ISearchService { }); } - return localResults; + return { + results: localResults, + limitHit + }; } private matches(resource: uri, query: ITextQuery): boolean { diff --git a/src/vs/workbench/services/search/node/fileSearch.ts b/src/vs/workbench/services/search/node/fileSearch.ts index e4d0dbbc75b..97fe8231b86 100644 --- a/src/vs/workbench/services/search/node/fileSearch.ts +++ b/src/vs/workbench/services/search/node/fileSearch.ts @@ -20,9 +20,9 @@ import * as strings from 'vs/base/common/strings'; import * as types from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { readdir } from 'vs/base/node/pfs'; -import { IFileQuery, IFolderQuery, IProgressMessage, ISearchEngineStats, IRawFileMatch, ISearchEngine, ISearchEngineSuccess } from 'vs/workbench/services/search/common/search'; +import { IFileQuery, IFolderQuery, IProgressMessage, ISearchEngineStats, IRawFileMatch, ISearchEngine, ISearchEngineSuccess, isFilePatternMatch } from 'vs/workbench/services/search/common/search'; import { spawnRipgrepCmd } from './ripgrepFileSearch'; -import { prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; +import { prepareQuery } from 'vs/base/common/fuzzyScorer'; interface IDirectoryEntry { base: string; @@ -77,7 +77,7 @@ export class FileWalker { this.errors = []; if (this.filePattern) { - this.normalizedFilePatternLowercase = prepareQuery(this.filePattern).lowercase; + this.normalizedFilePatternLowercase = prepareQuery(this.filePattern).normalizedLowercase; } this.globalExcludePattern = config.excludePattern && glob.parse(config.excludePattern); @@ -122,7 +122,7 @@ export class FileWalker { } // File: Check for match on file pattern and include pattern - this.matchFile(onResult, { relativePath: extraFilePath.fsPath /* no workspace relative path */, basename }); + this.matchFile(onResult, { relativePath: extraFilePath.fsPath /* no workspace relative path */ }); }); this.cmdSW = StopWatch.create(false); @@ -246,8 +246,7 @@ export class FileWalker { if (noSiblingsClauses) { for (const relativePath of relativeFiles) { - const basename = path.basename(relativePath); - this.matchFile(onResult, { base: rootFolder, relativePath, basename }); + this.matchFile(onResult, { base: rootFolder, relativePath, searchPath: this.getSearchPath(folderQuery, relativePath) }); if (this.isLimitHit) { killCmd(); break; @@ -393,8 +392,7 @@ export class FileWalker { private addDirectoryEntries({ pathToEntries }: IDirectoryTree, base: string, relativeFiles: string[], onResult: (result: IRawFileMatch) => void) { // Support relative paths to files from a root resource (ignores excludes) if (relativeFiles.indexOf(this.filePattern) !== -1) { - const basename = path.basename(this.filePattern); - this.matchFile(onResult, { base: base, relativePath: this.filePattern, basename }); + this.matchFile(onResult, { base: base, relativePath: this.filePattern }); } function add(relativePath: string) { @@ -540,7 +538,11 @@ export class FileWalker { return clb(null, undefined); // ignore file if max file size is hit } - this.matchFile(onResult, { base: rootFolder.fsPath, relativePath: currentRelativePath, basename: file, size: stat.size }); + this.matchFile(onResult, { + base: rootFolder.fsPath, + relativePath: currentRelativePath, + searchPath: this.getSearchPath(folderQuery, currentRelativePath), + }); } // Unwind @@ -554,7 +556,7 @@ export class FileWalker { } private matchFile(onResult: (result: IRawFileMatch) => void, candidate: IRawFileMatch): void { - if (this.isFilePatternMatch(candidate.relativePath) && (!this.includePattern || this.includePattern(candidate.relativePath, candidate.basename))) { + if (this.isFileMatch(candidate) && (!this.includePattern || this.includePattern(candidate.relativePath, path.basename(candidate.relativePath)))) { this.resultCount++; if (this.exists || (this.maxResults && this.resultCount > this.maxResults)) { @@ -567,8 +569,7 @@ export class FileWalker { } } - private isFilePatternMatch(path: string): boolean { - + private isFileMatch(candidate: IRawFileMatch): boolean { // Check for search pattern if (this.filePattern) { if (this.filePattern === '*') { @@ -576,7 +577,7 @@ export class FileWalker { } if (this.normalizedFilePatternLowercase) { - return strings.fuzzyContains(path, this.normalizedFilePatternLowercase); + return isFilePatternMatch(candidate, this.normalizedFilePatternLowercase); } } @@ -605,6 +606,19 @@ export class FileWalker { return clb(null, path); } + + /** + * If we're searching for files in multiple workspace folders, then better prepend the + * name of the workspace folder to the path of the file. This way we'll be able to + * better filter files that are all on the top of a workspace folder and have all the + * same name. A typical example are `package.json` or `README.md` files. + */ + private getSearchPath(folderQuery: IFolderQuery, relativePath: string): string { + if (folderQuery.folderName) { + return path.join(folderQuery.folderName, relativePath); + } + return relativePath; + } } export class Engine implements ISearchEngine { diff --git a/src/vs/workbench/services/search/node/rawSearchService.ts b/src/vs/workbench/services/search/node/rawSearchService.ts index 336d4cd0350..98e4ca43a95 100644 --- a/src/vs/workbench/services/search/node/rawSearchService.ts +++ b/src/vs/workbench/services/search/node/rawSearchService.ts @@ -5,7 +5,7 @@ import * as fs from 'fs'; import * as gracefulFs from 'graceful-fs'; -import { join, sep } from 'vs/base/common/path'; +import { basename, dirname, join, sep } from 'vs/base/common/path'; import * as arrays from 'vs/base/common/arrays'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -15,9 +15,9 @@ import * as objects from 'vs/base/common/objects'; import { StopWatch } from 'vs/base/common/stopwatch'; import * as strings from 'vs/base/common/strings'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { compareItemsByScore, IItemAccessor, prepareQuery, ScorerCache } from 'vs/base/parts/quickopen/common/quickOpenScorer'; +import { compareItemsByFuzzyScore, IItemAccessor, prepareQuery, FuzzyScorerCache } from 'vs/base/common/fuzzyScorer'; import { MAX_FILE_SIZE } from 'vs/base/node/pfs'; -import { ICachedSearchStats, IFileQuery, IFileSearchStats, IFolderQuery, IProgressMessage, IRawFileQuery, IRawQuery, IRawTextQuery, ITextQuery, IFileSearchProgressItem, IRawFileMatch, IRawSearchService, ISearchEngine, ISearchEngineSuccess, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedSearchSuccess } from 'vs/workbench/services/search/common/search'; +import { ICachedSearchStats, IFileQuery, IFileSearchStats, IFolderQuery, IProgressMessage, IRawFileQuery, IRawQuery, IRawTextQuery, ITextQuery, IFileSearchProgressItem, IRawFileMatch, IRawSearchService, ISearchEngine, ISearchEngineSuccess, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedSearchSuccess, isFilePatternMatch } from 'vs/workbench/services/search/common/search'; import { Engine as FileSearchEngine } from 'vs/workbench/services/search/node/fileSearch'; import { TextSearchEngineAdapter } from 'vs/workbench/services/search/node/textSearchAdapter'; @@ -179,11 +179,11 @@ export class SearchService implements IRawSearchService { } return allResultsPromise.then(([result, results]) => { - const scorerCache: ScorerCache = cache ? cache.scorerCache : Object.create(null); + const scorerCache: FuzzyScorerCache = cache ? cache.scorerCache : Object.create(null); const sortSW = (typeof config.maxResults !== 'number' || config.maxResults > 0) && StopWatch.create(false); return this.sortResults(config, results, scorerCache, token) .then<[ISerializedSearchSuccess, IRawFileMatch[]]>(sortedResults => { - // sortingTime: -1 indicates a "sorted" search that was not sorted, i.e. populating the cache when quickopen is opened. + // sortingTime: -1 indicates a "sorted" search that was not sorted, i.e. populating the cache when quickaccess is opened. // Contrasting with findFiles which is not sorted and will have sortingTime: undefined const sortingTime = sortSW ? sortSW.elapsed() : -1; @@ -246,13 +246,13 @@ export class SearchService implements IRawSearchService { return undefined; } - private sortResults(config: IFileQuery, results: IRawFileMatch[], scorerCache: ScorerCache, token?: CancellationToken): Promise { + private sortResults(config: IFileQuery, results: IRawFileMatch[], scorerCache: FuzzyScorerCache, token?: CancellationToken): Promise { // we use the same compare function that is used later when showing the results using fuzzy scoring // this is very important because we are also limiting the number of results by config.maxResults // and as such we want the top items to be included in this result set if the number of items // exceeds config.maxResults. const query = prepareQuery(config.filePattern || ''); - const compare = (matchA: IRawFileMatch, matchB: IRawFileMatch) => compareItemsByScore(matchA, matchB, query, true, FileMatchItemAccessor, scorerCache); + const compare = (matchA: IRawFileMatch, matchB: IRawFileMatch) => compareItemsByFuzzyScore(matchA, matchB, query, true, FileMatchItemAccessor, scorerCache); const maxResults = typeof config.maxResults === 'number' ? config.maxResults : Number.MAX_VALUE; return arrays.topAsync(results, compare, maxResults, 10000, token); @@ -312,11 +312,11 @@ export class SearchService implements IRawSearchService { // Pattern match on results const results: IRawFileMatch[] = []; - const normalizedSearchValueLowercase = prepareQuery(searchValue).lowercase; + const normalizedSearchValueLowercase = prepareQuery(searchValue).normalizedLowercase; for (const entry of cachedEntries) { // Check if this entry is a match for the search value - if (!strings.fuzzyContains(entry.relativePath, normalizedSearchValueLowercase)) { + if (!isFilePatternMatch(entry, normalizedSearchValueLowercase)) { continue; } @@ -408,17 +408,17 @@ class Cache { resultsToSearchCache: { [searchValue: string]: ICacheRow; } = Object.create(null); - scorerCache: ScorerCache = Object.create(null); + scorerCache: FuzzyScorerCache = Object.create(null); } const FileMatchItemAccessor = new class implements IItemAccessor { getItemLabel(match: IRawFileMatch): string { - return match.basename; // e.g. myFile.txt + return basename(match.relativePath); // e.g. myFile.txt } getItemDescription(match: IRawFileMatch): string { - return match.relativePath.substr(0, match.relativePath.length - match.basename.length - 1); // e.g. some/path/to/file + return dirname(match.relativePath); // e.g. some/path/to/file } getItemPath(match: IRawFileMatch): string { diff --git a/src/vs/workbench/services/search/test/node/rawSearchService.test.ts b/src/vs/workbench/services/search/test/node/rawSearchService.test.ts index 1d8bdda98df..b61f702fc56 100644 --- a/src/vs/workbench/services/search/test/node/rawSearchService.test.ts +++ b/src/vs/workbench/services/search/test/node/rawSearchService.test.ts @@ -83,8 +83,6 @@ suite('RawSearchService', () => { const rawMatch: IRawFileMatch = { base: path.normalize('/some'), relativePath: 'where', - basename: 'where', - size: 123 }; const match: ISerializedFileMatch = { @@ -342,8 +340,6 @@ suite('RawSearchService', () => { matches.push({ base: path.normalize('/some/where'), relativePath: 'bc', - basename: 'bc', - size: 3 }); const results: any[] = []; const cb: IProgressCallback = value => { diff --git a/src/vs/workbench/services/sharedProcess/electron-browser/sharedProcessService.ts b/src/vs/workbench/services/sharedProcess/electron-browser/sharedProcessService.ts index 8f1d6810a3d..8ebf99ffb30 100644 --- a/src/vs/workbench/services/sharedProcess/electron-browser/sharedProcessService.ts +++ b/src/vs/workbench/services/sharedProcess/electron-browser/sharedProcessService.ts @@ -5,11 +5,12 @@ import { Client } from 'vs/base/parts/ipc/common/ipc.net'; import { connect } from 'vs/base/parts/ipc/node/ipc.net'; -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; import { IChannel, IServerChannel, getDelayedChannel } from 'vs/base/parts/ipc/common/ipc'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; export class SharedProcessService implements ISharedProcessService { @@ -20,12 +21,12 @@ export class SharedProcessService implements ISharedProcessService { constructor( @IMainProcessService mainProcessService: IMainProcessService, - @IElectronEnvironmentService environmentService: IElectronEnvironmentService + @IWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService ) { this.sharedProcessMainChannel = mainProcessService.getChannel('sharedProcess'); this.withSharedProcessConnection = this.whenSharedProcessReady() - .then(() => connect(environmentService.sharedIPCHandle, `window:${environmentService.windowId}`)); + .then(() => connect(environmentService.sharedIPCHandle, `window:${environmentService.configuration.windowId}`)); } whenSharedProcessReady(): Promise { diff --git a/src/vs/workbench/services/statusbar/common/statusbar.ts b/src/vs/workbench/services/statusbar/common/statusbar.ts index 31f46524c9c..130101a530b 100644 --- a/src/vs/workbench/services/statusbar/common/statusbar.ts +++ b/src/vs/workbench/services/statusbar/common/statusbar.ts @@ -7,6 +7,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { IDisposable } from 'vs/base/common/lifecycle'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; import { Event } from 'vs/base/common/event'; +import { Command } from 'vs/editor/common/modes'; export const IStatusbarService = createDecorator('statusbarService'); @@ -45,12 +46,7 @@ export interface IStatusbarEntry { /** * An optional id of a command that is known to the workbench to execute on click */ - readonly command?: string; - - /** - * Optional arguments for the command. - */ - readonly arguments?: unknown[]; + readonly command?: string | Command; /** * Whether to show a beak above the status bar entry. diff --git a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts index c2900691c8e..b495fda6b19 100644 --- a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts @@ -17,6 +17,7 @@ import { resolveWorkbenchCommonProperties } from 'vs/platform/telemetry/node/wor import { TelemetryService as BaseTelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; export class TelemetryService extends Disposable implements ITelemetryService { @@ -25,7 +26,7 @@ export class TelemetryService extends Disposable implements ITelemetryService { private impl: ITelemetryService; constructor( - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, @IProductService productService: IProductService, @ISharedProcessService sharedProcessService: ISharedProcessService, @ILogService logService: ILogService, @@ -38,7 +39,7 @@ export class TelemetryService extends Disposable implements ITelemetryService { const channel = sharedProcessService.getChannel('telemetryAppender'); const config: ITelemetryServiceConfig = { appender: combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(logService)), - commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.machineId!, productService.msftInternalDomains, environmentService.installSourcePath, environmentService.configuration.remoteAuthority), + commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.machineId, productService.msftInternalDomains, environmentService.installSourcePath, environmentService.configuration.remoteAuthority), piiPaths: environmentService.extensionsPath ? [environmentService.appRoot, environmentService.extensionsPath] : [environmentService.appRoot] }; diff --git a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts index 6248309e3fb..d2509ab99b2 100644 --- a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts +++ b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts @@ -23,7 +23,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ITMSyntaxExtensionPoint, grammarsExtPoint } from 'vs/workbench/services/textMate/common/TMGrammars'; import { ITextMateService } from 'vs/workbench/services/textMate/common/textMateService'; -import { ITextMateThemingRule, IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ITextMateThemingRule, IWorkbenchThemeService, IWorkbenchColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IGrammar, StackElement, IOnigLib, IRawTheme } from 'vscode-textmate'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -247,7 +247,7 @@ export abstract class AbstractTextMateService extends Disposable implements ITex return result; } - private _updateTheme(grammarFactory: TMGrammarFactory, colorTheme: IColorTheme, forceUpdate: boolean): void { + private _updateTheme(grammarFactory: TMGrammarFactory, colorTheme: IWorkbenchColorTheme, forceUpdate: boolean): void { if (!forceUpdate && this._currentTheme && this._currentTokenColorMap && AbstractTextMateService.equalsTokenRules(this._currentTheme.settings, colorTheme.tokenColors) && equalArray(this._currentTokenColorMap, colorTheme.tokenColorMap)) { return; } diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index f97ce3ccfb2..ef6a97ac6ec 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -442,7 +442,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex //#region revert - async revert(resource: URI, options?: IRevertOptions): Promise { + async revert(resource: URI, options?: IRevertOptions): Promise { // Untitled if (resource.scheme === Schemas.untitled) { @@ -450,17 +450,15 @@ export abstract class AbstractTextFileService extends Disposable implements ITex if (model) { return model.revert(options); } - - return false; } // File - const model = this.files.get(resource); - if (model && (model.isDirty() || options?.force)) { - return model.revert(options); + else { + const model = this.files.get(resource); + if (model && (model.isDirty() || options?.force)) { + return model.revert(options); + } } - - return false; } //#endregion diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 949e69d12ef..17eadf382ca 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -206,9 +206,9 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil //#region Revert - async revert(options?: IRevertOptions): Promise { + async revert(options?: IRevertOptions): Promise { if (!this.isResolved()) { - return false; + return; } // Unset flags @@ -240,8 +240,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil if (wasDirty) { this._onDidChangeDirty.fire(); } - - return true; } //#endregion diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index 6f5824b53ed..a962a5338a7 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -9,7 +9,7 @@ import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { dispose, IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { ITextFileEditorModel, ITextFileEditorModelManager, ITextFileEditorModelLoadOrCreateOptions, ITextFileLoadEvent, ITextFileSaveEvent, ITextFileSaveParticipant, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileEditorModel, ITextFileEditorModelManager, ITextFileEditorModelLoadOrCreateOptions, ITextFileLoadEvent, ITextFileSaveEvent, ITextFileSaveParticipant } from 'vs/workbench/services/textfile/common/textfiles'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ResourceMap } from 'vs/base/common/map'; @@ -280,16 +280,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE const newModel = model = this.instantiationService.createInstance(TextFileEditorModel, resource, options ? options.encoding : undefined, options ? options.mode : undefined); modelPromise = model.load(options); - // Install model listeners - const modelListeners = new DisposableStore(); - modelListeners.add(model.onDidLoad(reason => this._onDidLoad.fire({ model: newModel, reason }))); - modelListeners.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(newModel))); - modelListeners.add(model.onDidSaveError(() => this._onDidSaveError.fire(newModel))); - modelListeners.add(model.onDidSave(reason => this._onDidSave.fire({ model: newModel, reason }))); - modelListeners.add(model.onDidRevert(() => this._onDidRevert.fire(newModel))); - modelListeners.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(newModel))); - - this.mapResourceToModelListeners.set(resource, modelListeners); + this.registerModel(newModel); } // Store pending loads to avoid race conditions @@ -298,9 +289,15 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE // Make known to manager (if not already known) this.add(resource, model); - // Signal as event if we created the model + // Emit some events if we created the model if (didCreateModel) { this._onDidCreate.fire(model); + + // If the model is dirty right from the beginning, + // make sure to emit this as an event + if (model.isDirty()) { + this._onDidChangeDirty.fire(model); + } } try { @@ -335,6 +332,21 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE } } + private registerModel(model: TextFileEditorModel): void { + + // Install model listeners + const modelListeners = new DisposableStore(); + modelListeners.add(model.onDidLoad(reason => this._onDidLoad.fire({ model, reason }))); + modelListeners.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(model))); + modelListeners.add(model.onDidSaveError(() => this._onDidSaveError.fire(model))); + modelListeners.add(model.onDidSave(reason => this._onDidSave.fire({ model: model, reason }))); + modelListeners.add(model.onDidRevert(() => this._onDidRevert.fire(model))); + modelListeners.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(model))); + + // Keep for disposal + this.mapResourceToModelListeners.set(model.resource, modelListeners); + } + add(resource: URI, model: TextFileEditorModel): void { const knownModel = this.mapResourceToModel.get(resource); if (knownModel === model) { @@ -376,7 +388,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE return this.saveParticipants.addSaveParticipant(participant); } - runSaveParticipants(model: IResolvedTextFileEditorModel, context: { reason: SaveReason; }, token: CancellationToken): Promise { + runSaveParticipants(model: ITextFileEditorModel, context: { reason: SaveReason; }, token: CancellationToken): Promise { return this.saveParticipants.participate(model, context, token); } diff --git a/src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts b/src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts index 0c40afacfa3..037f389fed8 100644 --- a/src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts +++ b/src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts @@ -8,9 +8,10 @@ import { raceCancellation } from 'vs/base/common/async'; import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; import { ILogService } from 'vs/platform/log/common/log'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { ITextFileSaveParticipant, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileSaveParticipant, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { SaveReason } from 'vs/workbench/common/editor'; import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { insert } from 'vs/base/common/arrays'; export class TextFileSaveParticipant extends Disposable { @@ -24,12 +25,12 @@ export class TextFileSaveParticipant extends Disposable { } addSaveParticipant(participant: ITextFileSaveParticipant): IDisposable { - this.saveParticipants.push(participant); + const remove = insert(this.saveParticipants, participant); - return toDisposable(() => this.saveParticipants.splice(this.saveParticipants.indexOf(participant), 1)); + return toDisposable(() => remove()); } - participate(model: IResolvedTextFileEditorModel, context: { reason: SaveReason; }, token: CancellationToken): Promise { + participate(model: ITextFileEditorModel, context: { reason: SaveReason; }, token: CancellationToken): Promise { const cts = new CancellationTokenSource(token); return this.progressService.withProgress({ @@ -40,10 +41,10 @@ export class TextFileSaveParticipant extends Disposable { }, async progress => { // undoStop before participation - model.textEditorModel.pushStackElement(); + model.textEditorModel?.pushStackElement(); for (const saveParticipant of this.saveParticipants) { - if (cts.token.isCancellationRequested) { + if (cts.token.isCancellationRequested || !model.textEditorModel /* disposed */) { break; } @@ -56,7 +57,7 @@ export class TextFileSaveParticipant extends Disposable { } // undoStop after participation - model.textEditorModel.pushStackElement(); + model.textEditorModel?.pushStackElement(); }, () => { // user cancel cts.dispose(true); diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index d3531ab7b7b..99154e4353a 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -78,7 +78,7 @@ export interface ITextFileService extends IDisposable { * @param resource the resource of the file to revert. * @param force to force revert even when the file is not dirty */ - revert(resource: URI, options?: IRevertOptions): Promise; + revert(resource: URI, options?: IRevertOptions): Promise; /** * Read the contents of a file identified by the resource. @@ -310,7 +310,7 @@ export interface ITextFileSaveParticipant { * before it is being saved to disk. */ participate( - model: IResolvedTextFileEditorModel, + model: ITextFileEditorModel, context: { reason: SaveReason }, progress: IProgress, token: CancellationToken @@ -347,7 +347,10 @@ export interface ITextFileEditorModelManager { */ addSaveParticipant(participant: ITextFileSaveParticipant): IDisposable; - runSaveParticipants(model: IResolvedTextFileEditorModel, context: { reason: SaveReason; }, token: CancellationToken): Promise + /** + * Runs the registered save participants on the provided model. + */ + runSaveParticipants(model: ITextFileEditorModel, context: { reason: SaveReason; }, token: CancellationToken): Promise disposeModel(model: ITextFileEditorModel): void; } @@ -411,7 +414,7 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport updatePreferredEncoding(encoding: string | undefined): void; save(options?: ITextFileSaveOptions): Promise; - revert(options?: IRevertOptions): Promise; + revert(options?: IRevertOptions): Promise; load(options?: ITextFileLoadOptions): Promise; diff --git a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts index aeff0a5f220..a605e6f64d7 100644 --- a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts @@ -39,6 +39,8 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; +import { ILogService } from 'vs/platform/log/common/log'; export class NativeTextFileService extends AbstractTextFileService { @@ -48,7 +50,7 @@ export class NativeTextFileService extends AbstractTextFileService { @ILifecycleService lifecycleService: ILifecycleService, @IInstantiationService instantiationService: IInstantiationService, @IModelService modelService: IModelService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService protected environmentService: INativeWorkbenchEnvironmentService, @IDialogService dialogService: IDialogService, @IFileDialogService fileDialogService: IFileDialogService, @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, @@ -57,7 +59,8 @@ export class NativeTextFileService extends AbstractTextFileService { @ITextModelService textModelService: ITextModelService, @ICodeEditorService codeEditorService: ICodeEditorService, @IRemotePathService remotePathService: IRemotePathService, - @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService + @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService, + @ILogService private readonly logService: ILogService ) { super(fileService, untitledTextEditorService, lifecycleService, instantiationService, modelService, environmentService, dialogService, fileDialogService, textResourceConfigurationService, filesConfigurationService, textModelService, codeEditorService, remotePathService, workingCopyFileService); } @@ -297,8 +300,16 @@ export class NativeTextFileService extends AbstractTextFileService { sudoCommand.push('--file-write', `"${source}"`, `"${target}"`); sudoPrompt.exec(sudoCommand.join(' '), promptOptions, (error: string, stdout: string, stderr: string) => { - if (error || stderr) { - reject(error || stderr); + if (stdout) { + this.logService.trace(`[sudo-prompt] received stdout: ${stdout}`); + } + + if (stderr) { + this.logService.trace(`[sudo-prompt] received stderr: ${stderr}`); + } + + if (error) { + reject(error); } else { resolve(undefined); } diff --git a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts index b61434bb1bc..1382a73a73f 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts @@ -8,7 +8,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { EncodingMode } from 'vs/workbench/common/editor'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { TextFileEditorModelState, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; -import { createFileInput, workbenchInstantiationService, TestServiceAccessor, TestReadonlyTextFileEditorModel } from 'vs/workbench/test/browser/workbenchTestServices'; +import { createFileEditorInput, workbenchInstantiationService, TestServiceAccessor, TestReadonlyTextFileEditorModel } from 'vs/workbench/test/browser/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { FileOperationResult, FileOperationError } from 'vs/platform/files/common/files'; @@ -469,8 +469,8 @@ suite('Files - TextFileEditorModel', () => { }); test('save() and isDirty() - proper with check for mtimes', async function () { - const input1 = createFileInput(instantiationService, toResource.call(this, '/path/index_async2.txt')); - const input2 = createFileInput(instantiationService, toResource.call(this, '/path/index_async.txt')); + const input1 = createFileEditorInput(instantiationService, toResource.call(this, '/path/index_async2.txt')); + const input2 = createFileEditorInput(instantiationService, toResource.call(this, '/path/index_async.txt')); const model1 = await input1.resolve() as TextFileEditorModel; const model2 = await input2.resolve() as TextFileEditorModel; diff --git a/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts index d90c11ca710..ab4d13394e2 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts @@ -97,8 +97,7 @@ suite('Files - TextFileService', () => { model!.textEditorModel!.setValue('foo'); assert.ok(accessor.textFileService.isDirty(model.resource)); - const res = await accessor.textFileService.revert(model.resource); - assert.ok(res); + await accessor.textFileService.revert(model.resource); assert.ok(!accessor.textFileService.isDirty(model.resource)); }); diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index dd95e155a03..bfc6050de83 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -17,6 +17,7 @@ import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/commo import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { IFileService } from 'vs/platform/files/common/files'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; class ResourceModelCollection extends ReferenceCollection> { @@ -26,7 +27,8 @@ class ResourceModelCollection extends ReferenceCollection () => Promise.resolve(p.provideTextContent(resource))); + if (resource.query || resource.fragment) { + type TextModelResolverUri = { + query: boolean; + fragment: boolean; + }; + type TextModelResolverUriMeta = { + query: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + fragment: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + }; + this.telemetryService.publicLog2('textmodelresolveruri', { + query: Boolean(resource.query), + fragment: Boolean(resource.fragment) + }); + } + const model = await first(factories); if (!model) { throw new Error('resource is not available'); diff --git a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts index a641d555298..d0b44241b87 100644 --- a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts +++ b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts @@ -8,12 +8,15 @@ import * as nls from 'vs/nls'; import * as Paths from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import * as Json from 'vs/base/common/json'; -import { ExtensionData, IThemeExtensionPoint, IFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ExtensionData, IThemeExtensionPoint, IWorkbenchFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IFileService } from 'vs/platform/files/common/files'; import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; import { asCSSUrl } from 'vs/base/browser/dom'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -export class FileIconThemeData implements IFileIconTheme { +const PERSISTED_FILE_ICON_THEME_STORAGE_KEY = 'iconThemeData'; + +export class FileIconThemeData implements IWorkbenchFileIconTheme { id: string; label: string; settingsId: string | null; @@ -78,7 +81,7 @@ export class FileIconThemeData implements IFileIconTheme { private static _noIconTheme: FileIconThemeData | null = null; - static noIconTheme(): FileIconThemeData { + static get noIconTheme(): FileIconThemeData { let themeData = FileIconThemeData._noIconTheme; if (!themeData) { themeData = FileIconThemeData._noIconTheme = new FileIconThemeData('', '', null); @@ -103,7 +106,12 @@ export class FileIconThemeData implements IFileIconTheme { return themeData; } - static fromStorageData(input: string): FileIconThemeData | null { + + static fromStorageData(storageService: IStorageService): FileIconThemeData | undefined { + const input = storageService.get(PERSISTED_FILE_ICON_THEME_STORAGE_KEY, StorageScope.GLOBAL); + if (!input) { + return undefined; + } try { let data = JSON.parse(input); const theme = new FileIconThemeData('', '', null); @@ -128,12 +136,12 @@ export class FileIconThemeData implements IFileIconTheme { } return theme; } catch (e) { - return null; + return undefined; } } - toStorageData() { - return JSON.stringify({ + toStorage(storageService: IStorageService) { + const data = JSON.stringify({ id: this.id, label: this.label, description: this.description, @@ -145,6 +153,7 @@ export class FileIconThemeData implements IFileIconTheme { hidesExplorerArrows: this.hidesExplorerArrows, watch: this.watch }); + storageService.store(PERSISTED_FILE_ICON_THEME_STORAGE_KEY, data, StorageScope.GLOBAL); } } @@ -228,8 +237,7 @@ function _processIconThemeDocument(id: string, iconThemeDocumentLocation: URI, i qualifier = baseThemeClassName + ' ' + qualifier; } - const expanded = '.monaco-tree-row.expanded'; // workaround for #11453 - const expanded2 = '.monaco-tl-twistie.collapsible:not(.collapsed) + .monaco-tl-contents'; // new tree + const expanded = '.monaco-tl-twistie.collapsible:not(.collapsed) + .monaco-tl-contents'; if (associations.folder) { addSelector(`${qualifier} .folder-icon::before`, associations.folder); @@ -238,7 +246,6 @@ function _processIconThemeDocument(id: string, iconThemeDocumentLocation: URI, i if (associations.folderExpanded) { addSelector(`${qualifier} ${expanded} .folder-icon::before`, associations.folderExpanded); - addSelector(`${qualifier} ${expanded2} .folder-icon::before`, associations.folderExpanded); result.hasFolderIcons = true; } @@ -252,7 +259,6 @@ function _processIconThemeDocument(id: string, iconThemeDocumentLocation: URI, i if (rootFolderExpanded) { addSelector(`${qualifier} ${expanded} .rootfolder-icon::before`, rootFolderExpanded); - addSelector(`${qualifier} ${expanded2} .rootfolder-icon::before`, rootFolderExpanded); result.hasFolderIcons = true; } @@ -272,7 +278,6 @@ function _processIconThemeDocument(id: string, iconThemeDocumentLocation: URI, i if (folderNamesExpanded) { for (let folderName in folderNamesExpanded) { addSelector(`${qualifier} ${expanded} .${escapeCSS(folderName.toLowerCase())}-name-folder-icon.folder-icon::before`, folderNamesExpanded[folderName]); - addSelector(`${qualifier} ${expanded2} .${escapeCSS(folderName.toLowerCase())}-name-folder-icon.folder-icon::before`, folderNamesExpanded[folderName]); result.hasFolderIcons = true; } } diff --git a/src/vs/workbench/services/themes/browser/fileIconThemeStore.ts b/src/vs/workbench/services/themes/browser/fileIconThemeStore.ts deleted file mode 100644 index 213986a9839..00000000000 --- a/src/vs/workbench/services/themes/browser/fileIconThemeStore.ts +++ /dev/null @@ -1,163 +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 nls from 'vs/nls'; - -import * as types from 'vs/base/common/types'; -import * as resources from 'vs/base/common/resources'; -import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { ExtensionData, IThemeExtensionPoint } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { Event, Emitter } from 'vs/base/common/event'; -import { FileIconThemeData } from 'vs/workbench/services/themes/browser/fileIconThemeData'; -import { URI } from 'vs/base/common/uri'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { find } from 'vs/base/common/arrays'; - -const iconThemeExtPoint = ExtensionsRegistry.registerExtensionPoint({ - extensionPoint: 'iconThemes', - jsonSchema: { - description: nls.localize('vscode.extension.contributes.iconThemes', 'Contributes file icon themes.'), - type: 'array', - items: { - type: 'object', - defaultSnippets: [{ body: { id: '${1:id}', label: '${2:label}', path: './fileicons/${3:id}-icon-theme.json' } }], - properties: { - id: { - description: nls.localize('vscode.extension.contributes.iconThemes.id', 'Id of the icon theme as used in the user settings.'), - type: 'string' - }, - label: { - description: nls.localize('vscode.extension.contributes.iconThemes.label', 'Label of the icon theme as shown in the UI.'), - type: 'string' - }, - path: { - description: nls.localize('vscode.extension.contributes.iconThemes.path', 'Path of the icon theme definition file. The path is relative to the extension folder and is typically \'./icons/awesome-icon-theme.json\'.'), - type: 'string' - } - }, - required: ['path', 'id'] - } - } -}); - -export interface FileIconThemeChangeEvent { - themes: FileIconThemeData[]; - added: FileIconThemeData[]; -} - -export class FileIconThemeStore extends Disposable { - - private knownIconThemes: FileIconThemeData[]; - - private readonly onDidChangeEmitter = this._register(new Emitter()); - readonly onDidChange: Event = this.onDidChangeEmitter.event; - - constructor(@IExtensionService private readonly extensionService: IExtensionService) { - super(); - this.knownIconThemes = []; - this.initialize(); - } - - private initialize() { - iconThemeExtPoint.setHandler((extensions) => { - const previousIds: { [key: string]: boolean; } = {}; - const added: FileIconThemeData[] = []; - for (const theme of this.knownIconThemes) { - previousIds[theme.id] = true; - } - this.knownIconThemes.length = 0; - for (let ext of extensions) { - let extensionData = { - extensionId: ext.description.identifier.value, - extensionPublisher: ext.description.publisher, - extensionName: ext.description.name, - extensionIsBuiltin: ext.description.isBuiltin, - extensionLocation: ext.description.extensionLocation - }; - this.onIconThemes(extensionData, ext.value, ext.collector); - } - for (const theme of this.knownIconThemes) { - if (!previousIds[theme.id]) { - added.push(theme); - } - } - this.onDidChangeEmitter.fire({ themes: this.knownIconThemes, added }); - }); - } - - private onIconThemes(extensionData: ExtensionData, iconThemes: IThemeExtensionPoint[], collector: ExtensionMessageCollector): void { - if (!Array.isArray(iconThemes)) { - collector.error(nls.localize( - 'reqarray', - "Extension point `{0}` must be an array.", - iconThemeExtPoint.name - )); - return; - } - iconThemes.forEach(iconTheme => { - if (!iconTheme.path || !types.isString(iconTheme.path)) { - collector.error(nls.localize( - 'reqpath', - "Expected string in `contributes.{0}.path`. Provided value: {1}", - iconThemeExtPoint.name, - String(iconTheme.path) - )); - return; - } - if (!iconTheme.id || !types.isString(iconTheme.id)) { - collector.error(nls.localize( - 'reqid', - "Expected string in `contributes.{0}.id`. Provided value: {1}", - iconThemeExtPoint.name, - String(iconTheme.path) - )); - return; - } - - const iconThemeLocation = resources.joinPath(extensionData.extensionLocation, iconTheme.path); - if (!resources.isEqualOrParent(iconThemeLocation, extensionData.extensionLocation)) { - collector.warn(nls.localize('invalid.path.1', "Expected `contributes.{0}.path` ({1}) to be included inside extension's folder ({2}). This might make the extension non-portable.", iconThemeExtPoint.name, iconThemeLocation.path, extensionData.extensionLocation.path)); - } - - let themeData = FileIconThemeData.fromExtensionTheme(iconTheme, iconThemeLocation, extensionData); - this.knownIconThemes.push(themeData); - }); - - } - - public findThemeData(iconTheme: string): Promise { - if (iconTheme.length === 0) { - return Promise.resolve(FileIconThemeData.noIconTheme()); - } - return this.getFileIconThemes().then(allIconSets => { - return find(allIconSets, iconSet => iconSet.id === iconTheme); - }); - } - - public findThemeBySettingsId(settingsId: string | null): Promise { - if (!settingsId) { - return Promise.resolve(FileIconThemeData.noIconTheme()); - } - return this.getFileIconThemes().then(allIconSets => { - return find(allIconSets, iconSet => iconSet.settingsId === settingsId); - }); - } - - public findThemeDataByExtensionLocation(extLocation: URI | undefined): Promise { - if (extLocation) { - return this.getFileIconThemes().then(allThemes => { - return allThemes.filter(t => t.extensionData && resources.isEqualOrParent(t.extensionData.extensionLocation, extLocation)); - }); - } - return Promise.resolve([]); - } - - public getFileIconThemes(): Promise { - return this.extensionService.whenInstalledExtensionsRegistered().then(isReady => { - return this.knownIconThemes; - }); - } -} diff --git a/src/vs/workbench/services/themes/browser/productIconThemeData.ts b/src/vs/workbench/services/themes/browser/productIconThemeData.ts new file mode 100644 index 00000000000..092732fc660 --- /dev/null +++ b/src/vs/workbench/services/themes/browser/productIconThemeData.ts @@ -0,0 +1,205 @@ +/*--------------------------------------------------------------------------------------------- + * 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 'vs/base/common/uri'; +import * as nls from 'vs/nls'; +import * as Paths from 'vs/base/common/path'; +import * as resources from 'vs/base/common/resources'; +import * as Json from 'vs/base/common/json'; +import { ExtensionData, IThemeExtensionPoint, IWorkbenchProductIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IFileService } from 'vs/platform/files/common/files'; +import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; +import { asCSSUrl } from 'vs/base/browser/dom'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { DEFAULT_PRODUCT_ICON_THEME_SETTING_VALUE } from 'vs/workbench/services/themes/common/themeConfiguration'; + +const PERSISTED_PRODUCT_ICON_THEME_STORAGE_KEY = 'productIconThemeData'; + +export const DEFAULT_PRODUCT_ICON_THEME_ID = ''; // TODO + +export class ProductIconThemeData implements IWorkbenchProductIconTheme { + id: string; + label: string; + settingsId: string; + description?: string; + isLoaded: boolean; + location?: URI; + extensionData?: ExtensionData; + watch?: boolean; + + styleSheetContent?: string; + + private constructor(id: string, label: string, settingsId: string) { + this.id = id; + this.label = label; + this.settingsId = settingsId; + this.isLoaded = false; + } + + public ensureLoaded(fileService: IFileService): Promise { + return !this.isLoaded ? this.load(fileService) : Promise.resolve(this.styleSheetContent); + } + + public reload(fileService: IFileService): Promise { + return this.load(fileService); + } + + private load(fileService: IFileService): Promise { + if (!this.location) { + return Promise.resolve(this.styleSheetContent); + } + return _loadProductIconThemeDocument(fileService, this.location).then(iconThemeDocument => { + const result = _processIconThemeDocument(this.id, this.location!, iconThemeDocument); + this.styleSheetContent = result.content; + this.isLoaded = true; + return this.styleSheetContent; + }); + } + + static fromExtensionTheme(iconTheme: IThemeExtensionPoint, iconThemeLocation: URI, extensionData: ExtensionData): ProductIconThemeData { + const id = extensionData.extensionId + '-' + iconTheme.id; + const label = iconTheme.label || Paths.basename(iconTheme.path); + const settingsId = iconTheme.id; + + const themeData = new ProductIconThemeData(id, label, settingsId); + + themeData.description = iconTheme.description; + themeData.location = iconThemeLocation; + themeData.extensionData = extensionData; + themeData.watch = iconTheme._watch; + themeData.isLoaded = false; + return themeData; + } + + static createUnloadedTheme(id: string): ProductIconThemeData { + const themeData = new ProductIconThemeData(id, '', '__' + id); + themeData.isLoaded = false; + themeData.extensionData = undefined; + themeData.watch = false; + return themeData; + } + + private static _defaultProductIconTheme: ProductIconThemeData | null = null; + + static get defaultTheme(): ProductIconThemeData { + let themeData = ProductIconThemeData._defaultProductIconTheme; + if (!themeData) { + themeData = ProductIconThemeData._defaultProductIconTheme = new ProductIconThemeData(DEFAULT_PRODUCT_ICON_THEME_ID, nls.localize('defaultTheme', 'Default'), DEFAULT_PRODUCT_ICON_THEME_SETTING_VALUE); + themeData.isLoaded = true; + themeData.extensionData = undefined; + themeData.watch = false; + } + return themeData; + } + + static fromStorageData(storageService: IStorageService): ProductIconThemeData | undefined { + const input = storageService.get(PERSISTED_PRODUCT_ICON_THEME_STORAGE_KEY, StorageScope.GLOBAL); + if (!input) { + return undefined; + } + try { + let data = JSON.parse(input); + const theme = new ProductIconThemeData('', '', ''); + for (let key in data) { + switch (key) { + case 'id': + case 'label': + case 'description': + case 'settingsId': + case 'extensionData': + case 'styleSheetContent': + case 'watch': + (theme as any)[key] = data[key]; + break; + case 'location': + theme.location = URI.revive(data.location); + break; + } + } + return theme; + } catch (e) { + return undefined; + } + } + + toStorage(storageService: IStorageService) { + const data = JSON.stringify({ + id: this.id, + label: this.label, + description: this.description, + settingsId: this.settingsId, + location: this.location, + styleSheetContent: this.styleSheetContent, + watch: this.watch + }); + storageService.store(PERSISTED_PRODUCT_ICON_THEME_STORAGE_KEY, data, StorageScope.GLOBAL); + } +} + +interface IconDefinition { + fontCharacter: string; + fontId: string; +} + +interface FontDefinition { + id: string; + weight: string; + style: string; + size: string; + src: { path: string; format: string; }[]; +} + +interface ProductIconThemeDocument { + iconDefinitions: { [key: string]: IconDefinition }; + fonts: FontDefinition[]; +} + +function _loadProductIconThemeDocument(fileService: IFileService, location: URI): Promise { + return fileService.readFile(location).then((content) => { + let errors: Json.ParseError[] = []; + let contentValue = Json.parse(content.value.toString(), errors); + if (errors.length > 0) { + return Promise.reject(new Error(nls.localize('error.cannotparseicontheme', "Problems parsing product icons file: {0}", errors.map(e => getParseErrorMessage(e.error)).join(', ')))); + } else if (Json.getNodeType(contentValue) !== 'object') { + return Promise.reject(new Error(nls.localize('error.invalidformat', "Invalid format for product icons theme file: Object expected."))); + } else if (!contentValue.iconDefinitions || !Array.isArray(contentValue.fonts) || !contentValue.fonts.length) { + return Promise.reject(new Error(nls.localize('error.missingProperties', "Invalid format for product icons theme file: Must contain iconDefinitions and fonts."))); + } + return Promise.resolve(contentValue); + }); +} + +function _processIconThemeDocument(id: string, iconThemeDocumentLocation: URI, iconThemeDocument: ProductIconThemeDocument): { content: string; } { + + const result = { content: '' }; + + if (!iconThemeDocument.iconDefinitions || !Array.isArray(iconThemeDocument.fonts) || !iconThemeDocument.fonts.length) { + return result; + } + + const iconThemeDocumentLocationDirname = resources.dirname(iconThemeDocumentLocation); + function resolvePath(path: string) { + return resources.joinPath(iconThemeDocumentLocationDirname, path); + } + + let cssRules: string[] = []; + + let fonts = iconThemeDocument.fonts; + for (const font of fonts) { + const src = font.src.map(l => `${asCSSUrl(resolvePath(l.path))} format('${l.format}')`).join(', '); + cssRules.push(`@font-face { src: ${src}; font-family: '${font.id}'; font-weight: ${font.weight}; font-style: ${font.style}; }`); + } + + let primaryFontId = fonts[0].id; + let iconDefinitions = iconThemeDocument.iconDefinitions; + for (const iconId in iconThemeDocument.iconDefinitions) { + const definition = iconDefinitions[iconId]; + if (definition && definition.fontCharacter) { + cssRules.push(`.codicon-${iconId}:before { content: '${definition.fontCharacter}' !important; font-family: ${definition.fontId || primaryFontId} !important; }`); + } + } + result.content = cssRules.join('\n'); + return result; +} diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 516ed99302b..7c23c58f963 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -6,64 +6,48 @@ import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IWorkbenchThemeService, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, IColorCustomizations, CUSTOM_EDITOR_TOKENSTYLES_SETTING, IExperimentalTokenStyleCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService, IWorkbenchColorTheme, IWorkbenchFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, ThemeSettings, IWorkbenchProductIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Registry } from 'vs/platform/registry/common/platform'; import * as errors from 'vs/base/common/errors'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry'; import { ColorThemeData } from 'vs/workbench/services/themes/common/colorThemeData'; -import { ITheme, Extensions as ThemingExtensions, IThemingRegistry, ThemeType, LIGHT, DARK, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, Extensions as ThemingExtensions, IThemingRegistry, ThemeType, LIGHT, DARK, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; import { Event, Emitter } from 'vs/base/common/event'; import { registerFileIconThemeSchemas } from 'vs/workbench/services/themes/common/fileIconThemeSchema'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { ColorThemeStore } from 'vs/workbench/services/themes/common/colorThemeStore'; -import { FileIconThemeStore } from 'vs/workbench/services/themes/browser/fileIconThemeStore'; import { FileIconThemeData } from 'vs/workbench/services/themes/browser/fileIconThemeData'; import { removeClasses, addClasses } from 'vs/base/browser/dom'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IFileService, FileChangeType } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; -import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { textmateColorsSchemaId, registerColorThemeSchemas, textmateColorGroupSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema'; -import { workbenchColorsSchemaId } from 'vs/platform/theme/common/colorRegistry'; -import { tokenStylingSchemaId } from 'vs/platform/theme/common/tokenClassificationRegistry'; +import { registerColorThemeSchemas } from 'vs/workbench/services/themes/common/colorThemeSchema'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; - -// settings - -const PREFERRED_DARK_THEME_SETTING = 'workbench.preferredDarkColorTheme'; -const PREFERRED_LIGHT_THEME_SETTING = 'workbench.preferredLightColorTheme'; -const PREFERRED_HC_THEME_SETTING = 'workbench.preferredHighContrastColorTheme'; -const DETECT_COLOR_SCHEME_SETTING = 'window.autoDetectColorScheme'; -const DETECT_HC_SETTING = 'window.autoDetectHighContrast'; +import { ThemeRegistry, registerColorThemeExtensionPoint, registerFileIconThemeExtensionPoint, registerProductIconThemeExtensionPoint } from 'vs/workbench/services/themes/common/themeExtensionPoints'; +import { updateColorThemeConfigurationSchemas, updateFileIconThemeConfigurationSchemas, ThemeConfiguration, updateProductIconThemeConfigurationSchemas } from 'vs/workbench/services/themes/common/themeConfiguration'; +import { ProductIconThemeData, DEFAULT_PRODUCT_ICON_THEME_ID } from 'vs/workbench/services/themes/browser/productIconThemeData'; +import { registerProductIconThemeSchemas } from 'vs/workbench/services/themes/common/productIconThemeSchema'; // implementation -const DEFAULT_THEME_ID = 'vs-dark vscode-theme-defaults-themes-dark_plus-json'; -const DEFAULT_THEME_SETTING_VALUE = 'Default Dark+'; -const DEFAULT_THEME_DARK_SETTING_VALUE = 'Default Dark+'; -const DEFAULT_THEME_LIGHT_SETTING_VALUE = 'Default Light+'; -const DEFAULT_THEME_HC_SETTING_VALUE = 'Default High Contrast'; +const DEFAULT_COLOR_THEME_ID = 'vs-dark vscode-theme-defaults-themes-dark_plus-json'; -const PERSISTED_THEME_STORAGE_KEY = 'colorThemeData'; -const PERSISTED_ICON_THEME_STORAGE_KEY = 'iconThemeData'; const PERSISTED_OS_COLOR_SCHEME = 'osColorScheme'; const defaultThemeExtensionId = 'vscode-theme-defaults'; const oldDefaultThemeExtensionId = 'vscode-theme-colorful-defaults'; -const DEFAULT_ICON_THEME_SETTING_VALUE = 'vs-seti'; -const DEFAULT_ICON_THEME_ID = 'vscode.vscode-theme-seti-vs-seti'; +const DEFAULT_FILE_ICON_THEME_ID = 'vscode.vscode-theme-seti-vs-seti'; const fileIconsEnabledClass = 'file-icons-enabled'; const colorThemeRulesClassName = 'contributedColorTheme'; -const iconThemeRulesClassName = 'contributedIconTheme'; +const fileIconThemeRulesClassName = 'contributedFileIconTheme'; +const productIconThemeRulesClassName = 'contributedProductIconTheme'; const themingRegistry = Registry.as(ThemingExtensions.ThemingContribution); @@ -79,35 +63,31 @@ function validateThemeId(theme: string): string { return theme; } +const colorThemesExtPoint = registerColorThemeExtensionPoint(); +const fileIconThemesExtPoint = registerFileIconThemeExtensionPoint(); +const productIconThemesExtPoint = registerProductIconThemeExtensionPoint(); + export class WorkbenchThemeService implements IWorkbenchThemeService { _serviceBrand: undefined; - private colorThemeStore: ColorThemeStore; + private readonly container: HTMLElement; + private settings: ThemeConfiguration; + + private readonly colorThemeRegistry: ThemeRegistry; private currentColorTheme: ColorThemeData; - private container: HTMLElement; - private readonly onColorThemeChange: Emitter; - private watchedColorThemeLocation: URI | undefined; - private watchedColorThemeDisposable: IDisposable | undefined; + private readonly onColorThemeChange: Emitter; + private readonly colorThemeWatcher: ThemeFileWatcher; + private colorThemingParticipantChangeListener: IDisposable | undefined; - private iconThemeStore: FileIconThemeStore; - private currentIconTheme: FileIconThemeData; - private readonly onFileIconThemeChange: Emitter; - private watchedIconThemeLocation: URI | undefined; - private watchedIconThemeDisposable: IDisposable | undefined; + private readonly fileIconThemeRegistry: ThemeRegistry; + private currentFileIconTheme: FileIconThemeData; + private readonly onFileIconThemeChange: Emitter; + private readonly fileIconThemeWatcher: ThemeFileWatcher; - private themingParticipantChangeListener: IDisposable | undefined; - - private get colorCustomizations(): IColorCustomizations { - return this.configurationService.getValue(CUSTOM_WORKBENCH_COLORS_SETTING) || {}; - } - - private get tokenColorCustomizations(): ITokenColorCustomizations { - return this.configurationService.getValue(CUSTOM_EDITOR_COLORS_SETTING) || {}; - } - - private get tokenStylesCustomizations(): IExperimentalTokenStyleCustomizations { - return this.configurationService.getValue(CUSTOM_EDITOR_TOKENSTYLES_SETTING) || {}; - } + private readonly productIconThemeRegistry: ThemeRegistry; + private currentProductIconTheme: ProductIconThemeData; + private readonly onProductIconThemeChange: Emitter; + private readonly productIconThemeWatcher: ThemeFileWatcher; constructor( @IExtensionService extensionService: IExtensionService, @@ -119,43 +99,43 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { @IExtensionResourceLoaderService private readonly extensionResourceLoaderService: IExtensionResourceLoaderService, @IWorkbenchLayoutService readonly layoutService: IWorkbenchLayoutService ) { - this.container = layoutService.getWorkbenchContainer(); - this.colorThemeStore = new ColorThemeStore(extensionService); - this.onFileIconThemeChange = new Emitter(); - this.iconThemeStore = new FileIconThemeStore(extensionService); - this.onColorThemeChange = new Emitter({ leakWarningThreshold: 400 }); + this.settings = new ThemeConfiguration(configurationService); + this.colorThemeRegistry = new ThemeRegistry(extensionService, colorThemesExtPoint, ColorThemeData.fromExtensionTheme); + this.colorThemeWatcher = new ThemeFileWatcher(fileService, environmentService, this.reloadCurrentColorTheme.bind(this)); + this.onColorThemeChange = new Emitter({ leakWarningThreshold: 400 }); this.currentColorTheme = ColorThemeData.createUnloadedTheme(''); - this.currentIconTheme = FileIconThemeData.createUnloadedTheme(''); + + this.fileIconThemeWatcher = new ThemeFileWatcher(fileService, environmentService, this.reloadCurrentFileIconTheme.bind(this)); + this.fileIconThemeRegistry = new ThemeRegistry(extensionService, fileIconThemesExtPoint, FileIconThemeData.fromExtensionTheme, true, FileIconThemeData.noIconTheme); + this.onFileIconThemeChange = new Emitter(); + this.currentFileIconTheme = FileIconThemeData.createUnloadedTheme(''); + + this.productIconThemeWatcher = new ThemeFileWatcher(fileService, environmentService, this.reloadCurrentProductIconTheme.bind(this)); + this.productIconThemeRegistry = new ThemeRegistry(extensionService, productIconThemesExtPoint, ProductIconThemeData.fromExtensionTheme, true, ProductIconThemeData.defaultTheme, true); + this.onProductIconThemeChange = new Emitter(); + this.currentProductIconTheme = ProductIconThemeData.createUnloadedTheme(''); // In order to avoid paint flashing for tokens, because // themes are loaded asynchronously, we need to initialize // a color theme document with good defaults until the theme is loaded - let themeData: ColorThemeData | undefined = undefined; - let persistedThemeData = this.storageService.get(PERSISTED_THEME_STORAGE_KEY, StorageScope.GLOBAL); - if (persistedThemeData) { - themeData = ColorThemeData.fromStorageData(persistedThemeData); - } - let containerBaseTheme = this.getBaseThemeFromContainer(); + let themeData: ColorThemeData | undefined = ColorThemeData.fromStorageData(this.storageService); + const containerBaseTheme = this.getBaseThemeFromContainer(); if (!themeData || themeData.baseTheme !== containerBaseTheme) { themeData = ColorThemeData.createUnloadedTheme(containerBaseTheme); } - themeData.setCustomColors(this.colorCustomizations); - themeData.setCustomTokenColors(this.tokenColorCustomizations); - themeData.setCustomTokenStyleRules(this.tokenStylesCustomizations); - this.updateDynamicCSSRules(themeData); + themeData.setCustomizations(this.settings); this.applyTheme(themeData, undefined, true); - let persistedIconThemeData = this.storageService.get(PERSISTED_ICON_THEME_STORAGE_KEY, StorageScope.GLOBAL); - if (persistedIconThemeData) { - const iconData = FileIconThemeData.fromStorageData(persistedIconThemeData); - if (iconData) { - _applyIconTheme(iconData, () => { - this.doSetFileIconTheme(iconData); - return Promise.resolve(iconData); - }); - } + const fileIconData = FileIconThemeData.fromStorageData(this.storageService); + if (fileIconData) { + this.applyAndSetFileIconTheme(fileIconData); + } + + const productIconData = ProductIconThemeData.fromStorageData(this.storageService); + if (productIconData) { + this.applyAndSetProductIconTheme(productIconData); } this.initialize().then(undefined, errors.onUnexpectedError).then(_ => { @@ -166,132 +146,76 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { let prevColorId: string | undefined = undefined; // update settings schema setting for theme specific settings - this.colorThemeStore.onDidChange(async event => { - // updates enum for the 'workbench.colorTheme` setting - colorThemeSettingEnum.splice(0, colorThemeSettingEnum.length, ...event.themes.map(t => t.settingsId)); - colorThemeSettingEnumDescriptions.splice(0, colorThemeSettingEnumDescriptions.length, ...event.themes.map(t => t.description || '')); - - const themeSpecificWorkbenchColors: IJSONSchema = { properties: {} }; - const themeSpecificTokenColors: IJSONSchema = { properties: {} }; - const themeSpecificTokenStyling: IJSONSchema = { properties: {} }; - - const workbenchColors = { $ref: workbenchColorsSchemaId, additionalProperties: false }; - const tokenColors = { properties: tokenColorSchema.properties, additionalProperties: false }; - const tokenStyling = { $ref: tokenStylingSchemaId, additionalProperties: false }; - for (let t of event.themes) { - // add theme specific color customization ("[Abyss]":{ ... }) - const themeId = `[${t.settingsId}]`; - themeSpecificWorkbenchColors.properties![themeId] = workbenchColors; - themeSpecificTokenColors.properties![themeId] = tokenColors; - themeSpecificTokenStyling.properties![themeId] = tokenStyling; - } - - colorCustomizationsSchema.allOf![1] = themeSpecificWorkbenchColors; - tokenColorCustomizationSchema.allOf![1] = themeSpecificTokenColors; - experimentalTokenStylingCustomizationSchema.allOf![1] = themeSpecificTokenStyling; - - configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration, tokenColorCustomizationConfiguration); - - let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); - if (colorThemeSetting !== this.currentColorTheme.settingsId) { - const theme = await this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, undefined); - if (theme) { - this.setColorTheme(theme.id, undefined); - return; - } - } - - if (this.currentColorTheme.isLoaded) { - const themeData = await this.colorThemeStore.findThemeData(this.currentColorTheme.id); - if (!themeData) { - // current theme is no longer available - prevColorId = this.currentColorTheme.id; - this.setColorTheme(DEFAULT_THEME_ID, 'auto'); - } else { - if (this.currentColorTheme.id === DEFAULT_THEME_ID && !types.isUndefined(prevColorId) && await this.colorThemeStore.findThemeData(prevColorId)) { - // restore color - this.setColorTheme(prevColorId, 'auto'); - prevColorId = undefined; - } else { - this.reloadCurrentColorTheme(); - } + this.colorThemeRegistry.onDidChange(async event => { + updateColorThemeConfigurationSchemas(event.themes); + if (await this.restoreColorTheme()) { // checks if theme from settings exists and is set + // restore theme + if (this.currentColorTheme.id === DEFAULT_COLOR_THEME_ID && !types.isUndefined(prevColorId) && await this.colorThemeRegistry.findThemeById(prevColorId)) { + // restore theme + this.setColorTheme(prevColorId, 'auto'); + prevColorId = undefined; + } else if (event.added.some(t => t.settingsId === this.currentColorTheme.settingsId)) { + this.reloadCurrentColorTheme(); } + } else if (event.removed.some(t => t.settingsId === this.currentColorTheme.settingsId)) { + // current theme is no longer available + prevColorId = this.currentColorTheme.id; + this.setColorTheme(DEFAULT_COLOR_THEME_ID, 'auto'); } }); let prevFileIconId: string | undefined = undefined; - this.iconThemeStore.onDidChange(async event => { - iconThemeSettingSchema.enum = [null, ...event.themes.map(t => t.settingsId)]; - iconThemeSettingSchema.enumDescriptions = [iconThemeSettingSchema.enumDescriptions![0], ...event.themes.map(t => t.description || '')]; - configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration); - - let iconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING); - if (iconThemeSetting !== this.currentIconTheme.settingsId) { - const theme = await this.iconThemeStore.findThemeBySettingsId(iconThemeSetting); - if (theme) { - this.setFileIconTheme(theme.id, undefined); - return; + this.fileIconThemeRegistry.onDidChange(async event => { + updateFileIconThemeConfigurationSchemas(event.themes); + if (await this.restoreFileIconTheme()) { // checks if theme from settings exists and is set + // restore theme + if (this.currentFileIconTheme.id === DEFAULT_FILE_ICON_THEME_ID && !types.isUndefined(prevFileIconId) && await this.fileIconThemeRegistry.findThemeById(prevFileIconId)) { + this.setFileIconTheme(prevFileIconId, 'auto'); + prevFileIconId = undefined; + } else if (event.added.some(t => t.settingsId === this.currentFileIconTheme.settingsId)) { + this.reloadCurrentFileIconTheme(); } + } else if (event.removed.some(t => t.settingsId === this.currentFileIconTheme.settingsId)) { + // current theme is no longer available + prevFileIconId = this.currentFileIconTheme.id; + this.setFileIconTheme(DEFAULT_FILE_ICON_THEME_ID, 'auto'); } - if (this.currentIconTheme.isLoaded) { - const theme = await this.iconThemeStore.findThemeData(this.currentIconTheme.id); - if (!theme) { - // current theme is no longer available - prevFileIconId = this.currentIconTheme.id; - this.setFileIconTheme(DEFAULT_ICON_THEME_ID, 'auto'); - } else { - // restore color - if (this.currentIconTheme.id === DEFAULT_ICON_THEME_ID && !types.isUndefined(prevFileIconId) && await this.iconThemeStore.findThemeData(prevFileIconId)) { - this.setFileIconTheme(prevFileIconId, 'auto'); - prevFileIconId = undefined; - } else { - this.reloadCurrentFileIconTheme(); - } - } - } }); - this.fileService.onDidFilesChange(async e => { - if (this.watchedColorThemeLocation && this.currentColorTheme && e.contains(this.watchedColorThemeLocation, FileChangeType.UPDATED)) { - this.reloadCurrentColorTheme(); - } - if (this.watchedIconThemeLocation && this.currentIconTheme && e.contains(this.watchedIconThemeLocation, FileChangeType.UPDATED)) { - this.reloadCurrentFileIconTheme(); + let prevProductIconId: string | undefined = undefined; + this.productIconThemeRegistry.onDidChange(async event => { + updateProductIconThemeConfigurationSchemas(event.themes); + if (await this.restoreProductIconTheme()) { // checks if theme from settings exists and is set + // restore theme + if (this.currentProductIconTheme.id === DEFAULT_PRODUCT_ICON_THEME_ID && !types.isUndefined(prevProductIconId) && await this.productIconThemeRegistry.findThemeById(prevProductIconId)) { + this.setProductIconTheme(prevProductIconId, 'auto'); + prevProductIconId = undefined; + } else if (event.added.some(t => t.settingsId === this.currentProductIconTheme.settingsId)) { + this.reloadCurrentProductIconTheme(); + } + } else if (event.removed.some(t => t.settingsId === this.currentProductIconTheme.settingsId)) { + // current theme is no longer available + prevProductIconId = this.currentProductIconTheme.id; + this.setProductIconTheme(DEFAULT_PRODUCT_ICON_THEME_ID, 'auto'); } }); } - public get onDidColorThemeChange(): Event { + public get onDidColorThemeChange(): Event { return this.onColorThemeChange.event; } - public get onDidFileIconThemeChange(): Event { - return this.onFileIconThemeChange.event; - } - - public get onIconThemeChange(): Event { - return this.onFileIconThemeChange.event; - } - - public get onThemeChange(): Event { - return this.onColorThemeChange.event; - } - - private initialize(): Promise<[IColorTheme | null, IFileIconTheme | null]> { - const colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); - const iconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING); - + private initialize(): Promise<[IWorkbenchColorTheme | null, IWorkbenchFileIconTheme | null, IWorkbenchProductIconTheme | null]> { const extDevLocs = this.environmentService.extensionDevelopmentLocationURI; + const extDevLoc = extDevLocs && extDevLocs.length === 1 ? extDevLocs[0] : undefined; // in dev mode, switch to a theme provided by the extension under dev. const initializeColorTheme = async () => { - if (extDevLocs && extDevLocs.length === 1) { // in dev mode, switch to a theme provided by the extension under dev. - const devThemes = await this.colorThemeStore.findThemeDataByExtensionLocation(extDevLocs[0]); - if (devThemes.length) { - return this.setColorTheme(devThemes[0].id, ConfigurationTarget.MEMORY); - } + const devThemes = await this.colorThemeRegistry.findThemeByExtensionLocation(extDevLoc); + if (devThemes.length) { + return this.setColorTheme(devThemes[0].id, ConfigurationTarget.MEMORY); } - let theme = await this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID); + const theme = await this.colorThemeRegistry.findThemeBySettingsId(this.settings.colorTheme, DEFAULT_COLOR_THEME_ID); const persistedColorScheme = this.storageService.get(PERSISTED_OS_COLOR_SCHEME, StorageScope.GLOBAL); const preferredColorScheme = this.getPreferredColorScheme(); @@ -301,64 +225,62 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return this.setColorTheme(theme && theme.id, undefined); }; - const initializeIconTheme = async () => { - if (extDevLocs && extDevLocs.length === 1) { // in dev mode, switch to a theme provided by the extension under dev. - const devThemes = await this.iconThemeStore.findThemeDataByExtensionLocation(extDevLocs[0]); - if (devThemes.length) { - return this.setFileIconTheme(devThemes[0].id, ConfigurationTarget.MEMORY); - } + const initializeFileIconTheme = async () => { + const devThemes = await this.fileIconThemeRegistry.findThemeByExtensionLocation(extDevLoc); + if (devThemes.length) { + return this.setFileIconTheme(devThemes[0].id, ConfigurationTarget.MEMORY); } - const theme = await this.iconThemeStore.findThemeBySettingsId(iconThemeSetting); - return this.setFileIconTheme(theme ? theme.id : DEFAULT_ICON_THEME_ID, undefined); + const theme = await this.fileIconThemeRegistry.findThemeBySettingsId(this.settings.fileIconTheme); + return this.setFileIconTheme(theme ? theme.id : DEFAULT_FILE_ICON_THEME_ID, undefined); }; - return Promise.all([initializeColorTheme(), initializeIconTheme()]); + const initializeProductIconTheme = async () => { + const devThemes = await this.productIconThemeRegistry.findThemeByExtensionLocation(extDevLoc); + if (devThemes.length) { + return this.setProductIconTheme(devThemes[0].id, ConfigurationTarget.MEMORY); + } + const theme = await this.productIconThemeRegistry.findThemeBySettingsId(this.settings.productIconTheme); + return this.setProductIconTheme(theme ? theme.id : DEFAULT_PRODUCT_ICON_THEME_ID, undefined); + }; + + return Promise.all([initializeColorTheme(), initializeFileIconTheme(), initializeProductIconTheme()]); } private installConfigurationListener() { this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(COLOR_THEME_SETTING)) { - let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); - if (colorThemeSetting !== this.currentColorTheme.settingsId) { - this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, undefined).then(theme => { - if (theme) { - this.setColorTheme(theme.id, undefined); - } - }); - } + if (e.affectsConfiguration(ThemeSettings.COLOR_THEME)) { + this.restoreColorTheme(); } - if (e.affectsConfiguration(DETECT_COLOR_SCHEME_SETTING)) { + if (e.affectsConfiguration(ThemeSettings.DETECT_COLOR_SCHEME)) { this.handlePreferredSchemeUpdated(); } - if (e.affectsConfiguration(PREFERRED_DARK_THEME_SETTING) && this.getPreferredColorScheme() === DARK) { + if (e.affectsConfiguration(ThemeSettings.PREFERRED_DARK_THEME) && this.getPreferredColorScheme() === DARK) { this.applyPreferredColorTheme(DARK); } - if (e.affectsConfiguration(PREFERRED_LIGHT_THEME_SETTING) && this.getPreferredColorScheme() === LIGHT) { + if (e.affectsConfiguration(ThemeSettings.PREFERRED_LIGHT_THEME) && this.getPreferredColorScheme() === LIGHT) { this.applyPreferredColorTheme(LIGHT); } - if (e.affectsConfiguration(PREFERRED_HC_THEME_SETTING) && this.getPreferredColorScheme() === HIGH_CONTRAST) { + if (e.affectsConfiguration(ThemeSettings.PREFERRED_HC_THEME) && this.getPreferredColorScheme() === HIGH_CONTRAST) { this.applyPreferredColorTheme(HIGH_CONTRAST); } - if (e.affectsConfiguration(ICON_THEME_SETTING)) { - let iconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING); - if (iconThemeSetting !== this.currentIconTheme.settingsId) { - this.iconThemeStore.findThemeBySettingsId(iconThemeSetting).then(theme => { - this.setFileIconTheme(theme ? theme.id : DEFAULT_ICON_THEME_ID, undefined); - }); - } + if (e.affectsConfiguration(ThemeSettings.FILE_ICON_THEME)) { + this.restoreFileIconTheme(); + } + if (e.affectsConfiguration(ThemeSettings.PRODUCT_ICON_THEME)) { + this.restoreProductIconTheme(); } if (this.currentColorTheme) { let hasColorChanges = false; - if (e.affectsConfiguration(CUSTOM_WORKBENCH_COLORS_SETTING)) { - this.currentColorTheme.setCustomColors(this.colorCustomizations); + if (e.affectsConfiguration(ThemeSettings.COLOR_CUSTOMIZATIONS)) { + this.currentColorTheme.setCustomColors(this.settings.colorCustomizations); hasColorChanges = true; } - if (e.affectsConfiguration(CUSTOM_EDITOR_COLORS_SETTING)) { - this.currentColorTheme.setCustomTokenColors(this.tokenColorCustomizations); + if (e.affectsConfiguration(ThemeSettings.TOKEN_COLOR_CUSTOMIZATIONS)) { + this.currentColorTheme.setCustomTokenColors(this.settings.tokenColorCustomizations); hasColorChanges = true; } - if (e.affectsConfiguration(CUSTOM_EDITOR_TOKENSTYLES_SETTING)) { - this.currentColorTheme.setCustomTokenStyleRules(this.tokenStylesCustomizations); + if (e.affectsConfiguration(ThemeSettings.TOKEN_COLOR_CUSTOMIZATIONS_EXPERIMENTAL)) { + this.currentColorTheme.setCustomTokenStyleRules(this.settings.tokenStylesCustomizations); hasColorChanges = true; } if (hasColorChanges) { @@ -385,11 +307,11 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } private getPreferredColorScheme(): ThemeType | undefined { - let detectHCThemeSetting = this.configurationService.getValue(DETECT_HC_SETTING); + const detectHCThemeSetting = this.configurationService.getValue(ThemeSettings.DETECT_HC); if (this.environmentService.configuration.highContrast && detectHCThemeSetting) { return HIGH_CONTRAST; } - if (this.configurationService.getValue(DETECT_COLOR_SCHEME_SETTING)) { + if (this.configurationService.getValue(ThemeSettings.DETECT_COLOR_SCHEME)) { if (window.matchMedia(`(prefers-color-scheme: light)`).matches) { return LIGHT; } else if (window.matchMedia(`(prefers-color-scheme: dark)`).matches) { @@ -399,11 +321,11 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return undefined; } - private async applyPreferredColorTheme(type: ThemeType): Promise { - const settingId = type === DARK ? PREFERRED_DARK_THEME_SETTING : type === LIGHT ? PREFERRED_LIGHT_THEME_SETTING : PREFERRED_HC_THEME_SETTING; + private async applyPreferredColorTheme(type: ThemeType): Promise { + const settingId = type === DARK ? ThemeSettings.PREFERRED_DARK_THEME : type === LIGHT ? ThemeSettings.PREFERRED_LIGHT_THEME : ThemeSettings.PREFERRED_HC_THEME; const themeSettingId = this.configurationService.getValue(settingId); if (themeSettingId) { - const theme = await this.colorThemeStore.findThemeDataBySettingsId(themeSettingId, undefined); + const theme = await this.colorThemeRegistry.findThemeBySettingsId(themeSettingId, undefined); if (theme) { return this.setColorTheme(theme.id, 'auto'); } @@ -411,29 +333,25 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return null; } - public getColorTheme(): IColorTheme { + public getColorTheme(): IWorkbenchColorTheme { return this.currentColorTheme; } - public getColorThemes(): Promise { - return this.colorThemeStore.getColorThemes(); + public getColorThemes(): Promise { + return this.colorThemeRegistry.getThemes(); } - public getTheme(): ITheme { - return this.getColorTheme(); - } - - public setColorTheme(themeId: string | undefined, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { + public setColorTheme(themeId: string | undefined, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { if (!themeId) { return Promise.resolve(null); } if (themeId === this.currentColorTheme.id && this.currentColorTheme.isLoaded) { - return this.writeColorThemeConfiguration(settingsTarget); + return this.settings.setColorTheme(this.currentColorTheme, settingsTarget); } themeId = validateThemeId(themeId); // migrate theme ids - return this.colorThemeStore.findThemeData(themeId, DEFAULT_THEME_ID).then(themeData => { + return this.colorThemeRegistry.findThemeById(themeId, DEFAULT_COLOR_THEME_ID).then(themeData => { if (!themeData) { return null; } @@ -442,15 +360,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.currentColorTheme.clearCaches(); // the loaded theme is identical to the perisisted theme. Don't need to send an event. this.currentColorTheme = themeData; - themeData.setCustomColors(this.colorCustomizations); - themeData.setCustomTokenColors(this.tokenColorCustomizations); - themeData.setCustomTokenStyleRules(this.tokenStylesCustomizations); + themeData.setCustomizations(this.settings); return Promise.resolve(themeData); } - themeData.setCustomColors(this.colorCustomizations); - themeData.setCustomTokenColors(this.tokenColorCustomizations); - themeData.setCustomTokenStyleRules(this.tokenStylesCustomizations); - this.updateDynamicCSSRules(themeData); + themeData.setCustomizations(this.settings); return this.applyTheme(themeData, settingsTarget); }, error => { return Promise.reject(new Error(nls.localize('error.cannotloadtheme', "Unable to load {0}: {1}", themeData.location!.toString(), error.message))); @@ -460,25 +373,24 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { private async reloadCurrentColorTheme() { await this.currentColorTheme.reload(this.extensionResourceLoaderService); - this.currentColorTheme.setCustomColors(this.colorCustomizations); - this.currentColorTheme.setCustomTokenColors(this.tokenColorCustomizations); - this.currentColorTheme.setCustomTokenStyleRules(this.tokenStylesCustomizations); - this.updateDynamicCSSRules(this.currentColorTheme); + this.currentColorTheme.setCustomizations(this.settings); this.applyTheme(this.currentColorTheme, undefined, false); } - public restoreColorTheme() { - let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); - if (colorThemeSetting !== this.currentColorTheme.settingsId) { - this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, undefined).then(theme => { - if (theme) { - this.setColorTheme(theme.id, undefined); - } - }); + public async restoreColorTheme(): Promise { + const settingId = this.settings.colorTheme; + const theme = await this.colorThemeRegistry.findThemeBySettingsId(settingId); + if (theme) { + if (settingId !== this.currentColorTheme.settingsId) { + await this.setColorTheme(theme.id, undefined); + } + return true; } + return false; } - private updateDynamicCSSRules(themeData: ITheme) { + + private updateDynamicCSSRules(themeData: IColorTheme) { const cssRules = new Set(); const ruleCollector = { addRule: (rule: string) => { @@ -491,7 +403,9 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { _applyRules([...cssRules].join('\n'), colorThemeRulesClassName); } - private applyTheme(newTheme: ColorThemeData, settingsTarget: ConfigurationTarget | undefined | 'auto', silent = false): Promise { + private applyTheme(newTheme: ColorThemeData, settingsTarget: ConfigurationTarget | undefined | 'auto', silent = false): Promise { + this.updateDynamicCSSRules(newTheme); + if (this.currentColorTheme.id) { removeClasses(this.container, this.currentColorTheme.id); } else { @@ -501,19 +415,11 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.currentColorTheme.clearCaches(); this.currentColorTheme = newTheme; - if (!this.themingParticipantChangeListener) { - this.themingParticipantChangeListener = themingRegistry.onThemingParticipantAdded(_ => this.updateDynamicCSSRules(this.currentColorTheme)); + if (!this.colorThemingParticipantChangeListener) { + this.colorThemingParticipantChangeListener = themingRegistry.onThemingParticipantAdded(_ => this.updateDynamicCSSRules(this.currentColorTheme)); } - if (this.fileService && !resources.isEqual(newTheme.location, this.watchedColorThemeLocation)) { - dispose(this.watchedColorThemeDisposable); - this.watchedColorThemeLocation = undefined; - - if (newTheme.location && (newTheme.watch || !!this.environmentService.extensionDevelopmentLocationURI)) { - this.watchedColorThemeLocation = newTheme.location; - this.watchedColorThemeDisposable = this.fileService.watch(newTheme.location); - } - } + this.colorThemeWatcher.update(newTheme); this.sendTelemetry(newTheme.id, newTheme.extensionData, 'color'); @@ -525,23 +431,17 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { // remember theme data for a quick restore if (newTheme.isLoaded) { - this.storageService.store(PERSISTED_THEME_STORAGE_KEY, newTheme.toStorageData(), StorageScope.GLOBAL); + newTheme.toStorage(this.storageService); } - return this.writeColorThemeConfiguration(settingsTarget); + return this.settings.setColorTheme(this.currentColorTheme, settingsTarget); } - private writeColorThemeConfiguration(settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { - if (!types.isUndefinedOrNull(settingsTarget)) { - return this.writeConfiguration(COLOR_THEME_SETTING, this.currentColorTheme.settingsId, settingsTarget).then(_ => this.currentColorTheme); - } - return Promise.resolve(this.currentColorTheme); - } private themeExtensionsActivated = new Map(); private sendTelemetry(themeId: string, themeData: ExtensionData | undefined, themeType: string) { if (themeData) { - let key = themeType + themeData.extensionId; + const key = themeType + themeData.extensionId; if (!this.themeExtensionsActivated.get(key)) { type ActivatePluginClassification = { id: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; @@ -569,63 +469,61 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } } - public getFileIconThemes(): Promise { - return this.iconThemeStore.getFileIconThemes(); + public getFileIconThemes(): Promise { + return this.fileIconThemeRegistry.getThemes(); } public getFileIconTheme() { - return this.currentIconTheme; + return this.currentFileIconTheme; } - public getIconTheme() { - return this.currentIconTheme; + public get onDidFileIconThemeChange(): Event { + return this.onFileIconThemeChange.event; } - public setFileIconTheme(iconTheme: string | undefined, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { + + public async setFileIconTheme(iconTheme: string | undefined, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { iconTheme = iconTheme || ''; - if (iconTheme === this.currentIconTheme.id && this.currentIconTheme.isLoaded) { - return this.writeFileIconConfiguration(settingsTarget); + if (iconTheme === this.currentFileIconTheme.id && this.currentFileIconTheme.isLoaded) { + await this.settings.setFileIconTheme(this.currentFileIconTheme, settingsTarget); + return this.currentFileIconTheme; } - let onApply = (newIconTheme: FileIconThemeData) => { - this.doSetFileIconTheme(newIconTheme); - // remember theme data for a quick restore - if (newIconTheme.isLoaded && (!newIconTheme.location || !getRemoteAuthority(newIconTheme.location))) { - this.storageService.store(PERSISTED_ICON_THEME_STORAGE_KEY, newIconTheme.toStorageData(), StorageScope.GLOBAL); - } + const newThemeData = (await this.fileIconThemeRegistry.findThemeById(iconTheme)) || FileIconThemeData.noIconTheme; + await newThemeData.ensureLoaded(this.fileService); - return this.writeFileIconConfiguration(settingsTarget); - }; + this.applyAndSetFileIconTheme(newThemeData); - return this.iconThemeStore.findThemeData(iconTheme).then(data => { - const iconThemeData = data || FileIconThemeData.noIconTheme(); - return iconThemeData.ensureLoaded(this.fileService).then(_ => { - return _applyIconTheme(iconThemeData, onApply); - }); - }); + // remember theme data for a quick restore + if (newThemeData.isLoaded && (!newThemeData.location || !getRemoteAuthority(newThemeData.location))) { + newThemeData.toStorage(this.storageService); + } + await this.settings.setFileIconTheme(this.currentFileIconTheme, settingsTarget); + + return newThemeData; } private async reloadCurrentFileIconTheme() { - await this.currentIconTheme.reload(this.fileService); - _applyIconTheme(this.currentIconTheme, () => { - this.doSetFileIconTheme(this.currentIconTheme); - return Promise.resolve(this.currentIconTheme); - }); + await this.currentFileIconTheme.reload(this.fileService); + this.applyAndSetFileIconTheme(this.currentFileIconTheme); } - public restoreFileIconTheme() { - let fileIconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING); - if (fileIconThemeSetting !== this.currentIconTheme.settingsId) { - this.iconThemeStore.findThemeBySettingsId(fileIconThemeSetting).then(theme => { - if (theme) { - this.setFileIconTheme(theme.id, undefined); - } - }); + public async restoreFileIconTheme(): Promise { + const settingId = this.settings.fileIconTheme; + const theme = await this.fileIconThemeRegistry.findThemeBySettingsId(settingId); + if (theme) { + if (settingId !== this.currentFileIconTheme.settingsId) { + await this.setFileIconTheme(theme.id, undefined); + } + return true; } + return false; } - private doSetFileIconTheme(iconThemeData: FileIconThemeData): void { - this.currentIconTheme = iconThemeData; + private applyAndSetFileIconTheme(iconThemeData: FileIconThemeData): void { + this.currentFileIconTheme = iconThemeData; + + _applyRules(iconThemeData.styleSheetContent!, fileIconThemeRulesClassName); if (iconThemeData.id) { addClasses(this.container, fileIconsEnabledClass); @@ -633,57 +531,78 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { removeClasses(this.container, fileIconsEnabledClass); } - if (this.fileService && !resources.isEqual(iconThemeData.location, this.watchedIconThemeLocation)) { - dispose(this.watchedIconThemeDisposable); - this.watchedIconThemeLocation = undefined; - - if (iconThemeData.location && (iconThemeData.watch || !!this.environmentService.extensionDevelopmentLocationURI)) { - this.watchedIconThemeLocation = iconThemeData.location; - this.watchedIconThemeDisposable = this.fileService.watch(iconThemeData.location); - } - } + this.fileIconThemeWatcher.update(iconThemeData); if (iconThemeData.id) { this.sendTelemetry(iconThemeData.id, iconThemeData.extensionData, 'fileIcon'); } - this.onFileIconThemeChange.fire(this.currentIconTheme); + this.onFileIconThemeChange.fire(this.currentFileIconTheme); } - private writeFileIconConfiguration(settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { - if (!types.isUndefinedOrNull(settingsTarget)) { - return this.writeConfiguration(ICON_THEME_SETTING, this.currentIconTheme.settingsId, settingsTarget).then(_ => this.currentIconTheme); - } - return Promise.resolve(this.currentIconTheme); + public getProductIconThemes(): Promise { + return this.productIconThemeRegistry.getThemes(); } - public writeConfiguration(key: string, value: any, settingsTarget: ConfigurationTarget | 'auto'): Promise { - let settings = this.configurationService.inspect(key); - if (settingsTarget === 'auto') { - if (!types.isUndefined(settings.workspaceFolderValue)) { - settingsTarget = ConfigurationTarget.WORKSPACE_FOLDER; - } else if (!types.isUndefined(settings.workspaceValue)) { - settingsTarget = ConfigurationTarget.WORKSPACE; - } else { - settingsTarget = ConfigurationTarget.USER; - } + public getProductIconTheme() { + return this.currentProductIconTheme; + } + + public get onDidProductIconThemeChange(): Event { + return this.onProductIconThemeChange.event; + } + + public async setProductIconTheme(iconTheme: string | undefined, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { + iconTheme = iconTheme || ''; + if (iconTheme === this.currentProductIconTheme.id && this.currentProductIconTheme.isLoaded) { + await this.settings.setProductIconTheme(this.currentProductIconTheme, settingsTarget); + return this.currentProductIconTheme; } - if (settingsTarget === ConfigurationTarget.USER) { - if (value === settings.userValue) { - return Promise.resolve(undefined); // nothing to do - } else if (value === settings.defaultValue) { - if (types.isUndefined(settings.userValue)) { - return Promise.resolve(undefined); // nothing to do - } - value = undefined; // remove configuration from user settings - } - } else if (settingsTarget === ConfigurationTarget.WORKSPACE || settingsTarget === ConfigurationTarget.WORKSPACE_FOLDER) { - if (value === settings.value) { - return Promise.resolve(undefined); // nothing to do - } + const newThemeData = await this.productIconThemeRegistry.findThemeById(iconTheme) || ProductIconThemeData.defaultTheme; + await newThemeData.ensureLoaded(this.fileService); + + this.applyAndSetProductIconTheme(newThemeData); + + // remember theme data for a quick restore + if (newThemeData.isLoaded && (!newThemeData.location || !getRemoteAuthority(newThemeData.location))) { + newThemeData.toStorage(this.storageService); } - return this.configurationService.updateValue(key, value, settingsTarget); + await this.settings.setProductIconTheme(this.currentProductIconTheme, settingsTarget); + + return newThemeData; + } + + private async reloadCurrentProductIconTheme() { + await this.currentProductIconTheme.reload(this.fileService); + this.applyAndSetProductIconTheme(this.currentProductIconTheme); + } + + public async restoreProductIconTheme(): Promise { + const settingId = this.settings.productIconTheme; + const theme = await this.productIconThemeRegistry.findThemeBySettingsId(settingId); + if (theme) { + if (settingId !== this.currentProductIconTheme.settingsId) { + await this.setProductIconTheme(theme.id, undefined); + } + return true; + } + return false; + } + + private applyAndSetProductIconTheme(iconThemeData: ProductIconThemeData): void { + + this.currentProductIconTheme = iconThemeData; + + _applyRules(iconThemeData.styleSheetContent!, productIconThemeRulesClassName); + + this.productIconThemeWatcher.update(iconThemeData); + + if (iconThemeData.id) { + this.sendTelemetry(iconThemeData.id, iconThemeData.extensionData, 'productIcon'); + } + this.onProductIconThemeChange.fire(this.currentProductIconTheme); + } private getBaseThemeFromContainer() { @@ -697,15 +616,43 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } } -function _applyIconTheme(data: FileIconThemeData, onApply: (theme: FileIconThemeData) => Promise): Promise { - _applyRules(data.styleSheetContent!, iconThemeRulesClassName); - return onApply(data); +class ThemeFileWatcher { + + private inExtensionDevelopment: boolean; + private watchedLocation: URI | undefined; + private watcherDisposable: IDisposable | undefined; + private fileChangeListener: IDisposable | undefined; + + constructor(private fileService: IFileService, environmentService: IWorkbenchEnvironmentService, private onUpdate: () => void) { + this.inExtensionDevelopment = !!environmentService.extensionDevelopmentLocationURI; + } + + update(theme: { location?: URI, watch?: boolean; }) { + if (!resources.isEqual(theme.location, this.watchedLocation)) { + this.dispose(); + if (theme.location && (theme.watch || this.inExtensionDevelopment)) { + this.watchedLocation = theme.location; + this.watcherDisposable = this.fileService.watch(theme.location); + this.fileService.onDidFilesChange(e => { + if (this.watchedLocation && e.contains(this.watchedLocation, FileChangeType.UPDATED)) { + this.onUpdate(); + } + }); + } + } + } + + dispose() { + this.watcherDisposable = dispose(this.watcherDisposable); + this.fileChangeListener = dispose(this.fileChangeListener); + this.watchedLocation = undefined; + } } function _applyRules(styleSheetContent: string, rulesClassName: string) { - let themeStyles = document.head.getElementsByClassName(rulesClassName); + const themeStyles = document.head.getElementsByClassName(rulesClassName); if (themeStyles.length === 0) { - let elStyle = document.createElement('style'); + const elStyle = document.createElement('style'); elStyle.type = 'text/css'; elStyle.className = rulesClassName; elStyle.innerHTML = styleSheetContent; @@ -717,127 +664,6 @@ function _applyRules(styleSheetContent: string, rulesClassName: string) { registerColorThemeSchemas(); registerFileIconThemeSchemas(); - -// Configuration: Themes -const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); - -const colorThemeSettingEnum: string[] = []; -const colorThemeSettingEnumDescriptions: string[] = []; - -const colorThemeSettingSchema: IConfigurationPropertySchema = { - type: 'string', - description: nls.localize('colorTheme', "Specifies the color theme used in the workbench."), - default: DEFAULT_THEME_SETTING_VALUE, - enum: colorThemeSettingEnum, - enumDescriptions: colorThemeSettingEnumDescriptions, - errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), -}; -const preferredDarkThemeSettingSchema: IConfigurationPropertySchema = { - type: 'string', - description: nls.localize('preferredDarkColorTheme', 'Specifies the preferred color theme for dark OS appearance when \'{0}\' is enabled.', DETECT_COLOR_SCHEME_SETTING), - default: DEFAULT_THEME_DARK_SETTING_VALUE, - enum: colorThemeSettingEnum, - enumDescriptions: colorThemeSettingEnumDescriptions, - errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), -}; -const preferredLightThemeSettingSchema: IConfigurationPropertySchema = { - type: 'string', - description: nls.localize('preferredLightColorTheme', 'Specifies the preferred color theme for light OS appearance when \'{0}\' is enabled.', DETECT_COLOR_SCHEME_SETTING), - default: DEFAULT_THEME_LIGHT_SETTING_VALUE, - enum: colorThemeSettingEnum, - enumDescriptions: colorThemeSettingEnumDescriptions, - errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), -}; -const preferredHCThemeSettingSchema: IConfigurationPropertySchema = { - type: 'string', - description: nls.localize('preferredHCColorTheme', 'Specifies the preferred color theme used in high contrast mode when \'{0}\' is enabled.', DETECT_HC_SETTING), - default: DEFAULT_THEME_HC_SETTING_VALUE, - enum: colorThemeSettingEnum, - enumDescriptions: colorThemeSettingEnumDescriptions, - errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), -}; -const detectColorSchemeSettingSchema: IConfigurationPropertySchema = { - type: 'boolean', - description: nls.localize('detectColorScheme', 'If set, automatically switch to the preferred color theme based on the OS appearance.'), - default: false -}; - -const iconThemeSettingSchema: IConfigurationPropertySchema = { - type: ['string', 'null'], - default: DEFAULT_ICON_THEME_SETTING_VALUE, - description: nls.localize('iconTheme', "Specifies the icon theme used in the workbench or 'null' to not show any file icons."), - enum: [null], - enumDescriptions: [nls.localize('noIconThemeDesc', 'No file icons')], - errorMessage: nls.localize('iconThemeError', "File icon theme is unknown or not installed.") -}; -const colorCustomizationsSchema: IConfigurationPropertySchema = { - type: 'object', - description: nls.localize('workbenchColors', "Overrides colors from the currently selected color theme."), - allOf: [{ $ref: workbenchColorsSchemaId }], - default: {}, - defaultSnippets: [{ - body: { - } - }] -}; - -const themeSettingsConfiguration: IConfigurationNode = { - id: 'workbench', - order: 7.1, - type: 'object', - properties: { - [COLOR_THEME_SETTING]: colorThemeSettingSchema, - [PREFERRED_DARK_THEME_SETTING]: preferredDarkThemeSettingSchema, - [PREFERRED_LIGHT_THEME_SETTING]: preferredLightThemeSettingSchema, - [PREFERRED_HC_THEME_SETTING]: preferredHCThemeSettingSchema, - [DETECT_COLOR_SCHEME_SETTING]: detectColorSchemeSettingSchema, - [ICON_THEME_SETTING]: iconThemeSettingSchema, - [CUSTOM_WORKBENCH_COLORS_SETTING]: colorCustomizationsSchema - } -}; -configurationRegistry.registerConfiguration(themeSettingsConfiguration); - -function tokenGroupSettings(description: string): IJSONSchema { - return { - description, - $ref: textmateColorGroupSchemaId - }; -} - -const tokenColorSchema: IJSONSchema = { - properties: { - comments: tokenGroupSettings(nls.localize('editorColors.comments', "Sets the colors and styles for comments")), - strings: tokenGroupSettings(nls.localize('editorColors.strings', "Sets the colors and styles for strings literals.")), - keywords: tokenGroupSettings(nls.localize('editorColors.keywords', "Sets the colors and styles for keywords.")), - numbers: tokenGroupSettings(nls.localize('editorColors.numbers', "Sets the colors and styles for number literals.")), - types: tokenGroupSettings(nls.localize('editorColors.types', "Sets the colors and styles for type declarations and references.")), - functions: tokenGroupSettings(nls.localize('editorColors.functions', "Sets the colors and styles for functions declarations and references.")), - variables: tokenGroupSettings(nls.localize('editorColors.variables', "Sets the colors and styles for variables declarations and references.")), - textMateRules: { - description: nls.localize('editorColors.textMateRules', 'Sets colors and styles using textmate theming rules (advanced).'), - $ref: textmateColorsSchemaId - } - } -}; -const tokenColorCustomizationSchema: IConfigurationPropertySchema = { - description: nls.localize('editorColors', "Overrides editor colors and font style from the currently selected color theme."), - default: {}, - allOf: [tokenColorSchema] -}; -const experimentalTokenStylingCustomizationSchema: IConfigurationPropertySchema = { - description: nls.localize('editorColorsTokenStyles', "Overrides token color and styles from the currently selected color theme."), - default: {}, - allOf: [{ $ref: tokenStylingSchemaId }] -}; -const tokenColorCustomizationConfiguration: IConfigurationNode = { - id: 'editor', - order: 7.2, - type: 'object', - properties: { - [CUSTOM_EDITOR_COLORS_SETTING]: tokenColorCustomizationSchema, - [CUSTOM_EDITOR_TOKENSTYLES_SETTING]: experimentalTokenStylingCustomizationSchema - } -}; -configurationRegistry.registerConfiguration(tokenColorCustomizationConfiguration); +registerProductIconThemeSchemas(); registerSingleton(IWorkbenchThemeService, WorkbenchThemeService); diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index 0ce389ce7e9..65634c5e67e 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -6,11 +6,12 @@ import { basename } from 'vs/base/common/path'; import * as Json from 'vs/base/common/json'; import { Color } from 'vs/base/common/color'; -import { ExtensionData, ITokenColorCustomizations, ITextMateThemingRule, IColorTheme, IColorMap, IThemeExtensionPoint, VS_LIGHT_THEME, VS_HC_THEME, IColorCustomizations, IExperimentalTokenStyleCustomizations, ITokenColorizationSetting } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ExtensionData, ITokenColorCustomizations, ITextMateThemingRule, IWorkbenchColorTheme, IColorMap, IThemeExtensionPoint, VS_LIGHT_THEME, VS_HC_THEME, IColorCustomizations, IExperimentalTokenStyleCustomizations, ITokenColorizationSetting } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { convertSettings } from 'vs/workbench/services/themes/common/themeCompatibility'; import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; import * as objects from 'vs/base/common/objects'; +import * as arrays from 'vs/base/common/arrays'; import * as resources from 'vs/base/common/resources'; import { Extensions as ColorRegistryExtensions, IColorRegistry, ColorIdentifier, editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; import { ThemeType, ITokenStyle } from 'vs/platform/theme/common/themeService'; @@ -19,10 +20,12 @@ import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; import { URI } from 'vs/base/common/uri'; import { parse as parsePList } from 'vs/workbench/services/themes/common/plistParser'; import { startsWith } from 'vs/base/common/strings'; -import { TokenStyle, ProbeScope, TokenStylingRule, getTokenClassificationRegistry, TokenStyleValue, TokenStyleData } from 'vs/platform/theme/common/tokenClassificationRegistry'; +import { TokenStyle, ProbeScope, TokenStylingRule, getTokenClassificationRegistry, TokenStyleValue, TokenStyleData, parseClassifierString } from 'vs/platform/theme/common/tokenClassificationRegistry'; import { MatcherWithPriority, Matcher, createMatchers } from 'vs/workbench/services/themes/common/textMateScopeMatcher'; import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; import { CharCode } from 'vs/base/common/charCode'; +import { StorageScope, IStorageService } from 'vs/platform/storage/common/storage'; +import { ThemeConfiguration } from 'vs/workbench/services/themes/common/themeConfiguration'; let colorRegistry = Registry.as(ColorRegistryExtensions.ColorContribution); @@ -42,7 +45,11 @@ const tokenGroupToScopesMap = { export type TokenStyleDefinition = TokenStylingRule | ProbeScope[] | TokenStyleValue; export type TokenStyleDefinitions = { [P in keyof TokenStyleData]?: TokenStyleDefinition | undefined }; -export class ColorThemeData implements IColorTheme { +export type TextMateThemingRuleDefinitions = { [P in keyof TokenStyleData]?: ITextMateThemingRule | undefined; } & { scope?: ProbeScope; }; + +const PERSISTED_THEME_STORAGE_KEY = 'colorThemeData'; + +export class ColorThemeData implements IWorkbenchColorTheme { id: string; label: string; @@ -53,12 +60,15 @@ export class ColorThemeData implements IColorTheme { watch?: boolean; extensionData?: ExtensionData; + private themeSemanticHighlighting: boolean | undefined; + private customSemanticHighlighting: boolean | undefined; + private themeTokenColors: ITextMateThemingRule[] = []; private customTokenColors: ITextMateThemingRule[] = []; private colorMap: IColorMap = {}; private customColorMap: IColorMap = {}; - private tokenStylingRules: TokenStylingRule[] | undefined = undefined; // undefined if the theme has no tokenStylingRules section + private tokenStylingRules: TokenStylingRule[] = []; private customTokenStylingRules: TokenStylingRule[] = []; private themeTokenScopeMatchers: Matcher[] | undefined; @@ -74,6 +84,10 @@ export class ColorThemeData implements IColorTheme { this.isLoaded = false; } + get semanticHighlighting(): boolean { + return this.customSemanticHighlighting !== undefined ? this.customSemanticHighlighting : !!this.themeSemanticHighlighting; + } + get tokenColors(): ITextMateThemingRule[] { if (!this.textMateThemingRules) { const result: ITextMateThemingRule[] = []; @@ -124,7 +138,7 @@ export class ColorThemeData implements IColorTheme { return color; } - public getTokenStyle(type: string, modifiers: string[], useDefault = true, definitions: TokenStyleDefinitions = {}): TokenStyle | undefined { + private getTokenStyle(type: string, modifiers: string[], language: string, useDefault = true, definitions: TokenStyleDefinitions = {}): TokenStyle | undefined { let result: any = { foreground: undefined, bold: undefined, @@ -156,9 +170,30 @@ export class ColorThemeData implements IColorTheme { } } } - if (this.tokenStylingRules === undefined) { + for (const rule of this.tokenStylingRules) { + const matchScore = rule.selector.match(type, modifiers, language); + if (matchScore >= 0) { + _processStyle(matchScore, rule.style, rule); + } + } + for (const rule of this.customTokenStylingRules) { + const matchScore = rule.selector.match(type, modifiers, language); + if (matchScore >= 0) { + _processStyle(matchScore, rule.style, rule); + } + } + let hasUndefinedStyleProperty = false; + for (let k in score) { + const key = k as keyof TokenStyle; + if (score[key] === -1) { + hasUndefinedStyleProperty = true; + } else { + score[key] = Number.MAX_VALUE; // set it to the max, so it won't be replaced by a default + } + } + if (hasUndefinedStyleProperty) { for (const rule of tokenClassificationRegistry.getTokenStylingDefaultRules()) { - const matchScore = rule.selector.match(type, modifiers); + const matchScore = rule.selector.match(type, modifiers, language); if (matchScore >= 0) { let style: TokenStyle | undefined; if (rule.defaults.scopesToProbe) { @@ -176,19 +211,6 @@ export class ColorThemeData implements IColorTheme { } } } - } else { - for (const rule of this.tokenStylingRules) { - const matchScore = rule.selector.match(type, modifiers); - if (matchScore >= 0) { - _processStyle(matchScore, rule.style, rule); - } - } - } - for (const rule of this.customTokenStylingRules) { - const matchScore = rule.selector.match(type, modifiers); - if (matchScore >= 0) { - _processStyle(matchScore, rule.style, rule); - } } return TokenStyle.fromData(result); @@ -197,12 +219,12 @@ export class ColorThemeData implements IColorTheme { /** * @param tokenStyleValue Resolve a tokenStyleValue in the context of a theme */ - private resolveTokenStyleValue(tokenStyleValue: TokenStyleValue | undefined): TokenStyle | undefined { + public resolveTokenStyleValue(tokenStyleValue: TokenStyleValue | undefined): TokenStyle | undefined { if (tokenStyleValue === undefined) { return undefined; } else if (typeof tokenStyleValue === 'string') { - const [type, ...modifiers] = tokenStyleValue.split('.'); - return this.getTokenStyle(type, modifiers); + const { type, modifiers, language } = parseClassifierString(tokenStyleValue, ''); + return this.getTokenStyle(type, modifiers, language); } else if (typeof tokenStyleValue === 'object') { return tokenStyleValue; } @@ -218,16 +240,13 @@ export class ColorThemeData implements IColorTheme { index.add(rule.settings.background); }); - if (this.tokenStylingRules) { - this.tokenStylingRules.forEach(r => index.add(r.style.foreground)); - } else { - tokenClassificationRegistry.getTokenStylingDefaultRules().forEach(r => { - const defaultColor = r.defaults[this.type]; - if (defaultColor && typeof defaultColor === 'object') { - index.add(defaultColor.foreground); - } - }); - } + this.tokenStylingRules.forEach(r => index.add(r.style.foreground)); + tokenClassificationRegistry.getTokenStylingDefaultRules().forEach(r => { + const defaultColor = r.defaults[this.type]; + if (defaultColor && typeof defaultColor === 'object') { + index.add(defaultColor.foreground); + } + }); this.customTokenStylingRules.forEach(r => index.add(r.style.foreground)); this.tokenColorIndex = index; @@ -239,8 +258,9 @@ export class ColorThemeData implements IColorTheme { return this.getTokenColorIndex().asArray(); } - public getTokenStyleMetadata(type: string, modifiers: string[], useDefault = true, definitions: TokenStyleDefinitions = {}): ITokenStyle | undefined { - const style = this.getTokenStyle(type, modifiers, useDefault, definitions); + public getTokenStyleMetadata(typeWithLanguage: string, modifiers: string[], defaultLanguage: string, useDefault = true, definitions: TokenStyleDefinitions = {}): ITokenStyle | undefined { + const { type, language } = parseClassifierString(typeWithLanguage, defaultLanguage); + let style = this.getTokenStyle(type, modifiers, language, useDefault, definitions); if (!style) { return undefined; } @@ -257,7 +277,7 @@ export class ColorThemeData implements IColorTheme { if (this.customTokenStylingRules.indexOf(rule) !== -1) { return 'setting'; } - if (this.tokenStylingRules && this.tokenStylingRules.indexOf(rule) !== -1) { + if (this.tokenStylingRules.indexOf(rule) !== -1) { return 'theme'; } return undefined; @@ -267,7 +287,8 @@ export class ColorThemeData implements IColorTheme { return colorRegistry.resolveDefaultColor(colorId, this); } - public resolveScopes(scopes: ProbeScope[]): TokenStyle | undefined { + + public resolveScopes(scopes: ProbeScope[], definitions?: TextMateThemingRuleDefinitions): TokenStyle | undefined { if (!this.themeTokenScopeMatchers) { this.themeTokenScopeMatchers = this.themeTokenColors.map(getScopeMatcher); @@ -281,17 +302,24 @@ export class ColorThemeData implements IColorTheme { let fontStyle: string | undefined = undefined; let foregroundScore = -1; let fontStyleScore = -1; + let fontStyleThemingRule: ITextMateThemingRule | undefined = undefined; + let foregroundThemingRule: ITextMateThemingRule | undefined = undefined; - function findTokenStyleForScopeInScopes(scopeMatchers: Matcher[], tokenColors: ITextMateThemingRule[]) { + function findTokenStyleForScopeInScopes(scopeMatchers: Matcher[], themingRules: ITextMateThemingRule[]) { for (let i = 0; i < scopeMatchers.length; i++) { const score = scopeMatchers[i](scope); if (score >= 0) { - const settings = tokenColors[i].settings; + const themingRule = themingRules[i]; + const settings = themingRules[i].settings; if (score >= foregroundScore && settings.foreground) { foreground = settings.foreground; + foregroundScore = score; + foregroundThemingRule = themingRule; } if (score >= fontStyleScore && types.isString(settings.fontStyle)) { fontStyle = settings.fontStyle; + fontStyleScore = score; + fontStyleThemingRule = themingRule; } } } @@ -299,6 +327,12 @@ export class ColorThemeData implements IColorTheme { findTokenStyleForScopeInScopes(this.themeTokenScopeMatchers, this.themeTokenColors); findTokenStyleForScopeInScopes(this.customTokenScopeMatchers, this.customTokenColors); if (foreground !== undefined || fontStyle !== undefined) { + if (definitions) { + definitions.foreground = foregroundThemingRule; + definitions.bold = definitions.italic = definitions.underline = fontStyleThemingRule; + definitions.scope = scope; + } + return TokenStyle.fromSettings(foreground, fontStyle); } } @@ -309,6 +343,12 @@ export class ColorThemeData implements IColorTheme { return this.customColorMap.hasOwnProperty(colorId) || this.colorMap.hasOwnProperty(colorId); } + public setCustomizations(settings: ThemeConfiguration) { + this.setCustomColors(settings.colorCustomizations); + this.setCustomTokenColors(settings.tokenColorCustomizations); + this.setCustomTokenStyleRules(settings.tokenStylesCustomizations); + } + public setCustomColors(colors: IColorCustomizations) { this.customColorMap = {}; this.overwriteCustomColors(colors); @@ -334,6 +374,7 @@ export class ColorThemeData implements IColorTheme { public setCustomTokenColors(customTokenColors: ITokenColorCustomizations) { this.customTokenColors = []; + this.customSemanticHighlighting = undefined; // first add the non-theme specific settings this.addCustomTokenColors(customTokenColors); @@ -385,6 +426,9 @@ export class ColorThemeData implements IColorTheme { } } } + if (customTokenColors.semanticHighlighting !== undefined) { + this.customSemanticHighlighting = customTokenColors.semanticHighlighting; + } } public ensureLoaded(extensionResourceLoaderService: IExtensionResourceLoaderService): Promise { @@ -405,13 +449,15 @@ export class ColorThemeData implements IColorTheme { const result = { colors: {}, textMateRules: [], - stylingRules: undefined + stylingRules: [], + semanticHighlighting: false }; return _loadColorTheme(extensionResourceLoaderService, this.location, result).then(_ => { this.isLoaded = true; this.tokenStylingRules = result.stylingRules; this.colorMap = result.colors; this.themeTokenColors = result.textMateRules; + this.themeSemanticHighlighting = result.semanticHighlighting; }); } @@ -422,26 +468,32 @@ export class ColorThemeData implements IColorTheme { this.customTokenScopeMatchers = undefined; } - toStorageData() { + toStorage(storageService: IStorageService) { let colorMapData: { [key: string]: string } = {}; for (let key in this.colorMap) { colorMapData[key] = Color.Format.CSS.formatHexA(this.colorMap[key], true); } // no need to persist custom colors, they will be taken from the settings - return JSON.stringify({ + const value = JSON.stringify({ id: this.id, label: this.label, settingsId: this.settingsId, selector: this.id.split(' ').join('.'), // to not break old clients themeTokenColors: this.themeTokenColors, + tokenStylingRules: this.tokenStylingRules.map(TokenStylingRule.toJSONObject), extensionData: this.extensionData, + themeSemanticHighlighting: this.themeSemanticHighlighting, colorMap: colorMapData, watch: this.watch }); + storageService.store(PERSISTED_THEME_STORAGE_KEY, value, StorageScope.GLOBAL); } hasEqualData(other: ColorThemeData) { - return objects.equals(this.colorMap, other.colorMap) && objects.equals(this.themeTokenColors, other.themeTokenColors); + return objects.equals(this.colorMap, other.colorMap) + && objects.equals(this.themeTokenColors, other.themeTokenColors) + && arrays.equals(this.tokenStylingRules, other.tokenStylingRules, TokenStylingRule.equals) + && this.themeSemanticHighlighting === other.themeSemanticHighlighting; } get baseTheme(): string { @@ -474,7 +526,11 @@ export class ColorThemeData implements IColorTheme { return themeData; } - static fromStorageData(input: string): ColorThemeData | undefined { + static fromStorageData(storageService: IStorageService): ColorThemeData | undefined { + const input = storageService.get(PERSISTED_THEME_STORAGE_KEY, StorageScope.GLOBAL); + if (!input) { + return undefined; + } try { let data = JSON.parse(input); let theme = new ColorThemeData('', '', ''); @@ -487,9 +543,20 @@ export class ColorThemeData implements IColorTheme { } break; case 'themeTokenColors': - case 'id': case 'label': case 'settingsId': case 'extensionData': case 'watch': + case 'id': case 'label': case 'settingsId': case 'extensionData': case 'watch': case 'themeSemanticHighlighting': (theme as any)[key] = data[key]; break; + case 'tokenStylingRules': + const rulesData = data[key]; + if (Array.isArray(rulesData)) { + for (let d of rulesData) { + const rule = TokenStylingRule.fromJSONObject(tokenClassificationRegistry, d); + if (rule) { + theme.tokenStylingRules.push(rule); + } + } + } + break; } } if (!theme.id || !theme.settingsId) { @@ -531,56 +598,60 @@ function toCSSSelector(extensionId: string, path: string) { return str; } -function _loadColorTheme(extensionResourceLoaderService: IExtensionResourceLoaderService, themeLocation: URI, result: { textMateRules: ITextMateThemingRule[], colors: IColorMap, stylingRules: TokenStylingRule[] | undefined }): Promise { +async function _loadColorTheme(extensionResourceLoaderService: IExtensionResourceLoaderService, themeLocation: URI, result: { textMateRules: ITextMateThemingRule[], colors: IColorMap, stylingRules: TokenStylingRule[], semanticHighlighting: boolean }): Promise { if (resources.extname(themeLocation) === '.json') { - return extensionResourceLoaderService.readExtensionResource(themeLocation).then(content => { - let errors: Json.ParseError[] = []; - let contentValue = Json.parse(content, errors); - if (errors.length > 0) { - return Promise.reject(new Error(nls.localize('error.cannotparsejson', "Problems parsing JSON theme file: {0}", errors.map(e => getParseErrorMessage(e.error)).join(', ')))); - } else if (Json.getNodeType(contentValue) !== 'object') { - return Promise.reject(new Error(nls.localize('error.invalidformat', "Invalid format for JSON theme file: Object expected."))); + const content = await extensionResourceLoaderService.readExtensionResource(themeLocation); + let errors: Json.ParseError[] = []; + let contentValue = Json.parse(content, errors); + if (errors.length > 0) { + return Promise.reject(new Error(nls.localize('error.cannotparsejson', "Problems parsing JSON theme file: {0}", errors.map(e => getParseErrorMessage(e.error)).join(', ')))); + } else if (Json.getNodeType(contentValue) !== 'object') { + return Promise.reject(new Error(nls.localize('error.invalidformat', "Invalid format for JSON theme file: Object expected."))); + } + if (contentValue.include) { + await _loadColorTheme(extensionResourceLoaderService, resources.joinPath(resources.dirname(themeLocation), contentValue.include), result); + } + if (Array.isArray(contentValue.settings)) { + convertSettings(contentValue.settings, result); + return null; + } + result.semanticHighlighting = result.semanticHighlighting || contentValue.semanticHighlighting; + let colors = contentValue.colors; + if (colors) { + if (typeof colors !== 'object') { + return Promise.reject(new Error(nls.localize({ key: 'error.invalidformat.colors', comment: ['{0} will be replaced by a path. Values in quotes should not be translated.'] }, "Problem parsing color theme file: {0}. Property 'colors' is not of type 'object'.", themeLocation.toString()))); } - let includeCompletes: Promise = Promise.resolve(null); - if (contentValue.include) { - includeCompletes = _loadColorTheme(extensionResourceLoaderService, resources.joinPath(resources.dirname(themeLocation), contentValue.include), result); + // new JSON color themes format + for (let colorId in colors) { + let colorHex = colors[colorId]; + if (typeof colorHex === 'string') { // ignore colors tht are null + result.colors[colorId] = Color.fromHex(colors[colorId]); + } } - return includeCompletes.then(_ => { - if (Array.isArray(contentValue.settings)) { - convertSettings(contentValue.settings, result); - return null; - } - let colors = contentValue.colors; - if (colors) { - if (typeof colors !== 'object') { - return Promise.reject(new Error(nls.localize({ key: 'error.invalidformat.colors', comment: ['{0} will be replaced by a path. Values in quotes should not be translated.'] }, "Problem parsing color theme file: {0}. Property 'colors' is not of type 'object'.", themeLocation.toString()))); - } - // new JSON color themes format - for (let colorId in colors) { - let colorHex = colors[colorId]; - if (typeof colorHex === 'string') { // ignore colors tht are null - result.colors[colorId] = Color.fromHex(colors[colorId]); - } + } + let tokenColors = contentValue.tokenColors; + if (tokenColors) { + if (Array.isArray(tokenColors)) { + result.textMateRules.push(...tokenColors); + } else if (typeof tokenColors === 'string') { + await _loadSyntaxTokens(extensionResourceLoaderService, resources.joinPath(resources.dirname(themeLocation), tokenColors), result); + } else { + return Promise.reject(new Error(nls.localize({ key: 'error.invalidformat.tokenColors', comment: ['{0} will be replaced by a path. Values in quotes should not be translated.'] }, "Problem parsing color theme file: {0}. Property 'tokenColors' should be either an array specifying colors or a path to a TextMate theme file", themeLocation.toString()))); + } + } + let semanticTokenColors = contentValue.semanticTokenColors; + if (semanticTokenColors && typeof semanticTokenColors === 'object') { + for (let key in semanticTokenColors) { + try { + const rule = readCustomTokenStyleRule(key, semanticTokenColors[key]); + if (rule) { + result.stylingRules.push(rule); } + } catch (e) { + return Promise.reject(new Error(nls.localize({ key: 'error.invalidformat.semanticTokenColors', comment: ['{0} will be replaced by a path. Values in quotes should not be translated.'] }, "Problem parsing color theme file: {0}. Property 'semanticTokenColors' conatains a invalid selector", themeLocation.toString()))); } - let tokenColors = contentValue.tokenColors; - if (tokenColors) { - if (Array.isArray(tokenColors)) { - result.textMateRules.push(...tokenColors); - return null; - } else if (typeof tokenColors === 'string') { - return _loadSyntaxTokens(extensionResourceLoaderService, resources.joinPath(resources.dirname(themeLocation), tokenColors), result); - } else { - return Promise.reject(new Error(nls.localize({ key: 'error.invalidformat.tokenColors', comment: ['{0} will be replaced by a path. Values in quotes should not be translated.'] }, "Problem parsing color theme file: {0}. Property 'tokenColors' should be either an array specifying colors or a path to a TextMate theme file", themeLocation.toString()))); - } - } - let tokenStylingRules = contentValue.tokenStylingRules; - if (tokenStylingRules && typeof tokenStylingRules === 'object') { - result.stylingRules = readCustomTokenStyleRules(tokenStylingRules, result.stylingRules); - } - return null; - }); - }); + } + } } else { return _loadSyntaxTokens(extensionResourceLoaderService, themeLocation, result); } @@ -642,7 +713,7 @@ function nameMatcher(identifers: string[], scope: ProbeScope): number { let lastScopeIndex = scope.length - 1; let lastIdentifierIndex = findInIdents(scope[lastScopeIndex--], identifers.length); if (lastIdentifierIndex >= 0) { - const score = (lastIdentifierIndex + 1) * 0x10000 + scope.length; + const score = (lastIdentifierIndex + 1) * 0x10000 + identifers[lastIdentifierIndex].length; while (lastScopeIndex >= 0) { lastIdentifierIndex = findInIdents(scope[lastScopeIndex--], lastIdentifierIndex); if (lastIdentifierIndex === -1) { @@ -692,22 +763,27 @@ function getScopeMatcher(rule: ITextMateThemingRule): Matcher { }; } - +function readCustomTokenStyleRule(selectorString: string, settings: ITokenColorizationSetting | string | undefined): TokenStylingRule | undefined { + const selector = tokenClassificationRegistry.parseTokenSelector(selectorString); + let style: TokenStyle | undefined; + if (typeof settings === 'string') { + style = TokenStyle.fromSettings(settings, undefined); + } else if (isTokenColorizationSetting(settings)) { + style = TokenStyle.fromSettings(settings.foreground, settings.fontStyle); + } + if (style) { + return { selector, style }; + } + return undefined; +} function readCustomTokenStyleRules(tokenStylingRuleSection: IExperimentalTokenStyleCustomizations, result: TokenStylingRule[] = []) { for (let key in tokenStylingRuleSection) { if (key[0] !== '[') { try { - const selector = tokenClassificationRegistry.parseTokenSelector(key); - const settings = tokenStylingRuleSection[key]; - let style: TokenStyle | undefined; - if (typeof settings === 'string') { - style = TokenStyle.fromSettings(settings, undefined); - } else if (isTokenColorizationSetting(settings)) { - style = TokenStyle.fromSettings(settings.foreground, settings.fontStyle); - } - if (style) { - result.push({ selector, style }); + const rule = readCustomTokenStyleRule(key, tokenStylingRuleSection[key]); + if (rule) { + result.push(rule); } } catch (e) { // invalid selector, ignore diff --git a/src/vs/workbench/services/themes/common/colorThemeSchema.ts b/src/vs/workbench/services/themes/common/colorThemeSchema.ts index 7f5d0f5c59a..18b1e596074 100644 --- a/src/vs/workbench/services/themes/common/colorThemeSchema.ts +++ b/src/vs/workbench/services/themes/common/colorThemeSchema.ts @@ -9,6 +9,7 @@ import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/plat import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { workbenchColorsSchemaId } from 'vs/platform/theme/common/colorRegistry'; +import { tokenStylingSchemaId } from 'vs/platform/theme/common/tokenClassificationRegistry'; let textMateScopes = [ 'comment', @@ -222,6 +223,15 @@ const colorThemeSchema: IJSONSchema = { $ref: textmateColorsSchemaId } ] + }, + semanticHighlighting: { + type: 'boolean', + description: nls.localize('schema.supportsSemanticHighlighting', 'Whether semantic highlighting should be enabled for this theme.') + }, + semanticTokenColors: { + type: 'object', + description: nls.localize('schema.semanticTokenColors', 'Colors for semantic tokens'), + $ref: tokenStylingSchemaId } } }; diff --git a/src/vs/workbench/services/themes/common/colorThemeStore.ts b/src/vs/workbench/services/themes/common/colorThemeStore.ts deleted file mode 100644 index 32dc2ad1d2f..00000000000 --- a/src/vs/workbench/services/themes/common/colorThemeStore.ts +++ /dev/null @@ -1,168 +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 nls from 'vs/nls'; - -import * as types from 'vs/base/common/types'; -import * as resources from 'vs/base/common/resources'; -import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { ExtensionData, IThemeExtensionPoint, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { ColorThemeData } from 'vs/workbench/services/themes/common/colorThemeData'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { Event, Emitter } from 'vs/base/common/event'; -import { URI } from 'vs/base/common/uri'; - -const themesExtPoint = ExtensionsRegistry.registerExtensionPoint({ - extensionPoint: 'themes', - jsonSchema: { - description: nls.localize('vscode.extension.contributes.themes', 'Contributes textmate color themes.'), - type: 'array', - items: { - type: 'object', - defaultSnippets: [{ body: { label: '${1:label}', id: '${2:id}', uiTheme: VS_DARK_THEME, path: './themes/${3:id}.tmTheme.' } }], - properties: { - id: { - description: nls.localize('vscode.extension.contributes.themes.id', 'Id of the icon theme as used in the user settings.'), - type: 'string' - }, - label: { - description: nls.localize('vscode.extension.contributes.themes.label', 'Label of the color theme as shown in the UI.'), - type: 'string' - }, - uiTheme: { - description: nls.localize('vscode.extension.contributes.themes.uiTheme', 'Base theme defining the colors around the editor: \'vs\' is the light color theme, \'vs-dark\' is the dark color theme. \'hc-black\' is the dark high contrast theme.'), - enum: [VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME] - }, - path: { - description: nls.localize('vscode.extension.contributes.themes.path', 'Path of the tmTheme file. The path is relative to the extension folder and is typically \'./themes/themeFile.tmTheme\'.'), - type: 'string' - } - }, - required: ['path', 'uiTheme'] - } - } -}); - -export interface ColorThemeChangeEvent { - themes: ColorThemeData[]; - added: ColorThemeData[]; -} - -export class ColorThemeStore { - - private extensionsColorThemes: ColorThemeData[]; - - private readonly onDidChangeEmitter = new Emitter(); - public readonly onDidChange: Event = this.onDidChangeEmitter.event; - - constructor(@IExtensionService private readonly extensionService: IExtensionService) { - this.extensionsColorThemes = []; - this.initialize(); - } - - private initialize() { - themesExtPoint.setHandler((extensions, delta) => { - const previousIds: { [key: string]: boolean } = {}; - const added: ColorThemeData[] = []; - for (const theme of this.extensionsColorThemes) { - previousIds[theme.id] = true; - } - this.extensionsColorThemes.length = 0; - for (let ext of extensions) { - let extensionData: ExtensionData = { - extensionId: ext.description.identifier.value, - extensionPublisher: ext.description.publisher, - extensionName: ext.description.name, - extensionIsBuiltin: ext.description.isBuiltin, - extensionLocation: ext.description.extensionLocation - }; - this.onThemes(extensionData, ext.value, ext.collector); - } - for (const theme of this.extensionsColorThemes) { - if (!previousIds[theme.id]) { - added.push(theme); - } - } - this.onDidChangeEmitter.fire({ themes: this.extensionsColorThemes, added }); - }); - } - - private onThemes(extensionData: ExtensionData, themes: IThemeExtensionPoint[], collector: ExtensionMessageCollector): void { - if (!Array.isArray(themes)) { - collector.error(nls.localize( - 'reqarray', - "Extension point `{0}` must be an array.", - themesExtPoint.name - )); - return; - } - themes.forEach(theme => { - if (!theme.path || !types.isString(theme.path)) { - collector.error(nls.localize( - 'reqpath', - "Expected string in `contributes.{0}.path`. Provided value: {1}", - themesExtPoint.name, - String(theme.path) - )); - return; - } - - const colorThemeLocation = resources.joinPath(extensionData.extensionLocation, theme.path); - if (!resources.isEqualOrParent(colorThemeLocation, extensionData.extensionLocation)) { - collector.warn(nls.localize('invalid.path.1', "Expected `contributes.{0}.path` ({1}) to be included inside extension's folder ({2}). This might make the extension non-portable.", themesExtPoint.name, colorThemeLocation.path, extensionData.extensionLocation.path)); - } - - let themeData = ColorThemeData.fromExtensionTheme(theme, colorThemeLocation, extensionData); - this.extensionsColorThemes.push(themeData); - }); - } - - public findThemeData(themeId: string, defaultId?: string): Promise { - return this.getColorThemes().then(allThemes => { - let defaultTheme: ColorThemeData | undefined = undefined; - for (let t of allThemes) { - if (t.id === themeId) { - return t; - } - if (t.id === defaultId) { - defaultTheme = t; - } - } - return defaultTheme; - }); - } - - public findThemeDataBySettingsId(settingsId: string, defaultId: string | undefined): Promise { - return this.getColorThemes().then(allThemes => { - let defaultTheme: ColorThemeData | undefined = undefined; - for (let t of allThemes) { - if (t.settingsId === settingsId) { - return t; - } - if (t.id === defaultId) { - defaultTheme = t; - } - } - return defaultTheme; - }); - } - - public findThemeDataByExtensionLocation(extLocation: URI | undefined): Promise { - if (extLocation) { - return this.getColorThemes().then(allThemes => { - return allThemes.filter(t => t.extensionData && resources.isEqual(t.extensionData.extensionLocation, extLocation)); - }); - } - return Promise.resolve([]); - - } - - public getColorThemes(): Promise { - return this.extensionService.whenInstalledExtensionsRegistered().then(_ => { - return this.extensionsColorThemes; - }); - } - -} diff --git a/src/vs/workbench/services/themes/common/fileIconThemeSchema.ts b/src/vs/workbench/services/themes/common/fileIconThemeSchema.ts index 1814eb94630..a90205aecfe 100644 --- a/src/vs/workbench/services/themes/common/fileIconThemeSchema.ts +++ b/src/vs/workbench/services/themes/common/fileIconThemeSchema.ts @@ -120,7 +120,7 @@ const schema: IJSONSchema = { properties: { path: { type: 'string', - description: nls.localize('schema.font-path', 'The font path, relative to the current icon theme file.'), + description: nls.localize('schema.font-path', 'The font path, relative to the current file icon theme file.'), }, format: { type: 'string', diff --git a/src/vs/workbench/services/themes/common/productIconThemeSchema.ts b/src/vs/workbench/services/themes/common/productIconThemeSchema.ts new file mode 100644 index 00000000000..92658504454 --- /dev/null +++ b/src/vs/workbench/services/themes/common/productIconThemeSchema.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. + *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; + +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; + + +const schemaId = 'vscode://schemas/product-icon-theme'; +const schema: IJSONSchema = { + type: 'object', + allowComments: true, + allowTrailingCommas: true, + properties: { + fonts: { + type: 'array', + description: nls.localize('schema.fonts', 'Fonts that are used in the icon definitions.'), + items: { + type: 'object', + properties: { + id: { + type: 'string', + description: nls.localize('schema.id', 'The ID of the font.') + }, + src: { + type: 'array', + description: nls.localize('schema.src', 'The location of the font.'), + items: { + type: 'object', + properties: { + path: { + type: 'string', + description: nls.localize('schema.font-path', 'The font path, relative to the current product icon theme file.'), + }, + format: { + type: 'string', + description: nls.localize('schema.font-format', 'The format of the font.') + } + }, + required: [ + 'path', + 'format' + ] + } + }, + weight: { + type: 'string', + description: nls.localize('schema.font-weight', 'The weight of the font.') + }, + style: { + type: 'string', + description: nls.localize('schema.font-sstyle', 'The style of the font.') + }, + size: { + type: 'string', + description: nls.localize('schema.font-size', 'The default size of the font.') + } + }, + required: [ + 'id', + 'src' + ] + } + }, + iconDefinitions: { + type: 'object', + description: nls.localize('schema.iconDefinitions', 'Assocation of icon name to a font character.'), + properties: getIconRegistry().getIconSchema().properties, + additionalProperties: false + } + } +}; + +export function registerProductIconThemeSchemas() { + let schemaRegistry = Registry.as(JSONExtensions.JSONContribution); + schemaRegistry.registerSchema(schemaId, schema); +} diff --git a/src/vs/workbench/services/themes/common/themeConfiguration.ts b/src/vs/workbench/services/themes/common/themeConfiguration.ts new file mode 100644 index 00000000000..23d689a2850 --- /dev/null +++ b/src/vs/workbench/services/themes/common/themeConfiguration.ts @@ -0,0 +1,282 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import * as types from 'vs/base/common/types'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry'; + +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { textmateColorsSchemaId, textmateColorGroupSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema'; +import { workbenchColorsSchemaId } from 'vs/platform/theme/common/colorRegistry'; +import { tokenStylingSchemaId } from 'vs/platform/theme/common/tokenClassificationRegistry'; +import { ThemeSettings, IWorkbenchColorTheme, IWorkbenchFileIconTheme, IColorCustomizations, ITokenColorCustomizations, IExperimentalTokenStyleCustomizations, IWorkbenchProductIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; + +const DEFAULT_THEME_SETTING_VALUE = 'Default Dark+'; +const DEFAULT_THEME_DARK_SETTING_VALUE = 'Default Dark+'; +const DEFAULT_THEME_LIGHT_SETTING_VALUE = 'Default Light+'; +const DEFAULT_THEME_HC_SETTING_VALUE = 'Default High Contrast'; + +const DEFAULT_FILE_ICON_THEME_SETTING_VALUE = 'vs-seti'; + +export const DEFAULT_PRODUCT_ICON_THEME_SETTING_VALUE = 'Default'; + +// Configuration: Themes +const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); + +const colorThemeSettingEnum: string[] = []; +const colorThemeSettingEnumDescriptions: string[] = []; + +const colorThemeSettingSchema: IConfigurationPropertySchema = { + type: 'string', + description: nls.localize('colorTheme', "Specifies the color theme used in the workbench."), + default: DEFAULT_THEME_SETTING_VALUE, + enum: colorThemeSettingEnum, + enumDescriptions: colorThemeSettingEnumDescriptions, + errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), +}; +const preferredDarkThemeSettingSchema: IConfigurationPropertySchema = { + type: 'string', + description: nls.localize('preferredDarkColorTheme', 'Specifies the preferred color theme for dark OS appearance when \'{0}\' is enabled.', ThemeSettings.DETECT_COLOR_SCHEME), + default: DEFAULT_THEME_DARK_SETTING_VALUE, + enum: colorThemeSettingEnum, + enumDescriptions: colorThemeSettingEnumDescriptions, + errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), +}; +const preferredLightThemeSettingSchema: IConfigurationPropertySchema = { + type: 'string', + description: nls.localize('preferredLightColorTheme', 'Specifies the preferred color theme for light OS appearance when \'{0}\' is enabled.', ThemeSettings.DETECT_COLOR_SCHEME), + default: DEFAULT_THEME_LIGHT_SETTING_VALUE, + enum: colorThemeSettingEnum, + enumDescriptions: colorThemeSettingEnumDescriptions, + errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), +}; +const preferredHCThemeSettingSchema: IConfigurationPropertySchema = { + type: 'string', + description: nls.localize('preferredHCColorTheme', 'Specifies the preferred color theme used in high contrast mode when \'{0}\' is enabled.', ThemeSettings.DETECT_HC), + default: DEFAULT_THEME_HC_SETTING_VALUE, + enum: colorThemeSettingEnum, + enumDescriptions: colorThemeSettingEnumDescriptions, + errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), +}; +const detectColorSchemeSettingSchema: IConfigurationPropertySchema = { + type: 'boolean', + description: nls.localize('detectColorScheme', 'If set, automatically switch to the preferred color theme based on the OS appearance.'), + default: false +}; + +const colorCustomizationsSchema: IConfigurationPropertySchema = { + type: 'object', + description: nls.localize('workbenchColors', "Overrides colors from the currently selected color theme."), + allOf: [{ $ref: workbenchColorsSchemaId }], + default: {}, + defaultSnippets: [{ + body: { + } + }] +}; +const fileIconThemeSettingSchema: IConfigurationPropertySchema = { + type: ['string', 'null'], + default: DEFAULT_FILE_ICON_THEME_SETTING_VALUE, + description: nls.localize('iconTheme', "Specifies the file icon theme used in the workbench or 'null' to not show any file icons."), + enum: [null], + enumDescriptions: [nls.localize('noIconThemeDesc', 'No file icons')], + errorMessage: nls.localize('iconThemeError', "File icon theme is unknown or not installed.") +}; +const productIconThemeSettingSchema: IConfigurationPropertySchema = { + type: ['string', 'null'], + default: DEFAULT_PRODUCT_ICON_THEME_SETTING_VALUE, + description: nls.localize('productIconTheme', "Specifies the product icon theme used."), + enum: [DEFAULT_PRODUCT_ICON_THEME_SETTING_VALUE], + enumDescriptions: [nls.localize('defaultProductIconThemeDesc', 'Default')], + errorMessage: nls.localize('productIconThemeError', "Product icon theme is unknown or not installed.") +}; + +const themeSettingsConfiguration: IConfigurationNode = { + id: 'workbench', + order: 7.1, + type: 'object', + properties: { + [ThemeSettings.COLOR_THEME]: colorThemeSettingSchema, + [ThemeSettings.PREFERRED_DARK_THEME]: preferredDarkThemeSettingSchema, + [ThemeSettings.PREFERRED_LIGHT_THEME]: preferredLightThemeSettingSchema, + [ThemeSettings.PREFERRED_HC_THEME]: preferredHCThemeSettingSchema, + [ThemeSettings.DETECT_COLOR_SCHEME]: detectColorSchemeSettingSchema, + [ThemeSettings.FILE_ICON_THEME]: fileIconThemeSettingSchema, + [ThemeSettings.COLOR_CUSTOMIZATIONS]: colorCustomizationsSchema, + [ThemeSettings.PRODUCT_ICON_THEME]: productIconThemeSettingSchema + } +}; +configurationRegistry.registerConfiguration(themeSettingsConfiguration); + +function tokenGroupSettings(description: string): IJSONSchema { + return { + description, + $ref: textmateColorGroupSchemaId + }; +} + +const tokenColorSchema: IJSONSchema = { + properties: { + comments: tokenGroupSettings(nls.localize('editorColors.comments', "Sets the colors and styles for comments")), + strings: tokenGroupSettings(nls.localize('editorColors.strings', "Sets the colors and styles for strings literals.")), + keywords: tokenGroupSettings(nls.localize('editorColors.keywords', "Sets the colors and styles for keywords.")), + numbers: tokenGroupSettings(nls.localize('editorColors.numbers', "Sets the colors and styles for number literals.")), + types: tokenGroupSettings(nls.localize('editorColors.types', "Sets the colors and styles for type declarations and references.")), + functions: tokenGroupSettings(nls.localize('editorColors.functions', "Sets the colors and styles for functions declarations and references.")), + variables: tokenGroupSettings(nls.localize('editorColors.variables', "Sets the colors and styles for variables declarations and references.")), + textMateRules: { + description: nls.localize('editorColors.textMateRules', 'Sets colors and styles using textmate theming rules (advanced).'), + $ref: textmateColorsSchemaId + }, + semanticHighlighting: { + description: nls.localize('editorColors.semanticHighlighting', 'Whether semantic highlighting should be enabled for this theme.'), + type: 'boolean' + } + } +}; +const tokenColorCustomizationSchema: IConfigurationPropertySchema = { + description: nls.localize('editorColors', "Overrides editor colors and font style from the currently selected color theme."), + default: {}, + allOf: [tokenColorSchema] +}; +const experimentalTokenStylingCustomizationSchema: IConfigurationPropertySchema = { + description: nls.localize('editorColorsTokenStyles', "Overrides token color and styles from the currently selected color theme."), + default: {}, + allOf: [{ $ref: tokenStylingSchemaId }] +}; +const tokenColorCustomizationConfiguration: IConfigurationNode = { + id: 'editor', + order: 7.2, + type: 'object', + properties: { + [ThemeSettings.TOKEN_COLOR_CUSTOMIZATIONS]: tokenColorCustomizationSchema, + [ThemeSettings.TOKEN_COLOR_CUSTOMIZATIONS_EXPERIMENTAL]: experimentalTokenStylingCustomizationSchema + } +}; + +configurationRegistry.registerConfiguration(tokenColorCustomizationConfiguration); + +export function updateColorThemeConfigurationSchemas(themes: IWorkbenchColorTheme[]) { + // updates enum for the 'workbench.colorTheme` setting + colorThemeSettingEnum.splice(0, colorThemeSettingEnum.length, ...themes.map(t => t.settingsId)); + colorThemeSettingEnumDescriptions.splice(0, colorThemeSettingEnumDescriptions.length, ...themes.map(t => t.description || '')); + + const themeSpecificWorkbenchColors: IJSONSchema = { properties: {} }; + const themeSpecificTokenColors: IJSONSchema = { properties: {} }; + const themeSpecificTokenStyling: IJSONSchema = { properties: {} }; + + const workbenchColors = { $ref: workbenchColorsSchemaId, additionalProperties: false }; + const tokenColors = { properties: tokenColorSchema.properties, additionalProperties: false }; + const tokenStyling = { $ref: tokenStylingSchemaId, additionalProperties: false }; + for (let t of themes) { + // add theme specific color customization ("[Abyss]":{ ... }) + const themeId = `[${t.settingsId}]`; + themeSpecificWorkbenchColors.properties![themeId] = workbenchColors; + themeSpecificTokenColors.properties![themeId] = tokenColors; + themeSpecificTokenStyling.properties![themeId] = tokenStyling; + } + + colorCustomizationsSchema.allOf![1] = themeSpecificWorkbenchColors; + tokenColorCustomizationSchema.allOf![1] = themeSpecificTokenColors; + experimentalTokenStylingCustomizationSchema.allOf![1] = themeSpecificTokenStyling; + + configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration, tokenColorCustomizationConfiguration); +} + +export function updateFileIconThemeConfigurationSchemas(themes: IWorkbenchFileIconTheme[]) { + fileIconThemeSettingSchema.enum!.splice(1, Number.MAX_VALUE, ...themes.map(t => t.settingsId)); + fileIconThemeSettingSchema.enumDescriptions!.splice(1, Number.MAX_VALUE, ...themes.map(t => t.description || '')); + + configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration); +} + +export function updateProductIconThemeConfigurationSchemas(themes: IWorkbenchProductIconTheme[]) { + productIconThemeSettingSchema.enum!.splice(1, Number.MAX_VALUE, ...themes.map(t => t.settingsId)); + productIconThemeSettingSchema.enumDescriptions!.splice(1, Number.MAX_VALUE, ...themes.map(t => t.description || '')); + + configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration); +} + + +export class ThemeConfiguration { + constructor(private configurationService: IConfigurationService) { + } + + public get colorTheme(): string { + return this.configurationService.getValue(ThemeSettings.COLOR_THEME); + } + + public get fileIconTheme(): string | null { + return this.configurationService.getValue(ThemeSettings.FILE_ICON_THEME); + } + + public get productIconTheme(): string { + return this.configurationService.getValue(ThemeSettings.PRODUCT_ICON_THEME); + } + + public get colorCustomizations(): IColorCustomizations { + return this.configurationService.getValue(ThemeSettings.COLOR_CUSTOMIZATIONS) || {}; + } + + public get tokenColorCustomizations(): ITokenColorCustomizations { + return this.configurationService.getValue(ThemeSettings.TOKEN_COLOR_CUSTOMIZATIONS) || {}; + } + + public get tokenStylesCustomizations(): IExperimentalTokenStyleCustomizations { + return this.configurationService.getValue(ThemeSettings.TOKEN_COLOR_CUSTOMIZATIONS_EXPERIMENTAL) || {}; + } + + public async setColorTheme(theme: IWorkbenchColorTheme, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { + await this.writeConfiguration(ThemeSettings.COLOR_THEME, theme.settingsId, settingsTarget); + return theme; + } + + public async setFileIconTheme(theme: IWorkbenchFileIconTheme, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { + await this.writeConfiguration(ThemeSettings.FILE_ICON_THEME, theme.settingsId, settingsTarget); + return theme; + } + + public async setProductIconTheme(theme: IWorkbenchProductIconTheme, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { + await this.writeConfiguration(ThemeSettings.PRODUCT_ICON_THEME, theme.settingsId, settingsTarget); + return theme; + } + + private async writeConfiguration(key: string, value: any, settingsTarget: ConfigurationTarget | 'auto' | undefined): Promise { + if (settingsTarget === undefined) { + return; + } + + let settings = this.configurationService.inspect(key); + if (settingsTarget === 'auto') { + if (!types.isUndefined(settings.workspaceFolderValue)) { + settingsTarget = ConfigurationTarget.WORKSPACE_FOLDER; + } else if (!types.isUndefined(settings.workspaceValue)) { + settingsTarget = ConfigurationTarget.WORKSPACE; + } else if (!types.isUndefined(settings.userRemote)) { + settingsTarget = ConfigurationTarget.USER_REMOTE; + } else { + settingsTarget = ConfigurationTarget.USER; + } + } + + if (settingsTarget === ConfigurationTarget.USER) { + if (value === settings.userValue) { + return Promise.resolve(undefined); // nothing to do + } else if (value === settings.defaultValue) { + if (types.isUndefined(settings.userValue)) { + return Promise.resolve(undefined); // nothing to do + } + value = undefined; // remove configuration from user settings + } + } else if (settingsTarget === ConfigurationTarget.WORKSPACE || settingsTarget === ConfigurationTarget.WORKSPACE_FOLDER || settingsTarget === ConfigurationTarget.USER_REMOTE) { + if (value === settings.value) { + return Promise.resolve(undefined); // nothing to do + } + } + return this.configurationService.updateValue(key, value, settingsTarget); + } +} diff --git a/src/vs/workbench/services/themes/common/themeExtensionPoints.ts b/src/vs/workbench/services/themes/common/themeExtensionPoints.ts new file mode 100644 index 00000000000..20e496a4fe3 --- /dev/null +++ b/src/vs/workbench/services/themes/common/themeExtensionPoints.ts @@ -0,0 +1,261 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; + +import * as types from 'vs/base/common/types'; +import * as resources from 'vs/base/common/resources'; +import { ExtensionMessageCollector, IExtensionPoint, ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { ExtensionData, IThemeExtensionPoint, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService'; + +import { IExtensionService, checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { Event, Emitter } from 'vs/base/common/event'; +import { URI } from 'vs/base/common/uri'; + +export function registerColorThemeExtensionPoint() { + return ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'themes', + jsonSchema: { + description: nls.localize('vscode.extension.contributes.themes', 'Contributes textmate color themes.'), + type: 'array', + items: { + type: 'object', + defaultSnippets: [{ body: { label: '${1:label}', id: '${2:id}', uiTheme: VS_DARK_THEME, path: './themes/${3:id}.tmTheme.' } }], + properties: { + id: { + description: nls.localize('vscode.extension.contributes.themes.id', 'Id of the color theme as used in the user settings.'), + type: 'string' + }, + label: { + description: nls.localize('vscode.extension.contributes.themes.label', 'Label of the color theme as shown in the UI.'), + type: 'string' + }, + uiTheme: { + description: nls.localize('vscode.extension.contributes.themes.uiTheme', 'Base theme defining the colors around the editor: \'vs\' is the light color theme, \'vs-dark\' is the dark color theme. \'hc-black\' is the dark high contrast theme.'), + enum: [VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME] + }, + path: { + description: nls.localize('vscode.extension.contributes.themes.path', 'Path of the tmTheme file. The path is relative to the extension folder and is typically \'./colorthemes/awesome-color-theme.json\'.'), + type: 'string' + } + }, + required: ['path', 'uiTheme'] + } + } + }); +} +export function registerFileIconThemeExtensionPoint() { + return ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'iconThemes', + jsonSchema: { + description: nls.localize('vscode.extension.contributes.iconThemes', 'Contributes file icon themes.'), + type: 'array', + items: { + type: 'object', + defaultSnippets: [{ body: { id: '${1:id}', label: '${2:label}', path: './fileicons/${3:id}-icon-theme.json' } }], + properties: { + id: { + description: nls.localize('vscode.extension.contributes.iconThemes.id', 'Id of the file icon theme as used in the user settings.'), + type: 'string' + }, + label: { + description: nls.localize('vscode.extension.contributes.iconThemes.label', 'Label of the file icon theme as shown in the UI.'), + type: 'string' + }, + path: { + description: nls.localize('vscode.extension.contributes.iconThemes.path', 'Path of the file icon theme definition file. The path is relative to the extension folder and is typically \'./fileicons/awesome-icon-theme.json\'.'), + type: 'string' + } + }, + required: ['path', 'id'] + } + } + }); +} + +export function registerProductIconThemeExtensionPoint() { + return ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'productIconThemes', + jsonSchema: { + description: nls.localize('vscode.extension.contributes.productIconThemes', 'Contributes product icon themes.'), + type: 'array', + items: { + type: 'object', + defaultSnippets: [{ body: { id: '${1:id}', label: '${2:label}', path: './producticons/${3:id}-product-icon-theme.json' } }], + properties: { + id: { + description: nls.localize('vscode.extension.contributes.productIconThemes.id', 'Id of the product icon theme as used in the user settings.'), + type: 'string' + }, + label: { + description: nls.localize('vscode.extension.contributes.productIconThemes.label', 'Label of the product icon theme as shown in the UI.'), + type: 'string' + }, + path: { + description: nls.localize('vscode.extension.contributes.productIconThemes.path', 'Path of the product icon theme definition file. The path is relative to the extension folder and is typically \'./producticons/awesome-product-icon-theme.json\'.'), + type: 'string' + } + }, + required: ['path', 'id'] + } + } + }); +} + +export interface ThemeChangeEvent { + themes: T[]; + added: T[]; + removed: T[]; +} + +export interface IThemeData { + id: string; + settingsId: string | null; + extensionData?: ExtensionData; +} + +export class ThemeRegistry { + + private extensionThemes: T[]; + + private readonly onDidChangeEmitter = new Emitter>(); + public readonly onDidChange: Event> = this.onDidChangeEmitter.event; + + constructor( + @IExtensionService private readonly extensionService: IExtensionService, + private readonly themesExtPoint: IExtensionPoint, + private create: (theme: IThemeExtensionPoint, themeLocation: URI, extensionData: ExtensionData) => T, + private idRequired = false, + private builtInTheme: T | undefined = undefined, + private isProposedApi = false + ) { + this.extensionThemes = []; + this.initialize(); + } + + private initialize() { + this.themesExtPoint.setHandler((extensions, delta) => { + const previousIds: { [key: string]: T } = {}; + + const added: T[] = []; + for (const theme of this.extensionThemes) { + previousIds[theme.id] = theme; + } + this.extensionThemes.length = 0; + for (let ext of extensions) { + if (this.isProposedApi) { + checkProposedApiEnabled(ext.description); + } + let extensionData: ExtensionData = { + extensionId: ext.description.identifier.value, + extensionPublisher: ext.description.publisher, + extensionName: ext.description.name, + extensionIsBuiltin: ext.description.isBuiltin, + extensionLocation: ext.description.extensionLocation + }; + this.onThemes(extensionData, ext.value, ext.collector); + } + for (const theme of this.extensionThemes) { + if (!previousIds[theme.id]) { + added.push(theme); + } else { + delete previousIds[theme.id]; + } + } + const removed = Object.values(previousIds); + this.onDidChangeEmitter.fire({ themes: this.extensionThemes, added, removed }); + }); + } + + private onThemes(extensionData: ExtensionData, themes: IThemeExtensionPoint[], collector: ExtensionMessageCollector): void { + if (!Array.isArray(themes)) { + collector.error(nls.localize( + 'reqarray', + "Extension point `{0}` must be an array.", + this.themesExtPoint.name + )); + return; + } + themes.forEach(theme => { + if (!theme.path || !types.isString(theme.path)) { + collector.error(nls.localize( + 'reqpath', + "Expected string in `contributes.{0}.path`. Provided value: {1}", + this.themesExtPoint.name, + String(theme.path) + )); + return; + } + if (this.idRequired && (!theme.id || !types.isString(theme.id))) { + collector.error(nls.localize( + 'reqid', + "Expected string in `contributes.{0}.id`. Provided value: {1}", + this.themesExtPoint.name, + String(theme.id) + )); + return; + } + + const themeLocation = resources.joinPath(extensionData.extensionLocation, theme.path); + if (!resources.isEqualOrParent(themeLocation, extensionData.extensionLocation)) { + collector.warn(nls.localize('invalid.path.1', "Expected `contributes.{0}.path` ({1}) to be included inside extension's folder ({2}). This might make the extension non-portable.", this.themesExtPoint.name, themeLocation.path, extensionData.extensionLocation.path)); + } + + let themeData = this.create(theme, themeLocation, extensionData); + this.extensionThemes.push(themeData); + }); + } + + public async findThemeById(themeId: string, defaultId?: string): Promise { + if (this.builtInTheme && this.builtInTheme.id === themeId) { + return this.builtInTheme; + } + const allThemes = await this.getThemes(); + let defaultTheme: T | undefined = undefined; + for (let t of allThemes) { + if (t.id === themeId) { + return t; + } + if (t.id === defaultId) { + defaultTheme = t; + } + } + return defaultTheme; + } + + public async findThemeBySettingsId(settingsId: string | null, defaultId?: string): Promise { + if (this.builtInTheme && this.builtInTheme.settingsId === settingsId) { + return this.builtInTheme; + } + const allThemes = await this.getThemes(); + let defaultTheme: T | undefined = undefined; + for (let t of allThemes) { + if (t.settingsId === settingsId) { + return t; + } + if (t.id === defaultId) { + defaultTheme = t; + } + } + return defaultTheme; + } + + public findThemeByExtensionLocation(extLocation: URI | undefined): Promise { + if (extLocation) { + return this.getThemes().then(allThemes => { + return allThemes.filter(t => t.extensionData && resources.isEqual(t.extensionData.extensionLocation, extLocation)); + }); + } + return Promise.resolve([]); + + } + + public getThemes(): Promise { + return this.extensionService.whenInstalledExtensionsRegistered().then(_ => { + return this.extensionThemes; + }); + } + +} diff --git a/src/vs/workbench/services/themes/common/tokenClassificationExtensionPoint.ts b/src/vs/workbench/services/themes/common/tokenClassificationExtensionPoint.ts index 55e11a97374..6ea7f40b026 100644 --- a/src/vs/workbench/services/themes/common/tokenClassificationExtensionPoint.ts +++ b/src/vs/workbench/services/themes/common/tokenClassificationExtensionPoint.ts @@ -5,8 +5,7 @@ import * as nls from 'vs/nls'; import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { getTokenClassificationRegistry, ITokenClassificationRegistry, typeAndModifierIdPattern, TokenStyleDefaults, TokenStyle, fontStylePattern } from 'vs/platform/theme/common/tokenClassificationRegistry'; -import { textmateColorSettingsSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema'; +import { getTokenClassificationRegistry, ITokenClassificationRegistry, typeAndModifierIdPattern } from 'vs/platform/theme/common/tokenClassificationRegistry'; interface ITokenTypeExtensionPoint { id: string; @@ -20,25 +19,10 @@ interface ITokenModifierExtensionPoint { } interface ITokenStyleDefaultExtensionPoint { - selector: string; - scope?: string[]; - light?: { - foreground?: string; - fontStyle?: string; - }; - dark?: { - foreground?: string; - fontStyle?: string; - }; - highContrast?: { - foreground?: string; - fontStyle?: string; - }; + language?: string; + scopes: { [selector: string]: string[] }; } -const selectorPattern = '^([-_\\w]+|\\*)(\\.[-_\\w+]+)*$'; -const colorPattern = '^#([0-9A-Fa-f]{6})([0-9A-Fa-f]{2})?$'; - const tokenClassificationRegistry: ITokenClassificationRegistry = getTokenClassificationRegistry(); const tokenTypeExtPoint = ExtensionsRegistry.registerExtensionPoint({ @@ -55,6 +39,12 @@ const tokenTypeExtPoint = ExtensionsRegistry.registerExtensionPoint({ - extensionPoint: 'semanticTokenStyleDefaults', + extensionPoint: 'semanticTokenScopes', jsonSchema: { - description: nls.localize('contributes.semanticTokenStyleDefaults', 'Contributes semantic token style defaults.'), + description: nls.localize('contributes.semanticTokenScopes', 'Contributes semantic token scope maps.'), type: 'array', items: { type: 'object', properties: { - selector: { - type: 'string', - description: nls.localize('contributes.semanticTokenStyleDefaults.selector', 'The selector matching token types and modifiers.'), - pattern: selectorPattern, - patternErrorMessage: nls.localize('contributes.semanticTokenStyleDefaults.selector.format', 'Selectors should be in the form (type|*)(.modifier)*'), + language: { + description: nls.localize('contributes.semanticTokenScopes.languages', 'Lists the languge for which the defaults are.'), + type: 'string' }, - scope: { - type: 'array', - description: nls.localize('contributes.semanticTokenStyleDefaults.scope', 'A TextMate scope against the current color theme is matched to find the style for the given selector'), - items: { - type: 'string' + scopes: { + description: nls.localize('contributes.semanticTokenScopes.scopes', 'Maps a semantic token (described by semantic token selector) to one or more textMate scopes used to represent that token.'), + type: 'object', + additionalProperties: { + type: 'array', + items: { + type: 'string' + } } - }, - light: { - description: nls.localize('contributes.semanticTokenStyleDefaults.light', 'The default style used for light themes'), - $ref: textmateColorSettingsSchemaId - }, - dark: { - description: nls.localize('contributes.semanticTokenStyleDefaults.dark', 'The default style used for dark themes'), - $ref: textmateColorSettingsSchemaId - }, - highContrast: { - description: nls.localize('contributes.semanticTokenStyleDefaults.hc', 'The default style used for high contrast themes'), - $ref: textmateColorSettingsSchemaId } } } @@ -148,24 +127,6 @@ export class TokenClassificationExtensionPoints { } return true; } - function validateStyle(style: { foreground?: string; fontStyle?: string; } | undefined, extensionPoint: string, collector: ExtensionMessageCollector): TokenStyle | undefined { - if (!style) { - return undefined; - } - if (style.foreground) { - if (typeof style.foreground !== 'string' || !style.foreground.match(colorPattern)) { - collector.error(nls.localize('invalid.color', "'configuration.{0}.foreground' must follow the pattern #RRGGBB[AA]", extensionPoint)); - return undefined; - } - } - if (style.fontStyle) { - if (typeof style.fontStyle !== 'string' || !style.fontStyle.match(fontStylePattern)) { - collector.error(nls.localize('invalid.fontStyle', "'configuration.{0}.fontStyle' must be one or a combination of \'italic\', \'bold\' or \'underline\' or the empty string", extensionPoint)); - return undefined; - } - } - return TokenStyle.fromSettings(style.foreground, style.fontStyle); - } tokenTypeExtPoint.setHandler((extensions, delta) => { for (const extension of delta.added) { @@ -217,49 +178,45 @@ export class TokenClassificationExtensionPoints { const collector = extension.collector; if (!extensionValue || !Array.isArray(extensionValue)) { - collector.error(nls.localize('invalid.semanticTokenStyleDefaultConfiguration', "'configuration.semanticTokenStyleDefaults' must be an array")); + collector.error(nls.localize('invalid.semanticTokenScopes.configuration', "'configuration.semanticTokenScopes' must be an array")); return; } for (const contribution of extensionValue) { - if (typeof contribution.selector !== 'string' || contribution.selector.length === 0) { - collector.error(nls.localize('invalid.selector', "'configuration.semanticTokenStyleDefaults.selector' must be defined and can not be empty")); + if (contribution.language && typeof contribution.language !== 'string') { + collector.error(nls.localize('invalid.semanticTokenScopes.language', "'configuration.semanticTokenScopes.language' must be a string")); continue; } - if (!contribution.selector.match(selectorPattern)) { - collector.error(nls.localize('invalid.selector.format', "'configuration.semanticTokenStyleDefaults.selector' must be in the form (type|*)(.modifier)*")); + if (!contribution.scopes || typeof contribution.scopes !== 'object') { + collector.error(nls.localize('invalid.semanticTokenScopes.scopes', "'configuration.semanticTokenScopes.scopes' must be defined as an object")); continue; } - - const tokenStyleDefault: TokenStyleDefaults = {}; - - if (contribution.scope) { - if ((!Array.isArray(contribution.scope) || contribution.scope.some(s => typeof s !== 'string'))) { - collector.error(nls.localize('invalid.scope', "If defined, 'configuration.semanticTokenStyleDefaults.scope' must be an array of strings")); + for (let selectorString in contribution.scopes) { + const tmScopes = contribution.scopes[selectorString]; + if (!Array.isArray(tmScopes) || tmScopes.some(l => typeof l !== 'string')) { + collector.error(nls.localize('invalid.semanticTokenScopes.scopes.value', "'configuration.semanticTokenScopes.scopes' values must be an array of strings")); continue; } - tokenStyleDefault.scopesToProbe = [contribution.scope]; - } - tokenStyleDefault.light = validateStyle(contribution.light, 'semanticTokenStyleDefaults.light', collector); - tokenStyleDefault.dark = validateStyle(contribution.dark, 'semanticTokenStyleDefaults.dark', collector); - tokenStyleDefault.hc = validateStyle(contribution.highContrast, 'semanticTokenStyleDefaults.highContrast', collector); - - try { - const selector = tokenClassificationRegistry.parseTokenSelector(contribution.selector); - tokenClassificationRegistry.registerTokenStyleDefault(selector, tokenStyleDefault); - } catch (e) { - collector.error(nls.localize('invalid.selector.parsing', "configuration.semanticTokenStyleDefaults.selector': Problems parsing {0}.", contribution.selector)); - // invalid selector, ignore + try { + const selector = tokenClassificationRegistry.parseTokenSelector(selectorString, contribution.language); + tokenClassificationRegistry.registerTokenStyleDefault(selector, { scopesToProbe: tmScopes.map(s => s.split(' ')) }); + } catch (e) { + collector.error(nls.localize('invalid.semanticTokenScopes.scopes.selector', "configuration.semanticTokenScopes.scopes': Problems parsing selector {0}.", selectorString)); + // invalid selector, ignore + } } } } for (const extension of delta.removed) { const extensionValue = extension.value; for (const contribution of extensionValue) { - try { - const selector = tokenClassificationRegistry.parseTokenSelector(contribution.selector); - tokenClassificationRegistry.deregisterTokenStyleDefault(selector); - } catch (e) { - // invalid selector, ignore + for (let selectorString in contribution.scopes) { + const tmScopes = contribution.scopes[selectorString]; + try { + const selector = tokenClassificationRegistry.parseTokenSelector(selectorString, contribution.language); + tokenClassificationRegistry.registerTokenStyleDefault(selector, { scopesToProbe: tmScopes.map(s => s.split(' ')) }); + } catch (e) { + // invalid selector, ignore + } } } } diff --git a/src/vs/workbench/services/themes/common/workbenchThemeService.ts b/src/vs/workbench/services/themes/common/workbenchThemeService.ts index 16765a13851..e6459c58672 100644 --- a/src/vs/workbench/services/themes/common/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/common/workbenchThemeService.ts @@ -6,7 +6,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; import { Color } from 'vs/base/common/color'; -import { ITheme, IThemeService, IIconTheme } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService, IFileIconTheme } from 'vs/platform/theme/common/themeService'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { URI } from 'vs/base/common/uri'; @@ -18,19 +18,31 @@ export const VS_HC_THEME = 'hc-black'; export const HC_THEME_ID = 'Default High Contrast'; -export const COLOR_THEME_SETTING = 'workbench.colorTheme'; -export const ICON_THEME_SETTING = 'workbench.iconTheme'; -export const CUSTOM_WORKBENCH_COLORS_SETTING = 'workbench.colorCustomizations'; -export const CUSTOM_EDITOR_COLORS_SETTING = 'editor.tokenColorCustomizations'; -export const CUSTOM_EDITOR_TOKENSTYLES_SETTING = 'editor.tokenColorCustomizationsExperimental'; +export enum ThemeSettings { + COLOR_THEME = 'workbench.colorTheme', + FILE_ICON_THEME = 'workbench.iconTheme', + PRODUCT_ICON_THEME = 'workbench.productIconTheme', + COLOR_CUSTOMIZATIONS = 'workbench.colorCustomizations', + TOKEN_COLOR_CUSTOMIZATIONS = 'editor.tokenColorCustomizations', + TOKEN_COLOR_CUSTOMIZATIONS_EXPERIMENTAL = 'editor.tokenColorCustomizationsExperimental', -export interface IColorTheme extends ITheme { + PREFERRED_DARK_THEME = 'workbench.preferredDarkColorTheme', + PREFERRED_LIGHT_THEME = 'workbench.preferredLightColorTheme', + PREFERRED_HC_THEME = 'workbench.preferredHighContrastColorTheme', + DETECT_COLOR_SCHEME = 'window.autoDetectColorScheme', + DETECT_HC = 'window.autoDetectHighContrast' +} + +export interface IWorkbenchTheme { readonly id: string; readonly label: string; - readonly settingsId: string; readonly extensionData?: ExtensionData; readonly description?: string; - readonly isLoaded: boolean; + readonly settingsId: string | null; +} + +export interface IWorkbenchColorTheme extends IWorkbenchTheme, IColorTheme { + readonly settingsId: string; readonly tokenColors: ITextMateThemingRule[]; } @@ -38,31 +50,32 @@ export interface IColorMap { [id: string]: Color; } -export interface IFileIconTheme extends IIconTheme { - readonly id: string; - readonly label: string; - readonly settingsId: string | null; - readonly description?: string; - readonly extensionData?: ExtensionData; - - readonly isLoaded: boolean; - readonly hasFileIcons: boolean; - readonly hasFolderIcons: boolean; - readonly hidesExplorerArrows: boolean; +export interface IWorkbenchFileIconTheme extends IWorkbenchTheme, IFileIconTheme { } +export interface IWorkbenchProductIconTheme extends IWorkbenchTheme { + readonly settingsId: string; +} + + export interface IWorkbenchThemeService extends IThemeService { _serviceBrand: undefined; - setColorTheme(themeId: string | undefined, settingsTarget: ConfigurationTarget | undefined): Promise; - getColorTheme(): IColorTheme; - getColorThemes(): Promise; - onDidColorThemeChange: Event; + setColorTheme(themeId: string | undefined, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise; + getColorTheme(): IWorkbenchColorTheme; + getColorThemes(): Promise; + onDidColorThemeChange: Event; restoreColorTheme(): void; - setFileIconTheme(iconThemeId: string | undefined, settingsTarget: ConfigurationTarget | undefined): Promise; - getFileIconTheme(): IFileIconTheme; - getFileIconThemes(): Promise; - onDidFileIconThemeChange: Event; + setFileIconTheme(iconThemeId: string | undefined, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise; + getFileIconTheme(): IWorkbenchFileIconTheme; + getFileIconThemes(): Promise; + onDidFileIconThemeChange: Event; + + setProductIconTheme(iconThemeId: string | undefined, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise; + getProductIconTheme(): IWorkbenchProductIconTheme; + getProductIconThemes(): Promise; + onDidProductIconThemeChange: Event; + } export interface IColorCustomizations { @@ -70,7 +83,7 @@ export interface IColorCustomizations { } export interface ITokenColorCustomizations { - [groupIdOrThemeSettingsId: string]: string | ITokenColorizationSetting | ITokenColorCustomizations | undefined | ITextMateThemingRule[]; + [groupIdOrThemeSettingsId: string]: string | ITokenColorizationSetting | ITokenColorCustomizations | undefined | ITextMateThemingRule[] | boolean; comments?: string | ITokenColorizationSetting; strings?: string | ITokenColorizationSetting; numbers?: string | ITokenColorizationSetting; @@ -79,6 +92,7 @@ export interface ITokenColorCustomizations { functions?: string | ITokenColorizationSetting; variables?: string | ITokenColorizationSetting; textMateRules?: ITextMateThemingRule[]; + semanticHighlighting?: boolean; } export interface IExperimentalTokenStyleCustomizations { diff --git a/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts index 8f5da088288..a62d9f560b6 100644 --- a/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts +++ b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts @@ -47,36 +47,34 @@ function assertTokenStyle(actual: TokenStyle | undefined | null, expected: Token assert.equal(tokenStyleAsString(actual), tokenStyleAsString(expected), message); } -function assertTokenStyleMetaData(colorIndex: string[], actual: ITokenStyle | undefined, expected: TokenStyle | undefined | null, message?: string) { +function assertTokenStyleMetaData(colorIndex: string[], actual: ITokenStyle | undefined, expected: TokenStyle | undefined | null, message = '') { if (expected === undefined || expected === null || actual === undefined) { assert.equal(actual, expected, message); return; } - assert.strictEqual(actual.bold, expected.bold, 'bold'); - assert.strictEqual(actual.italic, expected.italic, 'italic'); - assert.strictEqual(actual.underline, expected.underline, 'underline'); + assert.strictEqual(actual.bold, expected.bold, 'bold ' + message); + assert.strictEqual(actual.italic, expected.italic, 'italic ' + message); + assert.strictEqual(actual.underline, expected.underline, 'underline ' + message); const actualForegroundIndex = actual.foreground; - if (expected.foreground) { - assert.equal(actualForegroundIndex, colorIndex.indexOf(Color.Format.CSS.formatHexA(expected.foreground, true).toUpperCase()), 'foreground'); + if (actualForegroundIndex && expected.foreground) { + assert.equal(colorIndex[actualForegroundIndex], Color.Format.CSS.formatHexA(expected.foreground, true).toUpperCase(), 'foreground ' + message); } else { - assert.equal(actualForegroundIndex, 0, 'foreground'); + assert.equal(actualForegroundIndex, expected.foreground || 0, 'foreground ' + message); } } -function assertTokenStyles(themeData: ColorThemeData, expected: { [qualifiedClassifier: string]: TokenStyle }) { +function assertTokenStyles(themeData: ColorThemeData, expected: { [qualifiedClassifier: string]: TokenStyle }, language = 'typescript') { const colorIndex = themeData.tokenColorMap; for (let qualifiedClassifier in expected) { const [type, ...modifiers] = qualifiedClassifier.split('.'); - const tokenStyle = themeData.getTokenStyle(type, modifiers); const expectedTokenStyle = expected[qualifiedClassifier]; - assertTokenStyle(tokenStyle, expectedTokenStyle, qualifiedClassifier); - const tokenStyleMetaData = themeData.getTokenStyleMetadata(type, modifiers); - assertTokenStyleMetaData(colorIndex, tokenStyleMetaData, expectedTokenStyle); + const tokenStyleMetaData = themeData.getTokenStyleMetadata(type, modifiers, language); + assertTokenStyleMetaData(colorIndex, tokenStyleMetaData, expectedTokenStyle, qualifiedClassifier); } } @@ -289,6 +287,42 @@ suite('Themes - TokenStyleResolving', () => { }); + + test('resolveScopes - match most specific', async () => { + const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test'); + + const customTokenColors: ITokenColorCustomizations = { + textMateRules: [ + { + scope: 'entity.name.type', + settings: { + fontStyle: 'underline', + foreground: '#A6E22E' + } + }, + { + scope: 'entity.name.type.class', + settings: { + foreground: '#FF00FF' + } + }, + { + scope: 'entity.name', + settings: { + foreground: '#FFFFFF' + } + }, + ] + }; + + themeData.setCustomTokenColors(customTokenColors); + + const tokenStyle = themeData.resolveScopes([['entity.name.type.class']]); + assertTokenStyle(tokenStyle, ts('#FF00FF', { underline: true }), 'entity.name.type.class'); + + }); + + test('rule matching', async () => { const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test'); themeData.setCustomColors({ 'editor.foreground': '#000000' }); @@ -315,8 +349,10 @@ suite('Themes - TokenStyleResolving', () => { }); test('super type', async () => { - getTokenClassificationRegistry().registerTokenType('myTestInterface', 'A type just for testing', 'interface'); - getTokenClassificationRegistry().registerTokenType('myTestSubInterface', 'A type just for testing', 'myTestInterface'); + const registry = getTokenClassificationRegistry(); + + registry.registerTokenType('myTestInterface', 'A type just for testing', 'interface'); + registry.registerTokenType('myTestSubInterface', 'A type just for testing', 'myTestInterface'); try { const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test'); @@ -336,7 +372,62 @@ suite('Themes - TokenStyleResolving', () => { }); assertTokenStyles(themeData, { 'myTestSubInterface': ts('#ff00ff', { italic: true }) }); } finally { - getTokenClassificationRegistry().deregisterTokenType('myTestInterface'); + registry.deregisterTokenType('myTestInterface'); + registry.deregisterTokenType('myTestSubInterface'); + } + }); + + test('language', async () => { + try { + const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test'); + themeData.setCustomColors({ 'editor.foreground': '#000000' }); + themeData.setCustomTokenStyleRules({ + 'interface': '#fff000', + 'interface:java': '#ff0000', + 'interface.static': { fontStyle: 'bold' }, + 'interface.static:typescript': { fontStyle: 'italic' } + }); + + assertTokenStyles(themeData, { 'interface': ts('#ff0000', undefined) }, 'java'); + assertTokenStyles(themeData, { 'interface': ts('#fff000', undefined) }, 'typescript'); + assertTokenStyles(themeData, { 'interface.static': ts('#ff0000', { bold: true }) }, 'java'); + assertTokenStyles(themeData, { 'interface.static': ts('#fff000', { bold: true, italic: true }) }, 'typescript'); + } finally { + } + }); + + test('language - scope resolving', async () => { + const registry = getTokenClassificationRegistry(); + + const numberOfDefaultRules = registry.getTokenStylingDefaultRules().length; + + registry.registerTokenStyleDefault(registry.parseTokenSelector('type', 'typescript1'), { scopesToProbe: [['entity.name.type.ts1']] }); + registry.registerTokenStyleDefault(registry.parseTokenSelector('type:javascript1'), { scopesToProbe: [['entity.name.type.js1']] }); + + try { + const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test'); + themeData.setCustomColors({ 'editor.foreground': '#000000' }); + themeData.setCustomTokenColors({ + textMateRules: [ + { + scope: 'entity.name.type', + settings: { foreground: '#aa0000' } + }, + { + scope: 'entity.name.type.ts1', + settings: { foreground: '#bb0000' } + } + ] + }); + + assertTokenStyles(themeData, { 'type': ts('#aa0000', undefined) }, 'javascript1'); + assertTokenStyles(themeData, { 'type': ts('#bb0000', undefined) }, 'typescript1'); + + } finally { + registry.deregisterTokenStyleDefault(registry.parseTokenSelector('type', 'typescript1')); + registry.deregisterTokenStyleDefault(registry.parseTokenSelector('type:javascript1')); + + assert.equal(registry.getTokenStylingDefaultRules().length, numberOfDefaultRules); } }); }); diff --git a/src/vs/workbench/services/timer/electron-browser/timerService.ts b/src/vs/workbench/services/timer/electron-browser/timerService.ts index 3b3d71f9eb0..47fd9783039 100644 --- a/src/vs/workbench/services/timer/electron-browser/timerService.ts +++ b/src/vs/workbench/services/timer/electron-browser/timerService.ts @@ -18,6 +18,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; /* __GDPR__FRAGMENT__ "IMemoryInfo" : { @@ -303,7 +304,7 @@ class TimerService implements ITimerService { constructor( @IElectronService private readonly _electronService: IElectronService, - @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService private readonly _environmentService: INativeWorkbenchEnvironmentService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @IExtensionService private readonly _extensionService: IExtensionService, diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts index e42c09b2e33..bd4f8afbc99 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts @@ -253,7 +253,7 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt return !!target; } - async revert(): Promise { + async revert(): Promise { this.setDirty(false); // Emit as event @@ -263,8 +263,6 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt // no actual source on disk to revert to. As such we // dispose the model. this.dispose(); - - return true; } async backup(): Promise { diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts index 10c06b14c5e..5c91d4512b6 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts @@ -219,11 +219,13 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe } private registerModel(model: UntitledTextEditorModel): void { - const modelDisposables = new DisposableStore(); - modelDisposables.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(model))); - modelDisposables.add(model.onDidChangeName(() => this._onDidChangeLabel.fire(model))); - modelDisposables.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(model))); - modelDisposables.add(model.onDispose(() => this._onDidDispose.fire(model))); + + // Install model listeners + const modelListeners = new DisposableStore(); + modelListeners.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(model))); + modelListeners.add(model.onDidChangeName(() => this._onDidChangeLabel.fire(model))); + modelListeners.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(model))); + modelListeners.add(model.onDispose(() => this._onDidDispose.fire(model))); // Remove from cache on dispose Event.once(model.onDispose)(() => { @@ -232,11 +234,17 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe this.mapResourceToModel.delete(model.resource); // Listeners - modelDisposables.dispose(); + modelListeners.dispose(); }); // Add to cache this.mapResourceToModel.set(model.resource, model); + + // If the model is dirty right from the beginning, + // make sure to emit this as an event + if (model.isDirty()) { + this._onDidChangeDirty.fire(model); + } } } diff --git a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts index 9b2b8015372..bf8c715cb34 100644 --- a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts +++ b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts @@ -14,6 +14,7 @@ import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRe import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { Range } from 'vs/editor/common/core/range'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; +import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; suite('Untitled text editors', () => { @@ -82,7 +83,7 @@ suite('Untitled text editors', () => { assert.ok(!workingCopyService.isDirty(input2.resource)); assert.equal(workingCopyService.dirtyCount, 0); - assert.equal(await input1.revert(0), false); + await input1.revert(0); assert.ok(input1.isDisposed()); assert.ok(!service.get(input1.resource)); @@ -120,15 +121,23 @@ suite('Untitled text editors', () => { const service = accessor.untitledTextEditorService; const file = URI.file(join('C:\\', '/foo/file.txt')); - const untitled = instantiationService.createInstance(UntitledTextEditorInput, service.create({ associatedResource: file })); + let onDidChangeDirtyModel: IUntitledTextEditorModel | undefined = undefined; + const listener = service.onDidChangeDirty(model => { + onDidChangeDirtyModel = model; + }); + + const model = service.create({ associatedResource: file }); + const untitled = instantiationService.createInstance(UntitledTextEditorInput, model); assert.ok(untitled.isDirty()); + assert.equal(model, onDidChangeDirtyModel); - const model = await untitled.resolve(); + const resolvedModel = await untitled.resolve(); - assert.ok(model.hasAssociatedFilePath); + assert.ok(resolvedModel.hasAssociatedFilePath); assert.equal(untitled.isDirty(), true); untitled.dispose(); + listener.dispose(); }); test('no longer dirty when content gets empty (not with associated resource)', async () => { diff --git a/src/vs/workbench/services/url/electron-browser/urlService.ts b/src/vs/workbench/services/url/electron-browser/urlService.ts index c1485a6ec59..c5e9d85e467 100644 --- a/src/vs/workbench/services/url/electron-browser/urlService.ts +++ b/src/vs/workbench/services/url/electron-browser/urlService.ts @@ -11,9 +11,10 @@ import { URLService } from 'vs/platform/url/node/urlService'; import { IOpenerService, IOpener, matchesScheme } from 'vs/platform/opener/common/opener'; import product from 'vs/platform/product/common/product'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; import { createChannelSender } from 'vs/base/parts/ipc/node/ipc'; import { IElectronService } from 'vs/platform/electron/node/electron'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; export interface IRelayOpenURLOptions extends IOpenURLOptions { openToSide?: boolean; @@ -27,7 +28,7 @@ export class RelayURLService extends URLService implements IURLHandler, IOpener constructor( @IMainProcessService mainProcessService: IMainProcessService, @IOpenerService openerService: IOpenerService, - @IElectronEnvironmentService private electronEnvironmentService: IElectronEnvironmentService, + @IWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService, @IElectronService private electronService: IElectronService ) { super(); @@ -43,9 +44,9 @@ export class RelayURLService extends URLService implements IURLHandler, IOpener let query = uri.query; if (!query) { - query = `windowId=${encodeURIComponent(this.electronEnvironmentService.windowId)}`; + query = `windowId=${encodeURIComponent(this.environmentService.configuration.windowId)}`; } else { - query += `&windowId=${encodeURIComponent(this.electronEnvironmentService.windowId)}`; + query += `&windowId=${encodeURIComponent(this.environmentService.configuration.windowId)}`; } return uri.with({ query }); diff --git a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts deleted file mode 100644 index c02286f7208..00000000000 --- a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.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 { SyncStatus, ISettingsSyncService, IConflictSetting, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { Emitter, Event } from 'vs/base/common/event'; -import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; - -export class SettingsSyncService extends Disposable implements ISettingsSyncService { - - _serviceBrand: undefined; - - private readonly channel: IChannel; - - readonly resourceKey = 'settings'; - readonly source = SyncSource.Settings; - - private _status: SyncStatus = SyncStatus.Uninitialized; - get status(): SyncStatus { return this._status; } - private _onDidChangeStatus: Emitter = this._register(new Emitter()); - readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; - - private _conflicts: IConflictSetting[] = []; - get conflicts(): IConflictSetting[] { return this._conflicts; } - private _onDidChangeConflicts: Emitter = this._register(new Emitter()); - readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; - - get onDidChangeLocal(): Event { return this.channel.listen('onDidChangeLocal'); } - - constructor( - @ISharedProcessService sharedProcessService: ISharedProcessService - ) { - super(); - this.channel = sharedProcessService.getChannel('settingsSync'); - this.channel.call('_getInitialStatus').then(status => { - this.updateStatus(status); - this._register(this.channel.listen('onDidChangeStatus')(status => this.updateStatus(status))); - }); - this.channel.call('_getInitialConflicts').then(conflicts => { - if (conflicts.length) { - this.updateConflicts(conflicts); - } - this._register(this.channel.listen('onDidChangeConflicts')(conflicts => this.updateConflicts(conflicts))); - }); - } - - pull(): Promise { - return this.channel.call('pull'); - } - - push(): Promise { - return this.channel.call('push'); - } - - sync(): Promise { - return this.channel.call('sync'); - } - - stop(): Promise { - return this.channel.call('stop'); - } - - resetLocal(): Promise { - return this.channel.call('resetLocal'); - } - - hasPreviouslySynced(): Promise { - return this.channel.call('hasPreviouslySynced'); - } - - hasLocalData(): Promise { - return this.channel.call('hasLocalData'); - } - - accept(content: string): Promise { - return this.channel.call('accept', [content]); - } - - resolveSettingsConflicts(conflicts: { key: string, value: any | undefined }[]): Promise { - return this.channel.call('resolveConflicts', [conflicts]); - } - - getRemoteContent(preview?: boolean): Promise { - return this.channel.call('getRemoteContent', [!!preview]); - } - - private async updateStatus(status: SyncStatus): Promise { - this._status = status; - this._onDidChangeStatus.fire(status); - } - - private async updateConflicts(conflicts: IConflictSetting[]): Promise { - this._conflicts = conflicts; - this._onDidChangeConflicts.fire(conflicts); - } - -} - -registerSingleton(ISettingsSyncService, SettingsSyncService); diff --git a/src/vs/workbench/services/userDataSync/electron-browser/storageKeysSyncRegistryService.ts b/src/vs/workbench/services/userDataSync/electron-browser/storageKeysSyncRegistryService.ts new file mode 100644 index 00000000000..ce4c1262861 --- /dev/null +++ b/src/vs/workbench/services/userDataSync/electron-browser/storageKeysSyncRegistryService.ts @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; +import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; +import { StorageKeysSyncRegistryChannelClient } from 'vs/platform/userDataSync/common/userDataSyncIpc'; + +class StorageKeysSyncRegistryService extends StorageKeysSyncRegistryChannelClient implements IStorageKeysSyncRegistryService { + + constructor( + @IMainProcessService mainProcessService: IMainProcessService + ) { + super(mainProcessService.getChannel('storageKeysSyncRegistryService')); + } + +} + +registerSingleton(IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService); diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts index b3ec8ffeb95..3cfea3362a7 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts @@ -3,13 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SyncStatus, SyncSource, IUserDataSyncService, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncStatus, SyncResource, IUserDataSyncService, UserDataSyncError, SyncResourceConflicts, ISyncResourceHandle } from 'vs/platform/userDataSync/common/userDataSync'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { URI } from 'vs/base/common/uri'; export class UserDataSyncService extends Disposable implements IUserDataSyncService { @@ -22,20 +23,20 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ private _onDidChangeStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; - get onDidChangeLocal(): Event { return this.channel.listen('onDidChangeLocal'); } + get onDidChangeLocal(): Event { return this.channel.listen('onDidChangeLocal'); } - private _conflictsSources: SyncSource[] = []; - get conflictsSources(): SyncSource[] { return this._conflictsSources; } - private _onDidChangeConflicts: Emitter = this._register(new Emitter()); - readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; + private _conflicts: SyncResourceConflicts[] = []; + get conflicts(): SyncResourceConflicts[] { return this._conflicts; } + private _onDidChangeConflicts: Emitter = this._register(new Emitter()); + readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; private _lastSyncTime: number | undefined = undefined; get lastSyncTime(): number | undefined { return this._lastSyncTime; } private _onDidChangeLastSyncTime: Emitter = this._register(new Emitter()); readonly onDidChangeLastSyncTime: Event = this._onDidChangeLastSyncTime.event; - private _onSyncErrors: Emitter<[SyncSource, UserDataSyncError][]> = this._register(new Emitter<[SyncSource, UserDataSyncError][]>()); - readonly onSyncErrors: Event<[SyncSource, UserDataSyncError][]> = this._onSyncErrors.event; + private _onSyncErrors: Emitter<[SyncResource, UserDataSyncError][]> = this._register(new Emitter<[SyncResource, UserDataSyncError][]>()); + readonly onSyncErrors: Event<[SyncResource, UserDataSyncError][]> = this._onSyncErrors.event; constructor( @ISharedProcessService sharedProcessService: ISharedProcessService @@ -51,7 +52,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return userDataSyncChannel.listen(event, arg); } }; - this.channel.call<[SyncStatus, SyncSource[], number | undefined]>('_getInitialData').then(([status, conflicts, lastSyncTime]) => { + this.channel.call<[SyncStatus, SyncResourceConflicts[], number | undefined]>('_getInitialData').then(([status, conflicts, lastSyncTime]) => { this.updateStatus(status); this.updateConflicts(conflicts); if (lastSyncTime) { @@ -60,8 +61,8 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this._register(this.channel.listen('onDidChangeStatus')(status => this.updateStatus(status))); this._register(this.channel.listen('onDidChangeLastSyncTime')(lastSyncTime => this.updateLastSyncTime(lastSyncTime))); }); - this._register(this.channel.listen('onDidChangeConflicts')(conflicts => this.updateConflicts(conflicts))); - this._register(this.channel.listen<[SyncSource, Error][]>('onSyncErrors')(errors => this._onSyncErrors.fire(errors.map(([source, error]) => ([source, UserDataSyncError.toUserDataSyncError(error)]))))); + this._register(this.channel.listen('onDidChangeConflicts')(conflicts => this.updateConflicts(conflicts))); + this._register(this.channel.listen<[SyncResource, Error][]>('onSyncErrors')(errors => this._onSyncErrors.fire(errors.map(([source, error]) => ([source, UserDataSyncError.toUserDataSyncError(error)]))))); } pull(): Promise { @@ -72,8 +73,8 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return this.channel.call('sync'); } - accept(source: SyncSource, content: string): Promise { - return this.channel.call('accept', [source, content]); + stop(): Promise { + return this.channel.call('stop'); } reset(): Promise { @@ -84,25 +85,46 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return this.channel.call('resetLocal'); } - stop(): Promise { - return this.channel.call('stop'); - } - - getRemoteContent(source: SyncSource, preview: boolean): Promise { - return this.channel.call('getRemoteContent', [source, preview]); - } - isFirstTimeSyncWithMerge(): Promise { return this.channel.call('isFirstTimeSyncWithMerge'); } + acceptConflict(conflict: URI, content: string): Promise { + return this.channel.call('acceptConflict', [conflict, content]); + } + + resolveContent(resource: URI): Promise { + return this.channel.call('resolveContent', [resource]); + } + + async getLocalSyncResourceHandles(resource: SyncResource): Promise { + const handles = await this.channel.call('getLocalSyncResourceHandles', [resource]); + return handles.map(({ created, uri }) => ({ created, uri: URI.revive(uri) })); + } + + async getRemoteSyncResourceHandles(resource: SyncResource): Promise { + const handles = await this.channel.call('getRemoteSyncResourceHandles', [resource]); + return handles.map(({ created, uri }) => ({ created, uri: URI.revive(uri) })); + } + + async getAssociatedResources(resource: SyncResource, syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]> { + const result = await this.channel.call<{ resource: URI, comparableResource?: URI }[]>('getAssociatedResources', [resource, syncResourceHandle]); + return result.map(({ resource, comparableResource }) => ({ resource: URI.revive(resource), comparableResource: URI.revive(comparableResource) })); + } + private async updateStatus(status: SyncStatus): Promise { this._status = status; this._onDidChangeStatus.fire(status); } - private async updateConflicts(conflicts: SyncSource[]): Promise { - this._conflictsSources = conflicts; + private async updateConflicts(conflicts: SyncResourceConflicts[]): Promise { + // Revive URIs + this._conflicts = conflicts.map(c => + ({ + syncResource: c.syncResource, + conflicts: c.conflicts.map(({ local, remote }) => + ({ local: URI.revive(local), remote: URI.revive(remote) })) + })); this._onDidChangeConflicts.fire(conflicts); } diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index 9a8b2d91a6a..f1c7886537c 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -14,8 +14,8 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Event, Emitter } from 'vs/base/common/event'; import { firstIndex } from 'vs/base/common/arrays'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { VIEW_ID as SEARCH_VIEW_ID } from 'vs/workbench/services/search/common/search'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; class CounterSet implements IReadableSet { @@ -192,6 +192,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor private readonly viewDescriptorCollections: Map; private readonly activeViewContextKeys: Map>; private readonly movableViewContextKeys: Map>; + private readonly defaultViewLocationContextKeys: Map>; private readonly viewsRegistry: IViewsRegistry; private readonly viewContainersRegistry: IViewContainersRegistry; @@ -219,13 +220,16 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor @IContextKeyService private readonly contextKeyService: IContextKeyService, @IStorageService private readonly storageService: IStorageService, @IExtensionService private readonly extensionService: IExtensionService, - @IConfigurationService private readonly configurationService: IConfigurationService + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, ) { super(); + storageKeysSyncRegistryService.registerStorageKey({ key: ViewDescriptorService.CACHED_VIEW_POSITIONS, version: 1 }); this.viewDescriptorCollections = new Map(); this.activeViewContextKeys = new Map>(); this.movableViewContextKeys = new Map>(); + this.defaultViewLocationContextKeys = new Map>(); this.viewContainersRegistry = Registry.as(ViewExtensions.ViewContainersRegistry); this.viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); @@ -254,33 +258,6 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor this._register(this.storageService.onDidChangeStorage((e) => { this.onDidStorageChange(e); })); this._register(this.extensionService.onDidRegisterExtensions(() => this.onDidRegisterExtensions())); - - this._register(this.configurationService.onDidChangeConfiguration((changeEvent) => { - if (changeEvent.affectedKeys.find(key => key === 'workbench.view.experimental.allowMovingToNewContainer')) { - if (this.viewsCanMoveSettingValue) { - return; - } - - // update all moved views to their default locations - for (const viewId of this.cachedViewInfo.keys()) { - if (viewId === SEARCH_VIEW_ID) { - continue; - } - - const viewDescriptor = this.getViewDescriptor(viewId); - const viewLocation = this.getViewContainer(viewId); - const defaultLocation = this.getDefaultContainer(viewId); - - if (viewDescriptor && viewLocation && defaultLocation && defaultLocation !== viewLocation) { - this.moveViews([viewDescriptor], viewLocation, defaultLocation); - } - } - } - })); - } - - private get viewsCanMoveSettingValue(): boolean { - return !!this.configurationService.getValue('workbench.view.experimental.allowMovingToNewContainer'); } private registerGroupedViews(groupedViews: Map): void { @@ -377,7 +354,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } private shouldGenerateContainer(containerInfo: ICachedViewContainerInfo): boolean { - return !!containerInfo.sourceViewId && containerInfo.location !== undefined && (this.viewsCanMoveSettingValue || containerInfo.sourceViewId === SEARCH_VIEW_ID); + return !!containerInfo.sourceViewId && containerInfo.location !== undefined; } private onDidDeregisterViews(views: IViewDescriptor[], viewContainer: ViewContainer): void { @@ -445,16 +422,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } moveViewToLocation(view: IViewDescriptor, location: ViewContainerLocation): void { - const previousContainer = this.getViewContainer(view.id); - if (previousContainer && this.getViewContainerLocation(previousContainer) === location) { - return; - } - - let container = this.getDefaultContainer(view.id)!; - if (this.getViewContainerLocation(container) !== location) { - container = this.registerViewContainerForSingleView(view, location); - } - + let container = this.registerViewContainerForSingleView(view, location); this.moveViewsToContainer([view], container); } @@ -486,6 +454,43 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor if (!skipCacheUpdate) { this.saveViewPositionsToCache(); + + const containerToString = (container: ViewContainer): string => { + if (container.id.startsWith(ViewDescriptorService.COMMON_CONTAINER_ID_PREFIX)) { + return 'custom'; + } + + if (!container.extensionId) { + return container.id; + } + + return 'extension'; + }; + + // Log on cache update to avoid duplicate events in other windows + const viewCount = views.length; + const fromContainer = containerToString(from); + const toContainer = containerToString(to); + const fromLocation = oldLocation === ViewContainerLocation.Panel ? 'panel' : 'sidebar'; + const toLocation = newLocation === ViewContainerLocation.Panel ? 'panel' : 'sidebar'; + + interface ViewDescriptorServiceMoveViewsEvent { + viewCount: number; + fromContainer: string; + toContainer: string; + fromLocation: string; + toLocation: string; + } + + type ViewDescriptorServiceMoveViewsClassification = { + viewCount: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + fromContainer: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + toContainer: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + fromLocation: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + toLocation: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + }; + + this.telemetryService.publicLog2('viewDescriptorService.moveViews', { viewCount, fromContainer, toContainer, fromLocation, toLocation }); } } @@ -496,7 +501,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor id, ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }]), name: sourceView.name, - icon: sourceView.containerIcon, + icon: location === ViewContainerLocation.Sidebar ? sourceView.containerIcon || 'codicon-window' : undefined, hideIfEmpty: true }, location); } @@ -680,12 +685,17 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor const sourceViewId = this.generatedContainerSourceViewIds.get(container.id); views.forEach(view => { this.cachedViewInfo.set(view.id, { containerId: container.id, location, sourceViewId }); + this.getOrCreateDefaultViewLocationContextKey(view).set(this.getDefaultContainer(view.id) === container); }); this.getViewDescriptors(container).addViews(views); } private removeViews(container: ViewContainer, views: IViewDescriptor[]): void { + // Set view default location keys to false + views.forEach(view => this.getOrCreateDefaultViewLocationContextKey(view).set(false)); + + // Remove the views this.getViewDescriptors(container).removeViews(views); } @@ -708,6 +718,16 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } return contextKey; } + + private getOrCreateDefaultViewLocationContextKey(viewDescriptor: IViewDescriptor): IContextKey { + const defaultViewLocationContextKeyId = `${viewDescriptor.id}.defaultViewLocation`; + let contextKey = this.defaultViewLocationContextKeys.get(defaultViewLocationContextKeyId); + if (!contextKey) { + contextKey = new RawContextKey(defaultViewLocationContextKeyId, false).bindTo(this.contextKeyService); + this.defaultViewLocationContextKeys.set(defaultViewLocationContextKeyId, contextKey); + } + return contextKey; + } } registerSingleton(IViewDescriptorService, ViewDescriptorService); diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyFileOperationParticipant.ts b/src/vs/workbench/services/workingCopy/common/workingCopyFileOperationParticipant.ts index 0b194d5d1bf..4b0928035ee 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyFileOperationParticipant.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyFileOperationParticipant.ts @@ -13,6 +13,7 @@ import { IWorkingCopyFileOperationParticipant } from 'vs/workbench/services/work import { URI } from 'vs/base/common/uri'; import { FileOperation } from 'vs/platform/files/common/files'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { insert } from 'vs/base/common/arrays'; export class WorkingCopyFileOperationParticipant extends Disposable { @@ -27,9 +28,9 @@ export class WorkingCopyFileOperationParticipant extends Disposable { } addFileOperationParticipant(participant: IWorkingCopyFileOperationParticipant): IDisposable { - this.participants.push(participant); + const remove = insert(this.participants, participant); - return toDisposable(() => this.participants.splice(this.participants.indexOf(participant), 1)); + return toDisposable(() => remove()); } async participate(target: URI, source: URI | undefined, operation: FileOperation): Promise { diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts index 7b7e1bff3bc..1bfc60d2b8b 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts @@ -6,8 +6,9 @@ import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Event, AsyncEmitter, IWaitUntil } from 'vs/base/common/event'; +import { insert } from 'vs/base/common/arrays'; import { URI } from 'vs/base/common/uri'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IFileService, FileOperation, IFileStatWithMetadata } from 'vs/platform/files/common/files'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; @@ -57,6 +58,11 @@ export interface IWorkingCopyFileOperationParticipant { ): Promise; } +/** + * Returns the working copies for a given resource. + */ +type WorkingCopyProvider = (resourceOrFolder: URI) => IWorkingCopy[]; + /** * A service that allows to perform file operations with working copy support. * Any operation that would leave a stale dirty working copy behind will make @@ -142,9 +148,15 @@ export interface IWorkingCopyFileService { //#endregion - //#region Path related + /** + * Register a new provider for working copies based on a resource. + * + * @return a disposable that unregisters the provider. + */ + registerWorkingCopyProvider(provider: WorkingCopyProvider): IDisposable; + /** * Will return all working copies that are dirty matching the provided resource. * If the resource is a folder and the scheme supports file operations, a working @@ -180,6 +192,20 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); + + // register a default working copy provider that uses the working copy service + this.registerWorkingCopyProvider(resource => { + return this.workingCopyService.workingCopies.filter(workingCopy => { + if (this.fileService.canHandleResource(resource)) { + // only check for parents if the resource can be handled + // by the file system where we then assume a folder like + // path structure + return isEqualOrParent(workingCopy.resource, resource); + } + + return isEqual(workingCopy.resource, resource); + }); + }); } async move(source: URI, target: URI, overwrite?: boolean): Promise { @@ -275,17 +301,23 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi //#region Path related - getDirty(resource: URI): IWorkingCopy[] { - return this.workingCopyService.dirtyWorkingCopies.filter(dirty => { - if (this.fileService.canHandleResource(resource)) { - // only check for parents if the resource can be handled - // by the file system where we then assume a folder like - // path structure - return isEqualOrParent(dirty.resource, resource); - } + private readonly workingCopyProviders: WorkingCopyProvider[] = []; - return isEqual(dirty.resource, resource); - }); + registerWorkingCopyProvider(provider: WorkingCopyProvider): IDisposable { + const remove = insert(this.workingCopyProviders, provider); + return toDisposable(remove); + } + + getDirty(resource: URI): IWorkingCopy[] { + const dirtyWorkingCopies = new Set(); + for (const provider of this.workingCopyProviders) { + for (const workingCopy of provider(resource)) { + if (workingCopy.isDirty()) { + dirtyWorkingCopies.add(workingCopy); + } + } + } + return Array.from(dirtyWorkingCopies); } //#endregion diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts index 7be4f335d23..c63fce817a9 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts @@ -8,7 +8,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Event, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { Disposable, IDisposable, toDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; -import { TernarySearchTree, values } from 'vs/base/common/map'; +import { ResourceMap } from 'vs/base/common/map'; import { ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; import { ITextSnapshot } from 'vs/editor/common/model'; @@ -27,12 +27,12 @@ export const enum WorkingCopyCapabilities { * `IBackupFileService.resolve(workingCopy.resource)` to * retrieve the backup when loading the working copy. */ -export interface IWorkingCopyBackup { +export interface IWorkingCopyBackup { /** * Any serializable metadata to be associated with the backup. */ - meta?: object; + meta?: MetaType; /** * Use this for larger textual content of the backup. @@ -42,10 +42,20 @@ export interface IWorkingCopyBackup { export interface IWorkingCopy { + /** + * The unique resource of the working copy. There can only be one + * working copy in the system with the same URI. + */ readonly resource: URI; + /** + * Human readable name of the working copy. + */ readonly name: string; + /** + * The capabilities of the working copy. + */ readonly capabilities: WorkingCopyCapabilities; @@ -83,15 +93,22 @@ export interface IWorkingCopy { * * Providers of working copies should use `IBackupFileService.resolve(workingCopy.resource)` * to retrieve the backup metadata associated when loading the working copy. - * - * Not providing this method from the working copy will disable any - * backups and hot-exit functionality for those working copies. */ - backup?(): Promise; + backup(): Promise; + /** + * Asks the working copy to save. If the working copy was dirty, it is + * expected to be non-dirty after this operation has finished. + * + * @returns `true` if the operation was successful and `false` otherwise. + */ save(options?: ISaveOptions): Promise; - revert(options?: IRevertOptions): Promise; + /** + * Asks the working copy to revert. If the working copy was dirty, it is + * expected to be non-dirty after this operation has finished. + */ + revert(options?: IRevertOptions): Promise; //#endregion } @@ -133,8 +150,12 @@ export interface IWorkingCopyService { readonly workingCopies: IWorkingCopy[]; - getWorkingCopies(resource: URI): IWorkingCopy[]; - + /** + * Register a new working copy with the service. This method will + * throw if you try to register a working copy with a resource + * that was already registered before. There can only be 1 working + * copy per resource registered to the service. + */ registerWorkingCopy(workingCopy: IWorkingCopy): IDisposable; //#endregion @@ -163,30 +184,21 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic //#region Registry - private readonly mapResourceToWorkingCopy = TernarySearchTree.forPaths>(); - - get workingCopies(): IWorkingCopy[] { return values(this._workingCopies); } + get workingCopies(): IWorkingCopy[] { return Array.from(this._workingCopies.values()); } private _workingCopies = new Set(); - getWorkingCopies(resource: URI): IWorkingCopy[] { - const workingCopies = this.mapResourceToWorkingCopy.get(resource.toString()); - - return workingCopies ? values(workingCopies) : []; - } + private readonly mapResourceToWorkingCopy = new ResourceMap(); registerWorkingCopy(workingCopy: IWorkingCopy): IDisposable { + if (this.mapResourceToWorkingCopy.has(workingCopy.resource)) { + throw new Error(`Cannot register more than one working copy with the same resource ${workingCopy.resource.toString()}.`); + } + const disposables = new DisposableStore(); // Registry - let workingCopiesForResource = this.mapResourceToWorkingCopy.get(workingCopy.resource.toString()); - if (!workingCopiesForResource) { - workingCopiesForResource = new Set(); - this.mapResourceToWorkingCopy.set(workingCopy.resource.toString(), workingCopiesForResource); - } - - workingCopiesForResource.add(workingCopy); - this._workingCopies.add(workingCopy); + this.mapResourceToWorkingCopy.set(workingCopy.resource, workingCopy); // Wire in Events disposables.add(workingCopy.onDidChangeContent(() => this._onDidChangeContent.fire(workingCopy))); @@ -210,12 +222,8 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic private unregisterWorkingCopy(workingCopy: IWorkingCopy): void { // Remove from registry - const workingCopiesForResource = this.mapResourceToWorkingCopy.get(workingCopy.resource.toString()); - if (workingCopiesForResource && workingCopiesForResource.delete(workingCopy) && workingCopiesForResource.size === 0) { - this.mapResourceToWorkingCopy.delete(workingCopy.resource.toString()); - } - this._workingCopies.delete(workingCopy); + this.mapResourceToWorkingCopy.delete(workingCopy.resource); // If copy is dirty, ensure to fire an event to signal the dirty change // (a disposed working copy cannot account for being dirty in our model) @@ -256,13 +264,9 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic } isDirty(resource: URI): boolean { - const workingCopies = this.mapResourceToWorkingCopy.get(resource.toString()); - if (workingCopies) { - for (const workingCopy of workingCopies) { - if (workingCopy.isDirty()) { - return true; - } - } + const workingCopy = this.mapResourceToWorkingCopy.get(resource); + if (workingCopy) { + return workingCopy.isDirty(); } return false; diff --git a/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts b/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts index 9d05c8dd213..d533349c9ae 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts @@ -11,6 +11,7 @@ import { toResource } from 'vs/base/test/common/utils'; import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; import { URI } from 'vs/base/common/uri'; import { FileOperation } from 'vs/platform/files/common/files'; +import { TestWorkingCopy } from 'vs/workbench/services/workingCopy/test/common/workingCopyService.test'; suite('WorkingCopyFileService', () => { @@ -186,4 +187,29 @@ suite('WorkingCopyFileService', () => { model1.dispose(); model2.dispose(); }); + + test('registerWorkingCopyProvider', async function () { + const model1 = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file-1.txt'), 'utf8', undefined); + (accessor.textFileService.files).add(model.resource, model); + await model1.load(); + model1.textEditorModel!.setValue('foo'); + + const testWorkingCopy = new TestWorkingCopy(toResource.call(this, '/path/file-2.txt'), true); + const registration = accessor.workingCopyFileService.registerWorkingCopyProvider(() => { + return [model1, testWorkingCopy]; + }); + + let dirty = accessor.workingCopyFileService.getDirty(model1.resource); + assert.strictEqual(dirty.length, 2, 'Should return default working copy + working copy from provider'); + assert.strictEqual(dirty[0], model1); + assert.strictEqual(dirty[1], testWorkingCopy); + + registration.dispose(); + + dirty = accessor.workingCopyFileService.getDirty(model1.resource); + assert.strictEqual(dirty.length, 1, 'Should have unregistered our provider'); + assert.strictEqual(dirty[0], model1); + + model1.dispose(); + }); }); diff --git a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts index 4e189be6867..50fbeff1806 100644 --- a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts @@ -12,67 +12,66 @@ import { TestWorkingCopyService } from 'vs/workbench/test/common/workbenchTestSe import { ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; import { basename } from 'vs/base/common/resources'; -suite('WorkingCopyService', () => { +export class TestWorkingCopy extends Disposable implements IWorkingCopy { - class TestWorkingCopy extends Disposable implements IWorkingCopy { + private readonly _onDidChangeDirty = this._register(new Emitter()); + readonly onDidChangeDirty = this._onDidChangeDirty.event; - private readonly _onDidChangeDirty = this._register(new Emitter()); - readonly onDidChangeDirty = this._onDidChangeDirty.event; + private readonly _onDidChangeContent = this._register(new Emitter()); + readonly onDidChangeContent = this._onDidChangeContent.event; - private readonly _onDidChangeContent = this._register(new Emitter()); - readonly onDidChangeContent = this._onDidChangeContent.event; + private readonly _onDispose = this._register(new Emitter()); + readonly onDispose = this._onDispose.event; - private readonly _onDispose = this._register(new Emitter()); - readonly onDispose = this._onDispose.event; + readonly capabilities = 0; - readonly capabilities = 0; + readonly name = basename(this.resource); - readonly name = basename(this.resource); + private dirty = false; - private dirty = false; + constructor(public readonly resource: URI, isDirty = false) { + super(); - constructor(public readonly resource: URI, isDirty = false) { - super(); + this.dirty = isDirty; + } - this.dirty = isDirty; - } - - setDirty(dirty: boolean): void { - if (this.dirty !== dirty) { - this.dirty = dirty; - this._onDidChangeDirty.fire(); - } - } - - setContent(content: string): void { - this._onDidChangeContent.fire(); - } - - isDirty(): boolean { - return this.dirty; - } - - async save(options?: ISaveOptions): Promise { - return true; - } - - async revert(options?: IRevertOptions): Promise { - this.setDirty(false); - - return true; - } - - async backup(): Promise { - return {}; - } - - dispose(): void { - this._onDispose.fire(); - - super.dispose(); + setDirty(dirty: boolean): void { + if (this.dirty !== dirty) { + this.dirty = dirty; + this._onDidChangeDirty.fire(); } } + setContent(content: string): void { + this._onDidChangeContent.fire(); + } + + isDirty(): boolean { + return this.dirty; + } + + async save(options?: ISaveOptions): Promise { + return true; + } + + async revert(options?: IRevertOptions): Promise { + this.setDirty(false); + } + + async backup(): Promise { + return {}; + } + + dispose(): void { + this._onDispose.fire(); + + super.dispose(); + } +} + +suite('WorkingCopyService', () => { + + test('registry - basics', () => { const service = new TestWorkingCopyService(); @@ -112,8 +111,8 @@ suite('WorkingCopyService', () => { assert.equal(service.dirtyCount, 1); assert.equal(service.dirtyWorkingCopies.length, 1); assert.equal(service.dirtyWorkingCopies[0], copy1); - assert.equal(service.getWorkingCopies(copy1.resource).length, 1); - assert.equal(service.getWorkingCopies(copy1.resource)[0], copy1); + assert.equal(service.workingCopies.length, 1); + assert.equal(service.workingCopies[0], copy1); assert.equal(service.isDirty(resource1), true); assert.equal(service.hasDirty, true); assert.equal(onDidChangeDirty.length, 1); @@ -167,7 +166,7 @@ suite('WorkingCopyService', () => { assert.equal(onDidChangeDirty[3], copy2); }); - test('registry - multiple copies on same resource', () => { + test('registry - multiple copies on same resource throws', () => { const service = new TestWorkingCopyService(); const onDidChangeDirty: IWorkingCopy[] = []; @@ -176,37 +175,10 @@ suite('WorkingCopyService', () => { const resource = URI.parse('custom://some/folder/custom.txt'); const copy1 = new TestWorkingCopy(resource); - const unregister1 = service.registerWorkingCopy(copy1); + service.registerWorkingCopy(copy1); const copy2 = new TestWorkingCopy(resource); - const unregister2 = service.registerWorkingCopy(copy2); - assert.equal(service.getWorkingCopies(copy1.resource).length, 2); - assert.equal(service.getWorkingCopies(copy1.resource)[0], copy1); - assert.equal(service.getWorkingCopies(copy1.resource)[1], copy2); - - copy1.setDirty(true); - - assert.equal(service.dirtyCount, 1); - assert.equal(onDidChangeDirty.length, 1); - assert.equal(service.isDirty(resource), true); - - copy2.setDirty(true); - - assert.equal(service.dirtyCount, 2); - assert.equal(onDidChangeDirty.length, 2); - assert.equal(service.isDirty(resource), true); - - unregister1.dispose(); - - assert.equal(service.dirtyCount, 1); - assert.equal(onDidChangeDirty.length, 3); - assert.equal(service.isDirty(resource), true); - - unregister2.dispose(); - - assert.equal(service.dirtyCount, 0); - assert.equal(onDidChangeDirty.length, 4); - assert.equal(service.isDirty(resource), false); + assert.throws(() => service.registerWorkingCopy(copy2)); }); }); diff --git a/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts b/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts index 8db7376d378..3d651534079 100644 --- a/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IJSONEditingService, JSONEditingError, JSONEditingErrorCode } from 'vs/workbench/services/configuration/common/jsonEditing'; -import { IWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspacesService, rewriteWorkspaceFileForNewLocation, WORKSPACE_FILTER, IEnterWorkspaceResult, hasWorkspaceFileExtension, WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspacesService, rewriteWorkspaceFileForNewLocation, WORKSPACE_FILTER, IEnterWorkspaceResult, hasWorkspaceFileExtension, WORKSPACE_EXTENSION, isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; import { ConfigurationScope, IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -234,9 +234,11 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi return; } + const isFromUntitledWorkspace = isUntitledWorkspace(configPathURI, this.environmentService); + // Read the contents of the workspace file, update it to new location and save it. const raw = await this.fileService.readFile(configPathURI); - const newRawWorkspaceContents = rewriteWorkspaceFileForNewLocation(raw.value.toString(), configPathURI, targetConfigPathURI); + const newRawWorkspaceContents = rewriteWorkspaceFileForNewLocation(raw.value.toString(), configPathURI, isFromUntitledWorkspace, targetConfigPathURI); await this.textFileService.create(targetConfigPathURI, newRawWorkspaceContents, { overwrite: true }); } diff --git a/src/vs/workbench/services/workspaces/browser/workspacesService.ts b/src/vs/workbench/services/workspaces/browser/workspacesService.ts index 9d41e4ea7a1..7bbd6581909 100644 --- a/src/vs/workbench/services/workspaces/browser/workspacesService.ts +++ b/src/vs/workbench/services/workspaces/browser/workspacesService.ts @@ -6,7 +6,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkspacesService, IWorkspaceFolderCreationData, IWorkspaceIdentifier, IEnterWorkspaceResult, IRecentlyOpened, restoreRecentlyOpened, IRecent, isRecentFile, isRecentFolder, toStoreData, IStoredWorkspaceFolder, getStoredWorkspaceFolder, WORKSPACE_EXTENSION, IStoredWorkspace } from 'vs/platform/workspaces/common/workspaces'; import { URI } from 'vs/base/common/uri'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { ILogService } from 'vs/platform/log/common/log'; @@ -16,6 +16,7 @@ import { IFileService, FileOperationError, FileOperationResult } from 'vs/platfo import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { joinPath } from 'vs/base/common/resources'; import { VSBuffer } from 'vs/base/common/buffer'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; export class BrowserWorkspacesService extends Disposable implements IWorkspacesService { @@ -23,18 +24,22 @@ export class BrowserWorkspacesService extends Disposable implements IWorkspacesS _serviceBrand: undefined; - private readonly _onRecentlyOpenedChange: Emitter = this._register(new Emitter()); - readonly onRecentlyOpenedChange: Event = this._onRecentlyOpenedChange.event; + private readonly _onRecentlyOpenedChange = this._register(new Emitter()); + readonly onRecentlyOpenedChange = this._onRecentlyOpenedChange.event; constructor( @IStorageService private readonly storageService: IStorageService, @IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService, @ILogService private readonly logService: ILogService, @IFileService private readonly fileService: IFileService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService ) { super(); + // opt-in to syncing + storageKeysSyncRegistryService.registerStorageKey({ key: BrowserWorkspacesService.RECENTLY_OPENED_KEY, version: 1 }); + // Opening a workspace should push it as most // recently used to the workspaces history this.addWorkspaceToRecentlyOpened(); @@ -134,7 +139,7 @@ export class BrowserWorkspacesService extends Disposable implements IWorkspacesS const storedWorkspaceFolder: IStoredWorkspaceFolder[] = []; if (folders) { for (const folder of folders) { - storedWorkspaceFolder.push(getStoredWorkspaceFolder(folder.uri, folder.name, this.environmentService.untitledWorkspacesHome)); + storedWorkspaceFolder.push(getStoredWorkspaceFolder(folder.uri, true, folder.name, this.environmentService.untitledWorkspacesHome)); } } @@ -160,6 +165,15 @@ export class BrowserWorkspacesService extends Disposable implements IWorkspacesS } //#endregion + + + //#region Dirty Workspaces + + async getDirtyWorkspaces(): Promise> { + return []; // Currently not supported in web + } + + //#endregion } registerSingleton(IWorkspacesService, BrowserWorkspacesService, true); diff --git a/src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts b/src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts index d65ea2c055e..144b9b16f8e 100644 --- a/src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts @@ -31,6 +31,7 @@ import { IElectronService } from 'vs/platform/electron/node/electron'; import { isMacintosh } from 'vs/base/common/platform'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { BackupFileService } from 'vs/workbench/services/backup/common/backupFileService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingService { @@ -49,7 +50,7 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi @IFileService fileService: IFileService, @ITextFileService textFileService: ITextFileService, @IWorkspacesService workspacesService: IWorkspacesService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService protected environmentService: INativeWorkbenchEnvironmentService, @IFileDialogService fileDialogService: IFileDialogService, @IDialogService protected dialogService: IDialogService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @@ -139,8 +140,6 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi return false; } } - - return false; } async isValidTargetWorkspacePath(path: URI): Promise { diff --git a/src/vs/workbench/services/workspaces/electron-browser/workspacesService.ts b/src/vs/workbench/services/workspaces/electron-browser/workspacesService.ts index 81e178fc33d..22707759bd2 100644 --- a/src/vs/workbench/services/workspaces/electron-browser/workspacesService.ts +++ b/src/vs/workbench/services/workspaces/electron-browser/workspacesService.ts @@ -6,8 +6,9 @@ import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; import { createChannelSender } from 'vs/base/parts/ipc/node/ipc'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; export class NativeWorkspacesService { @@ -15,9 +16,9 @@ export class NativeWorkspacesService { constructor( @IMainProcessService mainProcessService: IMainProcessService, - @IElectronEnvironmentService electronEnvironmentService: IElectronEnvironmentService + @IWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService ) { - return createChannelSender(mainProcessService.getChannel('workspaces'), { context: electronEnvironmentService.windowId }); + return createChannelSender(mainProcessService.getChannel('workspaces'), { context: environmentService.configuration.windowId }); } } diff --git a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts index ccdf0740f5b..768e9528a37 100644 --- a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts @@ -41,10 +41,10 @@ import 'vs/editor/contrib/codelens/codelens'; import 'vs/editor/contrib/colorPicker/color'; import 'vs/editor/contrib/format/format'; import 'vs/editor/contrib/gotoSymbol/goToCommands'; +import 'vs/editor/contrib/gotoSymbol/documentSymbols'; import 'vs/editor/contrib/hover/getHover'; import 'vs/editor/contrib/links/getLinks'; import 'vs/editor/contrib/parameterHints/provideSignatureHelp'; -import 'vs/editor/contrib/quickOpen/quickOpen'; import 'vs/editor/contrib/smartSelect/smartSelect'; import 'vs/editor/contrib/suggest/suggest'; @@ -895,7 +895,7 @@ suite('ExtHostLanguageFeatureCommands', function () { disposables.push(extHost.registerCallHierarchyProvider(nullExtensionDescription, defaultSelector, new class implements vscode.CallHierarchyProvider { - prepareCallHierarchy(document: vscode.TextDocument, position: vscode.Position, ): vscode.ProviderResult { + prepareCallHierarchy(document: vscode.TextDocument, position: vscode.Position,): vscode.ProviderResult { return new types.CallHierarchyItem(types.SymbolKind.Constant, 'ROOT', 'ROOT', document.uri, new types.Range(0, 0, 0, 0), new types.Range(0, 0, 0, 0)); } @@ -931,4 +931,53 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.equal(outgoing.length, 1); assert.equal(outgoing[0].to.name, 'OUTGOING'); }); + + test('selectionRangeProvider on inner array always returns outer array #91852', async function () { + + disposables.push(extHost.registerSelectionRangeProvider(nullExtensionDescription, defaultSelector, { + provideSelectionRanges(_doc, positions) { + const [first] = positions; + return [ + new types.SelectionRange(new types.Range(first.line, first.character, first.line, first.character)), + ]; + } + })); + + await rpcProtocol.sync(); + let value = await commands.executeCommand('vscode.executeSelectionRangeProvider', model.uri, [new types.Position(0, 10)]); + assert.equal(value.length, 1); + assert.equal(value[0].range.start.line, 0); + assert.equal(value[0].range.start.character, 10); + assert.equal(value[0].range.end.line, 0); + assert.equal(value[0].range.end.character, 10); + }); + + test('selectionRangeProvider on inner array always returns outer array #91852', async function () { + + disposables.push(extHost.registerSelectionRangeProvider(nullExtensionDescription, defaultSelector, { + provideSelectionRanges(_doc, positions) { + const [first, second] = positions; + return [ + new types.SelectionRange(new types.Range(first.line, first.character, first.line, first.character)), + new types.SelectionRange(new types.Range(second.line, second.character, second.line, second.character)), + ]; + } + })); + + await rpcProtocol.sync(); + let value = await commands.executeCommand( + 'vscode.executeSelectionRangeProvider', + model.uri, + [new types.Position(0, 0), new types.Position(0, 10)] + ); + assert.equal(value.length, 2); + assert.equal(value[0].range.start.line, 0); + assert.equal(value[0].range.start.character, 0); + assert.equal(value[0].range.end.line, 0); + assert.equal(value[0].range.end.character, 0); + assert.equal(value[1].range.start.line, 0); + assert.equal(value[1].range.start.character, 10); + assert.equal(value[1].range.end.line, 0); + assert.equal(value[1].range.end.character, 10); + }); }); diff --git a/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts index 148148e352e..02c2209624b 100644 --- a/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts @@ -20,7 +20,7 @@ import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { MainThreadCommands } from 'vs/workbench/api/browser/mainThreadCommands'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; -import { getDocumentSymbols } from 'vs/editor/contrib/quickOpen/quickOpen'; +import { getDocumentSymbols } from 'vs/editor/contrib/gotoSymbol/documentSymbols'; import * as modes from 'vs/editor/common/modes'; import { getCodeLensData } from 'vs/editor/contrib/codelens/codelens'; import { getDefinitionsAtPosition, getImplementationsAtPosition, getTypeDefinitionsAtPosition, getDeclarationsAtPosition, getReferencesAtPosition } from 'vs/editor/contrib/gotoSymbol/goToSymbol'; diff --git a/src/vs/workbench/test/browser/api/extHostTypes.test.ts b/src/vs/workbench/test/browser/api/extHostTypes.test.ts index 25bf62c4691..92e9616c1a8 100644 --- a/src/vs/workbench/test/browser/api/extHostTypes.test.ts +++ b/src/vs/workbench/test/browser/api/extHostTypes.test.ts @@ -566,4 +566,76 @@ suite('ExtHostTypes', function () { assert.ok(!types.CodeActionKind.RefactorExtract.intersects(types.CodeActionKind.Empty.append('other').append('refactor'))); assert.ok(!types.CodeActionKind.RefactorExtract.intersects(types.CodeActionKind.Empty.append('refactory'))); }); + + function toArr(uint32Arr: Uint32Array): number[] { + const r = []; + for (let i = 0, len = uint32Arr.length; i < len; i++) { + r[i] = uint32Arr[i]; + } + return r; + } + + test('SemanticTokensBuilder simple', () => { + const builder = new types.SemanticTokensBuilder(); + builder.push(1, 0, 5, 1, 1); + builder.push(1, 10, 4, 2, 2); + builder.push(2, 2, 3, 2, 2); + assert.deepEqual(toArr(builder.build().data), [ + 1, 0, 5, 1, 1, + 0, 10, 4, 2, 2, + 1, 2, 3, 2, 2 + ]); + }); + + test('SemanticTokensBuilder no modifier', () => { + const builder = new types.SemanticTokensBuilder(); + builder.push(1, 0, 5, 1); + builder.push(1, 10, 4, 2); + builder.push(2, 2, 3, 2); + assert.deepEqual(toArr(builder.build().data), [ + 1, 0, 5, 1, 0, + 0, 10, 4, 2, 0, + 1, 2, 3, 2, 0 + ]); + }); + + test('SemanticTokensBuilder out of order 1', () => { + const builder = new types.SemanticTokensBuilder(); + builder.push(2, 0, 5, 1, 1); + builder.push(2, 10, 1, 2, 2); + builder.push(2, 15, 2, 3, 3); + builder.push(1, 0, 4, 4, 4); + assert.deepEqual(toArr(builder.build().data), [ + 1, 0, 4, 4, 4, + 1, 0, 5, 1, 1, + 0, 10, 1, 2, 2, + 0, 5, 2, 3, 3 + ]); + }); + + test('SemanticTokensBuilder out of order 2', () => { + const builder = new types.SemanticTokensBuilder(); + builder.push(2, 10, 5, 1, 1); + builder.push(2, 2, 4, 2, 2); + assert.deepEqual(toArr(builder.build().data), [ + 2, 2, 4, 2, 2, + 0, 8, 5, 1, 1 + ]); + }); + + test('SemanticTokensBuilder with legend', () => { + const legend = new types.SemanticTokensLegend( + ['aType', 'bType', 'cType', 'dType'], + ['mod0', 'mod1', 'mod2', 'mod3', 'mod4', 'mod5'] + ); + const builder = new types.SemanticTokensBuilder(legend); + builder.push(new types.Range(1, 0, 1, 5), 'bType'); + builder.push(new types.Range(2, 0, 2, 4), 'cType', ['mod0', 'mod5']); + builder.push(new types.Range(3, 0, 3, 3), 'dType', ['mod2', 'mod4']); + assert.deepEqual(toArr(builder.build().data), [ + 1, 0, 5, 1, 0, + 1, 0, 4, 2, 1 | (1 << 5), + 1, 0, 3, 3, (1 << 2) | (1 << 4) + ]); + }); }); diff --git a/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts b/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts index 4101a887f69..46e4713ca08 100644 --- a/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts @@ -13,7 +13,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { ExtHostDocumentsAndEditorsShape, IDocumentsAndEditorsDelta } from 'vs/workbench/api/common/extHost.protocol'; import { createTestCodeEditor, TestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { mock } from 'vs/workbench/test/browser/api/mock'; -import { TestEditorService, TestEditorGroupsService, TestTextResourcePropertiesService, TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEditorService, TestEditorGroupsService, TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices'; import { Event } from 'vs/base/common/event'; import { ITextModel } from 'vs/editor/common/model'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -25,6 +25,7 @@ import { NullLogService } from 'vs/platform/log/common/log'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; +import { TestTextResourcePropertiesService } from 'vs/workbench/test/common/workbenchTestServices'; suite('MainThreadDocumentsAndEditors', () => { @@ -50,7 +51,7 @@ suite('MainThreadDocumentsAndEditors', () => { const dialogService = new TestDialogService(); const notificationService = new TestNotificationService(); const undoRedoService = new UndoRedoService(dialogService, notificationService); - modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), undoRedoService); + modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), undoRedoService, dialogService); codeEditorService = new TestCodeEditorService(); textFileService = new class extends mock() { isDirty() { return false; } diff --git a/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts b/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts index 0d96189e4c9..7e8645f2c40 100644 --- a/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts @@ -19,7 +19,7 @@ import { Range } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; import { IModelService } from 'vs/editor/common/services/modelService'; import { EditOperation } from 'vs/editor/common/core/editOperation'; -import { TestFileService, TestEditorService, TestEditorGroupsService, TestEnvironmentService, TestContextService, TestTextResourcePropertiesService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestFileService, TestEditorService, TestEditorGroupsService, TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices'; import { BulkEditService } from 'vs/workbench/services/bulkEdit/browser/bulkEditService'; import { NullLogService, ILogService } from 'vs/platform/log/common/log'; import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; @@ -47,6 +47,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { TestTextResourcePropertiesService, TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; suite('MainThreadEditors', () => { @@ -72,7 +73,7 @@ suite('MainThreadEditors', () => { const dialogService = new TestDialogService(); const notificationService = new TestNotificationService(); const undoRedoService = new UndoRedoService(dialogService, notificationService); - modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), undoRedoService); + modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), undoRedoService, dialogService); const services = new ServiceCollection(); diff --git a/src/vs/workbench/test/browser/part.test.ts b/src/vs/workbench/test/browser/part.test.ts index 63946bb4257..a2bd94ea0ec 100644 --- a/src/vs/workbench/test/browser/part.test.ts +++ b/src/vs/workbench/test/browser/part.test.ts @@ -8,8 +8,9 @@ import { Part } from 'vs/workbench/browser/part'; import * as Types from 'vs/base/common/types'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { append, $, hide } from 'vs/base/browser/dom'; -import { TestStorageService, TestLayoutService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestLayoutService } from 'vs/workbench/test/browser/workbenchTestServices'; import { StorageScope } from 'vs/platform/storage/common/storage'; +import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; class SimplePart extends Part { diff --git a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts index 19ba3f55d7c..635c4c865fe 100644 --- a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts @@ -11,7 +11,7 @@ import * as Platform from 'vs/platform/registry/common/platform'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { workbenchInstantiationService, TestEditorGroupView, TestEditorGroupsService, TestStorageService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestEditorGroupView, TestEditorGroupsService } from 'vs/workbench/test/browser/workbenchTestServices'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { URI } from 'vs/base/common/uri'; @@ -19,6 +19,7 @@ import { IEditorRegistry, Extensions, EditorDescriptor } from 'vs/workbench/brow import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorModel } from 'vs/platform/editor/common/editor'; import { dispose } from 'vs/base/common/lifecycle'; +import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; const NullThemeService = new TestThemeService(); @@ -92,7 +93,7 @@ class MyOtherInput extends EditorInput { return null; } } -class MyResourceInput extends ResourceEditorInput { } +class MyResourceEditorInput extends ResourceEditorInput { } suite('Workbench base editor', () => { @@ -103,11 +104,9 @@ suite('Workbench base editor', () => { assert(!e.isVisible()); assert(!e.input); - assert(!e.options); await e.setInput(input, options, CancellationToken.None); assert.strictEqual(input, e.input); - assert.strictEqual(options, e.options); const group = new TestEditorGroupView(1); e.setVisible(true, group); assert(e.isVisible()); @@ -120,7 +119,6 @@ suite('Workbench base editor', () => { e.setVisible(false, group); assert(!e.isVisible()); assert(!e.input); - assert(!e.options); assert(!e.getControl()); }); @@ -156,11 +154,11 @@ suite('Workbench base editor', () => { test('Editor Lookup favors specific class over superclass (match on specific class)', function () { let d1 = EditorDescriptor.create(MyEditor, 'id1', 'name'); - const disposable = EditorRegistry.registerEditor(d1, [new SyncDescriptor(MyResourceInput)]); + const disposable = EditorRegistry.registerEditor(d1, [new SyncDescriptor(MyResourceEditorInput)]); let inst = workbenchInstantiationService(); - const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceInput, 'fake', '', URI.file('/fake'), undefined))!.instantiate(inst); + const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceEditorInput, 'fake', '', URI.file('/fake'), undefined))!.instantiate(inst); assert.strictEqual(editor.getId(), 'myEditor'); const otherEditor = EditorRegistry.getEditor(inst.createInstance(ResourceEditorInput, 'fake', '', URI.file('/fake'), undefined))!.instantiate(inst); @@ -172,7 +170,7 @@ suite('Workbench base editor', () => { test('Editor Lookup favors specific class over superclass (match on super class)', function () { let inst = workbenchInstantiationService(); - const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceInput, 'fake', '', URI.file('/fake'), undefined))!.instantiate(inst); + const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceEditorInput, 'fake', '', URI.file('/fake'), undefined))!.instantiate(inst); assert.strictEqual('workbench.editors.textResourceEditor', editor.getId()); }); diff --git a/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts b/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts index d74c28ef6c4..067919112c6 100644 --- a/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts @@ -7,9 +7,9 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { EditorBreadcrumbsModel, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel'; -import { TestContextService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { FileKind } from 'vs/platform/files/common/files'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; suite('Breadcrumb Model', function () { diff --git a/src/vs/workbench/test/browser/parts/editor/editor.test.ts b/src/vs/workbench/test/browser/parts/editor/editor.test.ts index 96f97d1ad98..e974da14e70 100644 --- a/src/vs/workbench/test/browser/parts/editor/editor.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editor.test.ts @@ -35,6 +35,8 @@ suite('Workbench editor', () => { assert.equal(toResource(untitled)!.toString(), untitled.resource.toString()); assert.equal(toResource(untitled, { supportSideBySide: SideBySideEditor.MASTER })!.toString(), untitled.resource.toString()); + assert.equal(toResource(untitled, { supportSideBySide: SideBySideEditor.DETAILS })!.toString(), untitled.resource.toString()); + assert.equal(toResource(untitled, { supportSideBySide: SideBySideEditor.BOTH })!.toString(), untitled.resource.toString()); assert.equal(toResource(untitled, { filterByScheme: Schemas.untitled })!.toString(), untitled.resource.toString()); assert.equal(toResource(untitled, { filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), untitled.resource.toString()); assert.ok(!toResource(untitled, { filterByScheme: Schemas.file })); @@ -43,6 +45,8 @@ suite('Workbench editor', () => { assert.equal(toResource(file)!.toString(), file.resource.toString()); assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER })!.toString(), file.resource.toString()); + assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.DETAILS })!.toString(), file.resource.toString()); + assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.BOTH })!.toString(), file.resource.toString()); assert.equal(toResource(file, { filterByScheme: Schemas.file })!.toString(), file.resource.toString()); assert.equal(toResource(file, { filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.resource.toString()); assert.ok(!toResource(file, { filterByScheme: Schemas.untitled })); @@ -52,8 +56,20 @@ suite('Workbench editor', () => { assert.ok(!toResource(diffEditorInput)); assert.ok(!toResource(diffEditorInput, { filterByScheme: Schemas.file })); - assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER })!.toString(), file.resource.toString()); - assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.file })!.toString(), file.resource.toString()); - assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.resource.toString()); + assert.equal(toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.MASTER })!.toString(), file.resource.toString()); + assert.equal(toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.file })!.toString(), file.resource.toString()); + assert.equal(toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.resource.toString()); + + assert.equal(toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.DETAILS })!.toString(), untitled.resource.toString()); + assert.equal(toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.DETAILS, filterByScheme: Schemas.untitled })!.toString(), untitled.resource.toString()); + assert.equal(toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.DETAILS, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), untitled.resource.toString()); + + assert.equal((toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH }) as { master: URI, detail: URI }).master.toString(), file.resource.toString()); + assert.equal((toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: Schemas.file }) as { master: URI, detail: URI }).master.toString(), file.resource.toString()); + assert.equal((toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: [Schemas.file, Schemas.untitled] }) as { master: URI, detail: URI }).master.toString(), file.resource.toString()); + + assert.equal((toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH }) as { master: URI, detail: URI }).detail.toString(), untitled.resource.toString()); + assert.equal((toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: Schemas.untitled }) as { master: URI, detail: URI }).detail.toString(), untitled.resource.toString()); + assert.equal((toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: [Schemas.file, Schemas.untitled] }) as { master: URI, detail: URI }).detail.toString(), untitled.resource.toString()); }); }); diff --git a/src/vs/workbench/test/browser/parts/editor/editorGroups.test.ts b/src/vs/workbench/test/browser/parts/editor/editorGroups.test.ts index 2eb866b7a4a..f8e3f5e90a5 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorGroups.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorGroups.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { EditorGroup, ISerializedEditorGroup, EditorCloseEvent } from 'vs/workbench/common/editor/editorGroup'; import { Extensions as EditorExtensions, IEditorInputFactoryRegistry, EditorInput, IFileEditorInput, IEditorInputFactory, CloseDirection, EditorsOrder } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; -import { TestLifecycleService, TestContextService, TestStorageService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -21,6 +21,7 @@ import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtil import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { TestContextService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; function inst(): IInstantiationService { let inst = new TestInstantiationService(); diff --git a/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts b/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts index d0c2abb5eea..4742a1c19d0 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts @@ -17,13 +17,15 @@ import { ITextBufferFactory } from 'vs/editor/common/model'; import { URI } from 'vs/base/common/uri'; import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; -import { TestTextResourcePropertiesService } from 'vs/workbench/test/browser/workbenchTestServices'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { TestTextResourcePropertiesService } from 'vs/workbench/test/common/workbenchTestServices'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; class MyEditorModel extends EditorModel { } class MyTextEditorModel extends BaseTextEditorModel { @@ -84,6 +86,7 @@ suite('Workbench editor model', () => { instantiationService.stub(IDialogService, dialogService); instantiationService.stub(INotificationService, notificationService); instantiationService.stub(IUndoRedoService, undoRedoService); + instantiationService.stub(IThemeService, new TestThemeService()); return instantiationService.createInstance(ModelServiceImpl); } }); diff --git a/src/vs/workbench/test/browser/parts/editor/rangeDecorations.test.ts b/src/vs/workbench/test/browser/parts/editor/rangeDecorations.test.ts index 06f12dbcaea..224d20cfa21 100644 --- a/src/vs/workbench/test/browser/parts/editor/rangeDecorations.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/rangeDecorations.test.ts @@ -22,6 +22,8 @@ import { CoreNavigationCommands } from 'vs/editor/browser/controller/coreCommand import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; suite('Editor - Range decorations', () => { @@ -42,7 +44,7 @@ suite('Editor - Range decorations', () => { codeEditor = createTestCodeEditor({ model: model }); instantiationService.stub(IEditorService, 'activeEditor', { get resource() { return codeEditor.getModel()!.uri; } }); - instantiationService.stub(IEditorService, 'activeTextEditorWidget', codeEditor); + instantiationService.stub(IEditorService, 'activeTextEditorControl', codeEditor); testObject = instantiationService.createInstance(RangeHighlightDecorations); }); @@ -157,6 +159,7 @@ suite('Editor - Range decorations', () => { function stubModelService(instantiationService: TestInstantiationService): IModelService { instantiationService.stub(IConfigurationService, new TestConfigurationService()); + instantiationService.stub(IThemeService, new TestThemeService()); return instantiationService.createInstance(ModelServiceImpl); } }); diff --git a/src/vs/workbench/test/browser/parts/views/views.test.ts b/src/vs/workbench/test/browser/parts/views/views.test.ts index e4127214b07..066b67fa550 100644 --- a/src/vs/workbench/test/browser/parts/views/views.test.ts +++ b/src/vs/workbench/test/browser/parts/views/views.test.ts @@ -535,8 +535,8 @@ suite('ViewDescriptorService', () => { sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); panelViews = viewDescriptorService.getViewDescriptors(panelContainer); - assert.equal(sidebarViews.activeViewDescriptors.length, 2, 'Sidebar should have 2 views'); - assert.equal(panelViews.activeViewDescriptors.length, 1, 'Panel should have 1 view'); + assert.equal(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar should have 2 views'); + assert.equal(panelViews.activeViewDescriptors.length, 0, 'Panel should have 1 view'); assert.equal(viewDescriptorService.getViewLocation(viewDescriptors[0].id), ViewContainerLocation.Sidebar, 'View should be located in the sidebar'); assert.equal(viewDescriptorService.getViewLocation(viewDescriptors[2].id), ViewContainerLocation.Panel, 'View should be located in the panel'); @@ -602,11 +602,11 @@ suite('ViewDescriptorService', () => { expectedSequence += locationMoveString(viewDescriptors[0], ViewContainerLocation.Panel, ViewContainerLocation.Sidebar); expectedSequence += containerMoveString(viewDescriptors[0], viewDescriptorService.getViewContainer(viewDescriptors[0].id)!, sidebarContainer); - viewDescriptorService.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Sidebar); + viewDescriptorService.moveViewsToContainer([viewDescriptors[0]], sidebarContainer); expectedSequence += locationMoveString(viewDescriptors[2], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); expectedSequence += containerMoveString(viewDescriptors[2], viewDescriptorService.getViewContainer(viewDescriptors[2].id)!, panelContainer); - viewDescriptorService.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Panel); + viewDescriptorService.moveViewsToContainer([viewDescriptors[2]], panelContainer); expectedSequence += locationMoveString(viewDescriptors[0], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); expectedSequence += containerMoveString(viewDescriptors[0], sidebarContainer, panelContainer); diff --git a/src/vs/workbench/test/browser/quickAccess.test.ts b/src/vs/workbench/test/browser/quickAccess.test.ts new file mode 100644 index 00000000000..ccaa36cdf27 --- /dev/null +++ b/src/vs/workbench/test/browser/quickAccess.test.ts @@ -0,0 +1,314 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { Registry } from 'vs/platform/registry/common/platform'; +import { IQuickAccessRegistry, Extensions, IQuickAccessProvider, QuickAccessRegistry } from 'vs/platform/quickinput/common/quickAccess'; +import { IQuickPick, IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { TestServiceAccessor, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { DisposableStore, toDisposable, IDisposable } from 'vs/base/common/lifecycle'; +import { timeout } from 'vs/base/common/async'; +import { PickerQuickAccessProvider, FastAndSlowPicks } from 'vs/platform/quickinput/browser/pickerQuickAccess'; + +suite('QuickAccess', () => { + + let instantiationService: IInstantiationService; + let accessor: TestServiceAccessor; + + let providerDefaultCalled = false; + let providerDefaultCanceled = false; + let providerDefaultDisposed = false; + + let provider1Called = false; + let provider1Canceled = false; + let provider1Disposed = false; + + let provider2Called = false; + let provider2Canceled = false; + let provider2Disposed = false; + + let provider3Called = false; + let provider3Canceled = false; + let provider3Disposed = false; + + class TestProviderDefault implements IQuickAccessProvider { + + constructor(@IQuickInputService private readonly quickInputService: IQuickInputService, disposables: DisposableStore) { } + + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + assert.ok(picker); + providerDefaultCalled = true; + token.onCancellationRequested(() => providerDefaultCanceled = true); + + // bring up provider #3 + setTimeout(() => this.quickInputService.quickAccess.show(providerDescriptor3.prefix)); + + return toDisposable(() => providerDefaultDisposed = true); + } + } + + class TestProvider1 implements IQuickAccessProvider { + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + assert.ok(picker); + provider1Called = true; + token.onCancellationRequested(() => provider1Canceled = true); + + return toDisposable(() => provider1Disposed = true); + } + } + + class TestProvider2 implements IQuickAccessProvider { + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + assert.ok(picker); + provider2Called = true; + token.onCancellationRequested(() => provider2Canceled = true); + + return toDisposable(() => provider2Disposed = true); + } + } + + class TestProvider3 implements IQuickAccessProvider { + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + assert.ok(picker); + provider3Called = true; + token.onCancellationRequested(() => provider3Canceled = true); + + // hide without picking + setTimeout(() => picker.hide()); + + return toDisposable(() => provider3Disposed = true); + } + } + + const providerDescriptorDefault = { ctor: TestProviderDefault, prefix: '', helpEntries: [] }; + const providerDescriptor1 = { ctor: TestProvider1, prefix: 'test', helpEntries: [] }; + const providerDescriptor2 = { ctor: TestProvider2, prefix: 'test something', helpEntries: [] }; + const providerDescriptor3 = { ctor: TestProvider3, prefix: 'changed', helpEntries: [] }; + + setup(() => { + instantiationService = workbenchInstantiationService(); + accessor = instantiationService.createInstance(TestServiceAccessor); + }); + + test('registry', () => { + const registry = (Registry.as(Extensions.Quickaccess)); + const restore = (registry as QuickAccessRegistry).clear(); + + assert.ok(!registry.getQuickAccessProvider('test')); + + const disposables = new DisposableStore(); + + disposables.add(registry.registerQuickAccessProvider(providerDescriptorDefault)); + assert(registry.getQuickAccessProvider('') === providerDescriptorDefault); + assert(registry.getQuickAccessProvider('test') === providerDescriptorDefault); + + const disposable = disposables.add(registry.registerQuickAccessProvider(providerDescriptor1)); + assert(registry.getQuickAccessProvider('test') === providerDescriptor1); + + const providers = registry.getQuickAccessProviders(); + assert(providers.some(provider => provider.prefix === 'test')); + + disposable.dispose(); + assert(registry.getQuickAccessProvider('test') === providerDescriptorDefault); + + disposables.dispose(); + assert.ok(!registry.getQuickAccessProvider('test')); + + restore(); + }); + + test('provider', async () => { + const registry = (Registry.as(Extensions.Quickaccess)); + const restore = (registry as QuickAccessRegistry).clear(); + + const disposables = new DisposableStore(); + + disposables.add(registry.registerQuickAccessProvider(providerDescriptorDefault)); + disposables.add(registry.registerQuickAccessProvider(providerDescriptor1)); + disposables.add(registry.registerQuickAccessProvider(providerDescriptor2)); + disposables.add(registry.registerQuickAccessProvider(providerDescriptor3)); + + accessor.quickInputService.quickAccess.show('test'); + assert.equal(providerDefaultCalled, false); + assert.equal(provider1Called, true); + assert.equal(provider2Called, false); + assert.equal(provider3Called, false); + assert.equal(providerDefaultCanceled, false); + assert.equal(provider1Canceled, false); + assert.equal(provider2Canceled, false); + assert.equal(provider3Canceled, false); + assert.equal(providerDefaultDisposed, false); + assert.equal(provider1Disposed, false); + assert.equal(provider2Disposed, false); + assert.equal(provider3Disposed, false); + provider1Called = false; + + accessor.quickInputService.quickAccess.show('test something'); + assert.equal(providerDefaultCalled, false); + assert.equal(provider1Called, false); + assert.equal(provider2Called, true); + assert.equal(provider3Called, false); + assert.equal(providerDefaultCanceled, false); + assert.equal(provider1Canceled, true); + assert.equal(provider2Canceled, false); + assert.equal(provider3Canceled, false); + assert.equal(providerDefaultDisposed, false); + assert.equal(provider1Disposed, true); + assert.equal(provider2Disposed, false); + assert.equal(provider3Disposed, false); + provider2Called = false; + provider1Canceled = false; + provider1Disposed = false; + + accessor.quickInputService.quickAccess.show('usedefault'); + assert.equal(providerDefaultCalled, true); + assert.equal(provider1Called, false); + assert.equal(provider2Called, false); + assert.equal(provider3Called, false); + assert.equal(providerDefaultCanceled, false); + assert.equal(provider1Canceled, false); + assert.equal(provider2Canceled, true); + assert.equal(provider3Canceled, false); + assert.equal(providerDefaultDisposed, false); + assert.equal(provider1Disposed, false); + assert.equal(provider2Disposed, true); + assert.equal(provider3Disposed, false); + + await timeout(1); + + assert.equal(providerDefaultCanceled, true); + assert.equal(providerDefaultDisposed, true); + assert.equal(provider3Called, true); + + await timeout(1); + + assert.equal(provider3Canceled, true); + assert.equal(provider3Disposed, true); + + disposables.dispose(); + + restore(); + }); + + let fastProviderCalled = false; + let slowProviderCalled = false; + let fastAndSlowProviderCalled = false; + + let slowProviderCanceled = false; + let fastAndSlowProviderCanceled = false; + + class FastTestQuickPickProvider extends PickerQuickAccessProvider { + + constructor() { + super('fast'); + } + + protected getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Array { + fastProviderCalled = true; + + return [{ label: 'Fast Pick' }]; + } + } + + class SlowTestQuickPickProvider extends PickerQuickAccessProvider { + + constructor() { + super('slow'); + } + + protected async getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Promise> { + slowProviderCalled = true; + + await timeout(1); + + if (token.isCancellationRequested) { + slowProviderCanceled = true; + } + + return [{ label: 'Slow Pick' }]; + } + } + + class FastAndSlowTestQuickPickProvider extends PickerQuickAccessProvider { + + constructor() { + super('bothFastAndSlow'); + } + + protected getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): FastAndSlowPicks { + fastAndSlowProviderCalled = true; + + return { + picks: [{ label: 'Fast Pick' }], + additionalPicks: (async () => { + await timeout(1); + + if (token.isCancellationRequested) { + fastAndSlowProviderCanceled = true; + } + + return [{ label: 'Slow Pick' }]; + })() + }; + } + } + + const fastProviderDescriptor = { ctor: FastTestQuickPickProvider, prefix: 'fast', helpEntries: [] }; + const slowProviderDescriptor = { ctor: SlowTestQuickPickProvider, prefix: 'slow', helpEntries: [] }; + const fastAndSlowProviderDescriptor = { ctor: FastAndSlowTestQuickPickProvider, prefix: 'bothFastAndSlow', helpEntries: [] }; + + test('quick pick access', async () => { + const registry = (Registry.as(Extensions.Quickaccess)); + const restore = (registry as QuickAccessRegistry).clear(); + + const disposables = new DisposableStore(); + + disposables.add(registry.registerQuickAccessProvider(fastProviderDescriptor)); + disposables.add(registry.registerQuickAccessProvider(slowProviderDescriptor)); + disposables.add(registry.registerQuickAccessProvider(fastAndSlowProviderDescriptor)); + + accessor.quickInputService.quickAccess.show('fast'); + assert.equal(fastProviderCalled, true); + assert.equal(slowProviderCalled, false); + assert.equal(fastAndSlowProviderCalled, false); + fastProviderCalled = false; + + accessor.quickInputService.quickAccess.show('slow'); + await timeout(2); + + assert.equal(fastProviderCalled, false); + assert.equal(slowProviderCalled, true); + assert.equal(slowProviderCanceled, false); + assert.equal(fastAndSlowProviderCalled, false); + slowProviderCalled = false; + + accessor.quickInputService.quickAccess.show('bothFastAndSlow'); + await timeout(2); + + assert.equal(fastProviderCalled, false); + assert.equal(slowProviderCalled, false); + assert.equal(fastAndSlowProviderCalled, true); + assert.equal(fastAndSlowProviderCanceled, false); + fastAndSlowProviderCalled = false; + + accessor.quickInputService.quickAccess.show('slow'); + accessor.quickInputService.quickAccess.show('bothFastAndSlow'); + accessor.quickInputService.quickAccess.show('fast'); + + assert.equal(fastProviderCalled, true); + assert.equal(slowProviderCalled, true); + assert.equal(fastAndSlowProviderCalled, true); + + await timeout(2); + assert.equal(slowProviderCanceled, true); + assert.equal(fastAndSlowProviderCanceled, true); + + disposables.dispose(); + + restore(); + }); +}); diff --git a/src/vs/workbench/test/browser/quickopen.test.ts b/src/vs/workbench/test/browser/quickopen.test.ts deleted file mode 100644 index 8303a042e22..00000000000 --- a/src/vs/workbench/test/browser/quickopen.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import 'vs/workbench/browser/parts/editor/editor.contribution'; // make sure to load all contributed editor things into tests -import { Event } from 'vs/base/common/event'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { QuickOpenHandlerDescriptor, IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenAction, QuickOpenHandler } from 'vs/workbench/browser/quickopen'; - -export class TestQuickOpenService implements IQuickOpenService { - - _serviceBrand: undefined; - - private callback?: (prefix?: string) => void; - - constructor(callback?: (prefix?: string) => void) { - this.callback = callback; - } - - accept(): void { - } - - focus(): void { - } - - close(): void { - } - - show(prefix?: string, options?: any): Promise { - if (this.callback) { - this.callback(prefix); - } - - return Promise.resolve(); - } - - get onShow(): Event { - return null!; - } - - get onHide(): Event { - return null!; - } - - dispose() { } - navigate(): void { } -} - -suite('QuickOpen', () => { - - class TestHandler extends QuickOpenHandler { } - - test('QuickOpen Handler and Registry', () => { - let registry = (Registry.as(QuickOpenExtensions.Quickopen)); - let handler = QuickOpenHandlerDescriptor.create( - TestHandler, - 'testhandler', - ',', - 'Handler', - null! - ); - - registry.registerQuickOpenHandler(handler); - - assert(registry.getQuickOpenHandler(',') === handler); - - let handlers = registry.getQuickOpenHandlers(); - assert(handlers.some((handler: QuickOpenHandlerDescriptor) => handler.prefix === ',')); - }); - - test('QuickOpen Action', () => { - let defaultAction = new QuickOpenAction('id', 'label', (undefined)!, new TestQuickOpenService(prefix => assert(!prefix))); - let prefixAction = new QuickOpenAction('id', 'label', ',', new TestQuickOpenService(prefix => assert(!!prefix))); - - defaultAction.run(); - prefixAction.run(); - }); -}); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 8147ecb50bf..71fa4dfdabb 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -10,7 +10,7 @@ import * as resources from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { IEditorInputWithOptions, CloseDirection, IEditorIdentifier, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInput, IEditor, IEditorCloseEvent, IEditorPartOptions, IRevertOptions, GroupIdentifier, EditorInput, EditorOptions, EditorsOrder, IFileEditorInput, IEditorInputFactoryRegistry, IEditorInputFactory, Extensions as EditorExtensions, ISaveOptions, IMoveResult, ITextEditor, ITextDiffEditor, ITextSideBySideEditor } from 'vs/workbench/common/editor'; +import { IEditorInputWithOptions, CloseDirection, IEditorIdentifier, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, IEditorInput, IEditorPane, IEditorCloseEvent, IEditorPartOptions, IRevertOptions, GroupIdentifier, EditorInput, EditorOptions, EditorsOrder, IFileEditorInput, IEditorInputFactoryRegistry, IEditorInputFactory, Extensions as EditorExtensions, ISaveOptions, IMoveResult, ITextEditorPane, ITextDiffEditorPane, IVisibleEditorPane } from 'vs/workbench/common/editor'; import { IEditorOpeningEvent, EditorServiceImpl, IEditorGroupView, IEditorGroupsAccessor } from 'vs/workbench/browser/parts/editor/editor'; import { Event, Emitter } from 'vs/base/common/event'; import { IBackupFileService, IResolvedBackup } from 'vs/workbench/services/backup/common/backup'; @@ -18,7 +18,7 @@ import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configur import { IWorkbenchLayoutService, Parts, Position as PartPosition } from 'vs/workbench/services/layout/browser/layoutService'; import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { IEditorOptions, IResourceInput, IEditorModel, ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { IEditorOptions, IResourceEditorInput, IEditorModel, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ILifecycleService, BeforeShutdownEvent, ShutdownReason, StartupKind, LifecyclePhase, WillShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; @@ -47,16 +47,16 @@ import { Range } from 'vs/editor/common/core/range'; import { IDialogService, IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; -import { IExtensionService, NullExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IDecorationsService, IResourceDecorationChangeEvent, IDecoration, IDecorationData, IDecorationsProvider } from 'vs/workbench/services/decorations/browser/decorations'; import { IDisposable, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IEditorGroupsService, IEditorGroup, GroupsOrder, GroupsArrangement, GroupDirection, IAddGroupOptions, IMergeGroupOptions, IMoveEditorOptions, ICopyEditorOptions, IEditorReplacement, IGroupChangeEvent, IFindGroupScope, EditorGroupLayout, ICloseEditorOptions, GroupOrientation } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IEditorService, IOpenEditorOverrideHandler, IVisibleEditor, ISaveEditorsOptions, IRevertAllEditorsOptions, IResourceEditor, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService, IOpenEditorOverrideHandler, ISaveEditorsOptions, IRevertAllEditorsOptions, IResourceEditorInputType, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IEditorRegistry, EditorDescriptor, Extensions } from 'vs/workbench/browser/editor'; import { EditorGroup } from 'vs/workbench/common/editor/editorGroup'; -import { Dimension } from 'vs/base/browser/dom'; +import { Dimension, IDimension } from 'vs/base/browser/dom'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { ILabelService } from 'vs/platform/label/common/label'; import { timeout } from 'vs/base/common/async'; @@ -64,9 +64,8 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ViewletDescriptor, Viewlet } from 'vs/workbench/browser/viewlet'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { isLinux } from 'vs/base/common/platform'; +import { isLinux, isWindows } from 'vs/base/common/platform'; import { LabelService } from 'vs/workbench/services/label/common/labelService'; -import { IDimension } from 'vs/platform/layout/browser/layoutService'; import { Part } from 'vs/workbench/browser/part'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IPanel } from 'vs/workbench/common/panel'; @@ -82,7 +81,6 @@ import { IFilesConfigurationService, FilesConfigurationService } from 'vs/workbe import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { BrowserTextFileService } from 'vs/workbench/services/textfile/browser/browserTextFileService'; -import * as CommonWorkbenchTestServices from 'vs/workbench/test/common/workbenchTestServices'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; @@ -99,15 +97,17 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; import { CodeEditorService } from 'vs/workbench/services/editor/browser/codeEditorService'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; - -export import TestTextResourcePropertiesService = CommonWorkbenchTestServices.TestTextResourcePropertiesService; -export import TestContextService = CommonWorkbenchTestServices.TestContextService; -export import TestStorageService = CommonWorkbenchTestServices.TestStorageService; -export import TestWorkingCopyService = CommonWorkbenchTestServices.TestWorkingCopyService; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { QuickInputService } from 'vs/workbench/services/quickinput/browser/quickInputService'; +import { IListService } from 'vs/platform/list/browser/listService'; +import { win32, posix } from 'vs/base/common/path'; +import { TestWorkingCopyService, TestContextService, TestStorageService, TestTextResourcePropertiesService, TestExtensionService } from 'vs/workbench/test/common/workbenchTestServices'; +import { IViewsService, IView } from 'vs/workbench/common/views'; +import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; -export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { +export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); } @@ -127,18 +127,23 @@ export function workbenchInstantiationService(overrides?: { textFileService?: (i instantiationService.stub(IWorkspaceContextService, workspaceContextService); const configService = new TestConfigurationService(); instantiationService.stub(IConfigurationService, configService); - instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService(contextKeyService, configService, TestEnvironmentService)); + instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService(contextKeyService, configService)); instantiationService.stub(ITextResourceConfigurationService, new TestTextResourceConfigurationService(configService)); instantiationService.stub(IUntitledTextEditorService, instantiationService.createInstance(UntitledTextEditorService)); instantiationService.stub(IStorageService, new TestStorageService()); - instantiationService.stub(IWorkbenchLayoutService, new TestLayoutService()); + instantiationService.stub(IRemotePathService, new TestRemotePathService(TestEnvironmentService)); + const layoutService = new TestLayoutService(); + instantiationService.stub(IWorkbenchLayoutService, layoutService); instantiationService.stub(IDialogService, new TestDialogService()); - instantiationService.stub(IAccessibilityService, new TestAccessibilityService()); + const accessibilityService = new TestAccessibilityService(); + instantiationService.stub(IAccessibilityService, accessibilityService); instantiationService.stub(IFileDialogService, new TestFileDialogService()); instantiationService.stub(IModeService, instantiationService.createInstance(ModeServiceImpl)); instantiationService.stub(IHistoryService, new TestHistoryService()); instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(configService)); instantiationService.stub(IUndoRedoService, instantiationService.createInstance(UndoRedoService)); + const themeService = new TestThemeService(); + instantiationService.stub(IThemeService, themeService); instantiationService.stub(IModelService, instantiationService.createInstance(ModelServiceImpl)); instantiationService.stub(IFileService, new TestFileService()); instantiationService.stub(IBackupFileService, new TestBackupFileService()); @@ -146,15 +151,14 @@ export function workbenchInstantiationService(overrides?: { textFileService?: (i instantiationService.stub(INotificationService, new TestNotificationService()); instantiationService.stub(IUntitledTextEditorService, instantiationService.createInstance(UntitledTextEditorService)); instantiationService.stub(IMenuService, new TestMenuService()); - instantiationService.stub(IKeybindingService, new MockKeybindingService()); + const keybindingService = new MockKeybindingService(); + instantiationService.stub(IKeybindingService, keybindingService); instantiationService.stub(IDecorationsService, new TestDecorationsService()); instantiationService.stub(IExtensionService, new TestExtensionService()); instantiationService.stub(IWorkingCopyFileService, instantiationService.createInstance(WorkingCopyFileService)); instantiationService.stub(ITextFileService, overrides?.textFileService ? overrides.textFileService(instantiationService) : instantiationService.createInstance(TestTextFileService)); instantiationService.stub(IHostService, instantiationService.createInstance(TestHostService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); - const themeService = new TestThemeService(); - instantiationService.stub(IThemeService, themeService); instantiationService.stub(ILogService, new NullLogService()); const editorGroupService = new TestEditorGroupsService([new TestEditorGroupView(0)]); instantiationService.stub(IEditorGroupsService, editorGroupService); @@ -163,6 +167,9 @@ export function workbenchInstantiationService(overrides?: { textFileService?: (i instantiationService.stub(IEditorService, editorService); instantiationService.stub(ICodeEditorService, new CodeEditorService(editorService, themeService)); instantiationService.stub(IViewletService, new TestViewletService()); + instantiationService.stub(IListService, new TestListService()); + instantiationService.stub(IQuickInputService, new QuickInputService(TestEnvironmentService, configService, instantiationService, keybindingService, contextKeyService, themeService, accessibilityService, layoutService)); + instantiationService.stub(IStorageKeysSyncRegistryService, new StorageKeysSyncRegistryService()); return instantiationService; } @@ -185,7 +192,8 @@ export class TestServiceAccessor { @IUntitledTextEditorService public untitledTextEditorService: UntitledTextEditorService, @IConfigurationService public testConfigurationService: TestConfigurationService, @IBackupFileService public backupFileService: TestBackupFileService, - @IHostService public hostService: TestHostService + @IHostService public hostService: TestHostService, + @IQuickInputService public quickInputService: IQuickInputService ) { } } @@ -290,8 +298,6 @@ export class TestDecorationsService implements IDecorationsService { getDecoration(_uri: URI, _includeChildren: boolean, _overwrite?: IDecorationData): IDecoration | undefined { return undefined; } } -export class TestExtensionService extends NullExtensionService { } - export class TestMenuService implements IMenuService { _serviceBrand: undefined; @@ -315,10 +321,10 @@ export class TestHistoryService implements IHistoryService { forward(): void { } back(): void { } last(): void { } - remove(_input: IEditorInput | IResourceInput): void { } + remove(_input: IEditorInput | IResourceEditorInput): void { } clear(): void { } clearRecentlyOpened(): void { } - getHistory(): ReadonlyArray { return []; } + getHistory(): ReadonlyArray { return []; } openNextRecentlyUsedEditor(group?: GroupIdentifier): void { } openPreviouslyUsedEditor(group?: GroupIdentifier): void { } getLastActiveWorkspaceRoot(_schemeFilter: string): URI | undefined { return this.root; } @@ -380,7 +386,6 @@ export class TestLayoutService implements IWorkbenchLayoutService { getDimension(_part: Parts): Dimension { return new Dimension(0, 0); } getContainer(_part: Parts): HTMLElement { return null!; } isTitleBarHidden(): boolean { return false; } - getTitleBarOffset(): number { return 0; } isStatusBarHidden(): boolean { return false; } isActivityBarHidden(): boolean { return false; } setActivityBarHidden(_hidden: boolean): void { } @@ -399,7 +404,6 @@ export class TestLayoutService implements IWorkbenchLayoutService { removeClass(_clazz: string): void { } getMaximumEditorDimensions(): Dimension { throw new Error('not implemented'); } getWorkbenchContainer(): HTMLElement { throw new Error('not implemented'); } - getWorkbenchElement(): HTMLElement { throw new Error('not implemented'); } toggleZenMode(): void { } isEditorLayoutCentered(): boolean { return false; } centerEditorLayout(_active: boolean): void { } @@ -408,6 +412,7 @@ export class TestLayoutService implements IWorkbenchLayoutService { isWindowMaximized() { return false; } updateWindowMaximizedState(maximized: boolean): void { } getVisibleNeighborPart(part: Parts, direction: Direction): Parts | undefined { return undefined; } + focus() { } } let activeViewlet: Viewlet = {} as any; @@ -447,7 +452,7 @@ export class TestPanelService implements IPanelService { getPanel(id: string): any { return activeViewlet; } getPanels() { return []; } getPinnedPanels() { return []; } - getActivePanel(): IViewlet { return activeViewlet; } + getActivePanel(): IPanel { return activeViewlet; } setPanelEnablement(id: string, enabled: boolean): void { } dispose() { } showActivity(panelId: string, badge: IBadge, clazz?: string): IDisposable { throw new Error('Method not implemented.'); } @@ -456,6 +461,19 @@ export class TestPanelService implements IPanelService { getLastActivePanelId(): string { return undefined!; } } +export class TestViewsService implements IViewsService { + _serviceBrand: undefined; + + onDidChangeViewVisibilityEmitter = new Emitter<{ id: string; visible: boolean; }>(); + + onDidChangeViewVisibility = this.onDidChangeViewVisibilityEmitter.event; + isViewVisible(id: string): boolean { return true; } + getActiveViewWithId(id: string): T | null { return null; } + openView(id: string, focus?: boolean | undefined): Promise { return Promise.resolve(null); } + closeView(id: string): void { } + getProgressIndicator(id: string) { return null!; } +} + export class TestEditorGroupsService implements IEditorGroupsService { _serviceBrand: undefined; @@ -508,7 +526,7 @@ export class TestEditorGroupView implements IEditorGroupView { constructor(public id: number) { } get group(): EditorGroup { throw new Error('not implemented'); } - activeControl!: IVisibleEditor; + activeEditorPane!: IVisibleEditorPane; activeEditor!: IEditorInput; previewEditor!: IEditorInput; count!: number; @@ -539,9 +557,9 @@ export class TestEditorGroupView implements IEditorGroupView { getEditors(_order?: EditorsOrder): ReadonlyArray { return []; } getEditorByIndex(_index: number): IEditorInput { throw new Error('not implemented'); } getIndexOfEditor(_editor: IEditorInput): number { return -1; } - openEditor(_editor: IEditorInput, _options?: IEditorOptions): Promise { throw new Error('not implemented'); } - openEditors(_editors: IEditorInputWithOptions[]): Promise { throw new Error('not implemented'); } - isOpened(_editor: IEditorInput | IResourceInput): boolean { return false; } + openEditor(_editor: IEditorInput, _options?: IEditorOptions): Promise { throw new Error('not implemented'); } + openEditors(_editors: IEditorInputWithOptions[]): Promise { throw new Error('not implemented'); } + isOpened(_editor: IEditorInput | IResourceEditorInput): boolean { return false; } isPinned(_editor: IEditorInput): boolean { return false; } isActive(_editor: IEditorInput): boolean { return false; } moveEditor(_editor: IEditorInput, _target: IEditorGroup, _options?: IMoveEditorOptions): void { } @@ -593,14 +611,14 @@ export class TestEditorService implements EditorServiceImpl { onDidOpenEditorFail: Event = Event.None; onDidMostRecentlyActiveEditorsChange: Event = Event.None; - activeControl: IVisibleEditor | undefined; - activeTextEditorWidget: ICodeEditor | IDiffEditor | undefined; + activeEditorPane: IVisibleEditorPane | undefined; + activeTextEditorControl: ICodeEditor | IDiffEditor | undefined; activeTextEditorMode: string | undefined; activeEditor: IEditorInput | undefined; editors: ReadonlyArray = []; mostRecentlyActiveEditors: ReadonlyArray = []; - visibleControls: ReadonlyArray = []; - visibleTextEditorWidgets = []; + visibleEditorPanes: ReadonlyArray = []; + visibleTextEditorControls = []; visibleEditors: ReadonlyArray = []; count = this.editors.length; @@ -608,29 +626,28 @@ export class TestEditorService implements EditorServiceImpl { getEditors() { return []; } overrideOpenEditor(_handler: IOpenEditorOverrideHandler): IDisposable { return toDisposable(() => undefined); } - openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceInput | IUntitledTextResourceInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceDiffInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceSideBySideInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - async openEditor(editor: IEditorInput | IResourceEditor, optionsOrGroup?: IEditorOptions | ITextEditorOptions | IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise { + openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceEditorInput | IUntitledTextResourceEditorInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceDiffEditorInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + async openEditor(editor: IEditorInput | IResourceEditorInputType, optionsOrGroup?: IEditorOptions | ITextEditorOptions | IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise { throw new Error('not implemented'); } - doResolveEditorOpenRequest(editor: IEditorInput | IResourceEditor): [IEditorGroup, EditorInput, EditorOptions | undefined] | undefined { + doResolveEditorOpenRequest(editor: IEditorInput | IResourceEditorInputType): [IEditorGroup, EditorInput, EditorOptions | undefined] | undefined { if (!this.editorGroupService) { return undefined; } return [this.editorGroupService.activeGroup, editor as EditorInput, undefined]; } - openEditors(_editors: any, _group?: any): Promise { throw new Error('not implemented'); } - isOpen(_editor: IEditorInput): boolean { return false; } + openEditors(_editors: any, _group?: any): Promise { throw new Error('not implemented'); } + isOpen(_editor: IEditorInput | IResourceEditorInput): boolean { return false; } replaceEditors(_editors: any, _group: any) { return Promise.resolve(undefined); } invokeWithinEditorContext(fn: (accessor: ServicesAccessor) => T): T { throw new Error('not implemented'); } - createInput(_input: IResourceInput | IUntitledTextResourceInput | IResourceDiffInput | IResourceSideBySideInput): EditorInput { throw new Error('not implemented'); } + createEditorInput(_input: IResourceEditorInput | IUntitledTextResourceEditorInput | IResourceDiffEditorInput): EditorInput { throw new Error('not implemented'); } save(editors: IEditorIdentifier[], options?: ISaveEditorsOptions): Promise { throw new Error('Method not implemented.'); } saveAll(options?: ISaveEditorsOptions): Promise { throw new Error('Method not implemented.'); } - revert(editors: IEditorIdentifier[], options?: IRevertOptions): Promise { throw new Error('Method not implemented.'); } - revertAll(options?: IRevertAllEditorsOptions): Promise { throw new Error('Method not implemented.'); } + revert(editors: IEditorIdentifier[], options?: IRevertOptions): Promise { throw new Error('Method not implemented.'); } + revertAll(options?: IRevertAllEditorsOptions): Promise { throw new Error('Method not implemented.'); } } export class TestFileService implements IFileService { @@ -792,6 +809,7 @@ export class TestBackupFileService implements IBackupFileService { getBackups(): Promise { return Promise.resolve([]); } resolve(_backup: URI): Promise | undefined> { return Promise.resolve(undefined); } discardBackup(_resource: URI): Promise { return Promise.resolve(); } + discardBackups(): Promise { return Promise.resolve(); } parseBackupContent(textBufferFactory: ITextBufferFactory): string { const textBuffer = textBufferFactory.create(DefaultEndOfLine.LF); const lineCount = textBuffer.getLineCount(); @@ -1029,11 +1047,10 @@ export class TestFileEditorInput extends EditorInput implements IFileEditorInput this.gotSavedAs = true; return this; } - async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { + async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { this.gotReverted = true; this.gotSaved = false; this.gotSavedAs = false; - return true; } setDirty(): void { this.dirty = true; } isDirty(): boolean { @@ -1069,3 +1086,28 @@ export class TestEditorPart extends EditorPart { } } } + +export class TestListService implements IListService { + _serviceBrand: undefined; + + lastFocusedList: any | undefined = undefined; + + register(): IDisposable { + return Disposable.None; + } +} + +export class TestRemotePathService implements IRemotePathService { + + _serviceBrand: undefined; + + constructor(@IWorkbenchEnvironmentService private readonly environmentService: IEnvironmentService) { } + + get path() { return Promise.resolve(isWindows ? win32 : posix); } + + get userHome() { return Promise.resolve(URI.file(this.environmentService.userHome)); } + + async fileURI(path: string): Promise { + return URI.file(path); + } +} diff --git a/src/vs/workbench/test/common/api/semanticTokensDto.test.ts b/src/vs/workbench/test/common/api/semanticTokensDto.test.ts new file mode 100644 index 00000000000..3f59943a43b --- /dev/null +++ b/src/vs/workbench/test/common/api/semanticTokensDto.test.ts @@ -0,0 +1,110 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { IFullSemanticTokensDto, IDeltaSemanticTokensDto, encodeSemanticTokensDto, ISemanticTokensDto, decodeSemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokensDto'; + +suite('SemanticTokensDto', () => { + + function toArr(arr: Uint32Array): number[] { + const result: number[] = []; + for (let i = 0, len = arr.length; i < len; i++) { + result[i] = arr[i]; + } + return result; + } + + function assertEqualFull(actual: IFullSemanticTokensDto, expected: IFullSemanticTokensDto): void { + const convert = (dto: IFullSemanticTokensDto) => { + return { + id: dto.id, + type: dto.type, + data: toArr(dto.data) + }; + }; + assert.deepEqual(convert(actual), convert(expected)); + } + + function assertEqualDelta(actual: IDeltaSemanticTokensDto, expected: IDeltaSemanticTokensDto): void { + const convertOne = (delta: { start: number; deleteCount: number; data?: Uint32Array; }) => { + if (!delta.data) { + return delta; + } + return { + start: delta.start, + deleteCount: delta.deleteCount, + data: toArr(delta.data) + }; + }; + const convert = (dto: IDeltaSemanticTokensDto) => { + return { + id: dto.id, + type: dto.type, + deltas: dto.deltas.map(convertOne) + }; + }; + assert.deepEqual(convert(actual), convert(expected)); + } + + function testRoundTrip(value: ISemanticTokensDto): void { + const decoded = decodeSemanticTokensDto(encodeSemanticTokensDto(value)); + if (value.type === 'full' && decoded.type === 'full') { + assertEqualFull(decoded, value); + } else if (value.type === 'delta' && decoded.type === 'delta') { + assertEqualDelta(decoded, value); + } else { + assert.fail('wrong type'); + } + } + + test('full encoding', () => { + testRoundTrip({ + id: 12, + type: 'full', + data: new Uint32Array([(1 << 24) + (2 << 16) + (3 << 8) + 4]) + }); + }); + + test('delta encoding', () => { + testRoundTrip({ + id: 12, + type: 'delta', + deltas: [{ + start: 0, + deleteCount: 4, + data: undefined + }, { + start: 15, + deleteCount: 0, + data: new Uint32Array([(1 << 24) + (2 << 16) + (3 << 8) + 4]) + }, { + start: 27, + deleteCount: 5, + data: new Uint32Array([(1 << 24) + (2 << 16) + (3 << 8) + 4, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + }] + }); + }); + + test('partial array buffer', () => { + const sharedArr = new Uint32Array([ + (1 << 24) + (2 << 16) + (3 << 8) + 4, + 1, 2, 3, 4, 5, (1 << 24) + (2 << 16) + (3 << 8) + 4 + ]); + testRoundTrip({ + id: 12, + type: 'delta', + deltas: [{ + start: 0, + deleteCount: 4, + data: sharedArr.subarray(0, 1) + }, { + start: 15, + deleteCount: 0, + data: sharedArr.subarray(1, sharedArr.length) + }] + }); + }); + +}); diff --git a/src/vs/workbench/test/common/memento.test.ts b/src/vs/workbench/test/common/memento.test.ts index 3f63c1c2948..82456f2e115 100644 --- a/src/vs/workbench/test/common/memento.test.ts +++ b/src/vs/workbench/test/common/memento.test.ts @@ -21,7 +21,7 @@ suite('Memento', () => { let myMemento = new Memento('memento.test', storage); // Global - let memento: any = myMemento.getMemento(StorageScope.GLOBAL); + let memento = myMemento.getMemento(StorageScope.GLOBAL); memento.foo = [1, 2, 3]; let globalMemento = myMemento.getMemento(StorageScope.GLOBAL); assert.deepEqual(globalMemento, memento); @@ -76,7 +76,7 @@ suite('Memento', () => { let myMemento = new Memento('memento.test', storage); // Global - let memento: any = myMemento.getMemento(context!); + let memento = myMemento.getMemento(context!); memento.foo = [1, 2, 3]; // Workspace @@ -141,7 +141,7 @@ suite('Memento', () => { let myMemento2 = new Memento('memento.test', storage); // Global - let memento: any = myMemento.getMemento(context!); + let memento = myMemento.getMemento(context!); memento.foo = [1, 2, 3]; memento = myMemento2.getMemento(context!); diff --git a/src/vs/workbench/test/common/notifications.test.ts b/src/vs/workbench/test/common/notifications.test.ts index a050bc29422..765c140dc60 100644 --- a/src/vs/workbench/test/common/notifications.test.ts +++ b/src/vs/workbench/test/common/notifications.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { NotificationsModel, NotificationViewItem, INotificationChangeEvent, NotificationChangeType, NotificationViewItemLabelKind, IStatusMessageChangeEvent, StatusMessageChangeType } from 'vs/workbench/common/notifications'; +import { NotificationsModel, NotificationViewItem, INotificationChangeEvent, NotificationChangeType, NotificationViewItemContentChangeKind, IStatusMessageChangeEvent, StatusMessageChangeType } from 'vs/workbench/common/notifications'; import { Action } from 'vs/base/common/actions'; import { INotification, Severity, NotificationsFilter } from 'vs/platform/notification/common/notification'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; @@ -58,8 +58,8 @@ suite('Notifications', () => { assert.equal(called, 2); called = 0; - item1.onDidChangeLabel(e => { - if (e.kind === NotificationViewItemLabelKind.PROGRESS) { + item1.onDidChangeContent(e => { + if (e.kind === NotificationViewItemContentChangeKind.PROGRESS) { called++; } }); @@ -70,8 +70,8 @@ suite('Notifications', () => { assert.equal(called, 2); called = 0; - item1.onDidChangeLabel(e => { - if (e.kind === NotificationViewItemLabelKind.MESSAGE) { + item1.onDidChangeContent(e => { + if (e.kind === NotificationViewItemContentChangeKind.MESSAGE) { called++; } }); @@ -79,8 +79,8 @@ suite('Notifications', () => { item1.updateMessage('message update'); called = 0; - item1.onDidChangeLabel(e => { - if (e.kind === NotificationViewItemLabelKind.SEVERITY) { + item1.onDidChangeContent(e => { + if (e.kind === NotificationViewItemContentChangeKind.SEVERITY) { called++; } }); @@ -88,8 +88,8 @@ suite('Notifications', () => { item1.updateSeverity(Severity.Error); called = 0; - item1.onDidChangeLabel(e => { - if (e.kind === NotificationViewItemLabelKind.ACTIONS) { + item1.onDidChangeContent(e => { + if (e.kind === NotificationViewItemContentChangeKind.ACTIONS) { called++; } }); @@ -159,6 +159,22 @@ suite('Notifications', () => { assert.equal(lastNotificationEvent.index, 0); assert.equal(lastNotificationEvent.kind, NotificationChangeType.ADD); + item1Handle.updateMessage('Error Message'); + assert.equal(lastNotificationEvent.kind, NotificationChangeType.CHANGE); + assert.equal(lastNotificationEvent.detail, NotificationViewItemContentChangeKind.MESSAGE); + + item1Handle.updateSeverity(Severity.Error); + assert.equal(lastNotificationEvent.kind, NotificationChangeType.CHANGE); + assert.equal(lastNotificationEvent.detail, NotificationViewItemContentChangeKind.SEVERITY); + + item1Handle.updateActions({ primary: [], secondary: [] }); + assert.equal(lastNotificationEvent.kind, NotificationChangeType.CHANGE); + assert.equal(lastNotificationEvent.detail, NotificationViewItemContentChangeKind.ACTIONS); + + item1Handle.progress.infinite(); + assert.equal(lastNotificationEvent.kind, NotificationChangeType.CHANGE); + assert.equal(lastNotificationEvent.detail, NotificationViewItemContentChangeKind.PROGRESS); + let item2Handle = model.addNotification(item2); assert.equal(lastNotificationEvent.item.severity, item2.severity); assert.equal(lastNotificationEvent.item.message.linkedText.toString(), item2.message); @@ -204,7 +220,7 @@ suite('Notifications', () => { assert.equal(lastNotificationEvent.item.severity, item3.severity); assert.equal(lastNotificationEvent.item.message.linkedText.toString(), item3.message); assert.equal(lastNotificationEvent.index, 0); - assert.equal(lastNotificationEvent.kind, NotificationChangeType.CHANGE); + assert.equal(lastNotificationEvent.kind, NotificationChangeType.EXPAND_COLLAPSE); const disposable = model.showStatusMessage('Hello World'); assert.equal(model.statusMessage!.message, 'Hello World'); diff --git a/src/vs/workbench/test/common/workbenchTestServices.ts b/src/vs/workbench/test/common/workbenchTestServices.ts index 82de69c0655..3f4f21f7cec 100644 --- a/src/vs/workbench/test/common/workbenchTestServices.ts +++ b/src/vs/workbench/test/common/workbenchTestServices.ts @@ -15,6 +15,7 @@ import { ITextResourcePropertiesService } from 'vs/editor/common/services/textRe import { isLinux, isMacintosh } from 'vs/base/common/platform'; import { InMemoryStorageService, IWillSaveStateEvent } from 'vs/platform/storage/common/storage'; import { WorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { NullExtensionService } from 'vs/workbench/services/extensions/common/extensions'; export class TestTextResourcePropertiesService implements ITextResourcePropertiesService { @@ -39,7 +40,7 @@ export class TestContextService implements IWorkspaceContextService { _serviceBrand: undefined; private workspace: Workspace; - private options: any; + private options: object; private readonly _onDidChangeWorkspaceName: Emitter; get onDidChangeWorkspaceName(): Event { return this._onDidChangeWorkspaceName.event; } @@ -50,7 +51,7 @@ export class TestContextService implements IWorkspaceContextService { private readonly _onDidChangeWorkbenchState: Emitter; get onDidChangeWorkbenchState(): Event { return this._onDidChangeWorkbenchState.event; } - constructor(workspace: any = TestWorkspace, options: any = null) { + constructor(workspace = TestWorkspace, options = null) { this.workspace = workspace; this.options = options || Object.create(null); this._onDidChangeWorkspaceName = new Emitter(); @@ -127,3 +128,5 @@ export function mock(): Ctor { export interface Ctor { new(): T; } + +export class TestExtensionService extends NullExtensionService { } diff --git a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts deleted file mode 100644 index 4af6351e22c..00000000000 --- a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts +++ /dev/null @@ -1,202 +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 minimist from 'vscode-minimist'; -import * as path from 'vs/base/common/path'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { URI } from 'vs/base/common/uri'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { createSyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { ISearchService } from 'vs/workbench/services/search/common/search'; -import { ITelemetryInfo, ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { testWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; -import { Extensions, IQuickOpenRegistry } from 'vs/workbench/browser/quickopen'; -import 'vs/workbench/contrib/search/browser/search.contribution'; // load contributions -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { LocalSearchService } from 'vs/workbench/services/search/node/searchService'; -import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; -import { TestContextService, TestEditorGroupsService, TestEditorService, TestTextResourcePropertiesService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { TestEnvironmentService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; -import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; -import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; -import { NullLogService } from 'vs/platform/log/common/log'; -import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; -import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; -import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; -import { INotificationService } from 'vs/platform/notification/common/notification'; - -namespace Timer { - export interface ITimerEvent { - id: number; - topic: string; - name: string; - description: string; - data: any; - - startTime: Date; - stopTime: Date; - - stop(stopTime?: Date): void; - timeTaken(): number; - } -} - -// declare var __dirname: string; - -// Checkout sources to run against: -// git clone --separate-git-dir=testGit --no-checkout --single-branch https://chromium.googlesource.com/chromium/src testWorkspace -// cd testWorkspace; git checkout 39a7f93d67f7 -// Run from repository root folder with (test.bat on Windows): ./scripts/test.sh --grep QuickOpen.performance --timeout 180000 --testWorkspace -suite.skip('QuickOpen performance (integration)', () => { - - test('Measure', () => { - if (process.env['VSCODE_PID']) { - return undefined; // TODO@Christoph find out why test fails when run from within VS Code - } - - const n = 3; - const argv = minimist(process.argv); - const testWorkspaceArg = argv['testWorkspace']; - const verboseResults = argv['verboseResults']; - const testWorkspacePath = testWorkspaceArg ? path.resolve(testWorkspaceArg) : __dirname; - - const telemetryService = new TestTelemetryService(); - const configurationService = new TestConfigurationService(); - const textResourcePropertiesService = new TestTextResourcePropertiesService(configurationService); - const dialogService = new TestDialogService(); - const notificationService = new TestNotificationService(); - const undoRedoService = new UndoRedoService(dialogService, notificationService); - const instantiationService = new InstantiationService(new ServiceCollection( - [ITelemetryService, telemetryService], - [IConfigurationService, configurationService], - [ITextResourcePropertiesService, textResourcePropertiesService], - [IDialogService, dialogService], - [INotificationService, notificationService], - [IUndoRedoService, undoRedoService], - [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService, new TestThemeService(), new NullLogService(), undoRedoService)], - [IWorkspaceContextService, new TestContextService(testWorkspace(URI.file(testWorkspacePath)))], - [IEditorService, new TestEditorService()], - [IEditorGroupsService, new TestEditorGroupsService()], - [IEnvironmentService, TestEnvironmentService], - [IUntitledTextEditorService, createSyncDescriptor(UntitledTextEditorService)], - [ISearchService, createSyncDescriptor(LocalSearchService)] - )); - - const registry = Registry.as(Extensions.Quickopen); - const descriptor = registry.getDefaultQuickOpenHandler(); - assert.ok(descriptor); - - function measure() { - const handler = descriptor.instantiate(instantiationService); - handler.onOpen(); - return handler.getResults('a', CancellationToken.None).then(result => { - const uncachedEvent = popEvent(); - assert.strictEqual(uncachedEvent.data.symbols.fromCache, false, 'symbols.fromCache'); - assert.strictEqual(uncachedEvent.data.files.fromCache, true, 'files.fromCache'); - if (testWorkspaceArg) { - assert.ok(!!uncachedEvent.data.files.joined, 'files.joined'); - } - return uncachedEvent; - }).then(uncachedEvent => { - return handler.getResults('ab', CancellationToken.None).then(result => { - const cachedEvent = popEvent(); - assert.strictEqual(uncachedEvent.data.symbols.fromCache, false, 'symbols.fromCache'); - assert.ok(cachedEvent.data.files.fromCache, 'filesFromCache'); - handler.onClose(false); - return [uncachedEvent, cachedEvent]; - }); - }); - } - - function popEvent() { - const events = telemetryService.events - .filter(event => event.name === 'openAnything'); - assert.strictEqual(events.length, 1); - const event = events[0]; - telemetryService.events.length = 0; - return event; - } - - function printResult(data: any) { - if (verboseResults) { - console.log(JSON.stringify(data, null, ' ') + ','); - } else { - console.log(JSON.stringify({ - filesfromCacheNotJoined: data.files.fromCache && !data.files.joined, - searchLength: data.searchLength, - sortedResultDuration: data.sortedResultDuration, - filesResultCount: data.files.resultCount, - errorCount: data.files.errors && data.files.errors.length || undefined - }) + ','); - } - } - - return measure() // Warm-up first - .then(() => { - if (testWorkspaceArg || verboseResults) { // Don't measure by default - const cachedEvents: Timer.ITimerEvent[] = []; - let i = n; - return (function iterate(): Promise { - if (!i--) { - return undefined!; - } - return measure() - .then(([uncachedEvent, cachedEvent]) => { - printResult(uncachedEvent.data); - cachedEvents.push(cachedEvent); - return iterate(); - }); - })().then(() => { - console.log(); - cachedEvents.forEach(cachedEvent => { - printResult(cachedEvent.data); - }); - }); - } - return undefined; - }); - }); -}); - -class TestTelemetryService implements ITelemetryService { - - public _serviceBrand: undefined; - public isOptedIn = true; - - public events: any[] = []; - - public setEnabled(value: boolean): void { - } - - public publicLog(eventName: string, data?: any): Promise { - this.events.push({ name: eventName, data: data }); - return Promise.resolve(undefined); - } - - public publicLog2 = never, T extends GDPRClassification = never>(eventName: string, data?: StrictPropertyCheck) { - return this.publicLog(eventName, data as any); - } - - public getTelemetryInfo(): Promise { - return Promise.resolve({ - instanceId: 'someValue.instanceId', - sessionId: 'someValue.sessionId', - machineId: 'someValue.machineId' - }); - } -} diff --git a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts index be6ef9e5492..bcf4ec6475f 100644 --- a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts @@ -13,11 +13,11 @@ import { ISearchService } from 'vs/workbench/services/search/common/search'; import { ITelemetryService, ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import * as minimist from 'vscode-minimist'; +import * as minimist from 'minimist'; import * as path from 'vs/base/common/path'; import { LocalSearchService } from 'vs/workbench/services/search/node/searchService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { TestContextService, TestEditorService, TestEditorGroupsService, TestTextResourcePropertiesService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEditorService, TestEditorGroupsService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestEnvironmentService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { URI } from 'vs/base/common/uri'; @@ -42,6 +42,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { TestTextResourcePropertiesService, TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; // declare var __dirname: string; @@ -78,7 +79,7 @@ suite.skip('TextSearch performance (integration)', () => { [IDialogService, dialogService], [INotificationService, notificationService], [IUndoRedoService, undoRedoService], - [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService, new TestThemeService(), logService, undoRedoService)], + [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService, new TestThemeService(), logService, undoRedoService, dialogService)], [IWorkspaceContextService, new TestContextService(testWorkspace(URI.file(testWorkspacePath)))], [IEditorService, new TestEditorService()], [IEditorGroupsService, new TestEditorGroupsService()], diff --git a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts index 929d1d748ad..1823b7a662a 100644 --- a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { workbenchInstantiationService as browserWorkbenchInstantiationService, ITestInstantiationService, TestLifecycleService, TestFilesConfigurationService, TestContextService, TestFileService, TestFileDialogService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService as browserWorkbenchInstantiationService, ITestInstantiationService, TestLifecycleService, TestFilesConfigurationService, TestFileService, TestFileDialogService } from 'vs/workbench/test/browser/workbenchTestServices'; import { Event } from 'vs/base/common/event'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; -import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; +import { NativeWorkbenchEnvironmentService, INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { NativeTextFileService, EncodingOracle, IEncodingOverride } from 'vs/workbench/services/textfile/electron-browser/nativeTextFileService'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; @@ -25,9 +25,9 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { URI } from 'vs/base/common/uri'; import { IReadTextFileOptions, ITextFileStreamContent, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; -import { IOpenedWindow, IOpenEmptyWindowOptions, IWindowOpenable, IOpenWindowOptions, IWindowConfiguration } from 'vs/platform/windows/common/windows'; +import { IOpenedWindow, IOpenEmptyWindowOptions, IWindowOpenable, IOpenWindowOptions } from 'vs/platform/windows/common/windows'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; -import { LogLevel } from 'vs/platform/log/common/log'; +import { LogLevel, ILogService } from 'vs/platform/log/common/log'; import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { UTF16le, UTF16be, UTF8_with_bom } from 'vs/base/node/encoding'; @@ -37,12 +37,16 @@ import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { NodeTestBackupFileService } from 'vs/workbench/services/backup/test/electron-browser/backupFileService.test'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { INativeWindowConfiguration } from 'vs/platform/windows/node/window'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; -export const TestWindowConfiguration: IWindowConfiguration = { +export const TestWindowConfiguration: INativeWindowConfiguration = { windowId: 0, + machineId: 'testMachineId', sessionId: 'testSessionId', logLevel: LogLevel.Error, mainPid: 0, + partsSplashPath: '', appRoot: '', userEnv: {}, execPath: process.execPath, @@ -50,7 +54,7 @@ export const TestWindowConfiguration: IWindowConfiguration = { ...parseArgs(process.argv, OPTIONS) }; -export const TestEnvironmentService = new NativeWorkbenchEnvironmentService(TestWindowConfiguration, process.execPath, 0); +export const TestEnvironmentService = new NativeWorkbenchEnvironmentService(TestWindowConfiguration, process.execPath); export class TestTextFileService extends NativeTextFileService { private resolveTextContentError!: FileOperationError | null; @@ -61,7 +65,7 @@ export class TestTextFileService extends NativeTextFileService { @ILifecycleService lifecycleService: ILifecycleService, @IInstantiationService instantiationService: IInstantiationService, @IModelService modelService: IModelService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, @IDialogService dialogService: IDialogService, @IFileDialogService fileDialogService: IFileDialogService, @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, @@ -70,7 +74,8 @@ export class TestTextFileService extends NativeTextFileService { @ITextModelService textModelService: ITextModelService, @ICodeEditorService codeEditorService: ICodeEditorService, @IRemotePathService remotePathService: IRemotePathService, - @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService + @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService, + @ILogService logService: ILogService ) { super( fileService, @@ -87,7 +92,8 @@ export class TestTextFileService extends NativeTextFileService { textModelService, codeEditorService, remotePathService, - workingCopyFileService + workingCopyFileService, + logService ); } @@ -203,6 +209,7 @@ export class TestElectronService implements IElectronService { async relaunch(options?: { addArgs?: string[] | undefined; removeArgs?: string[] | undefined; } | undefined): Promise { } async reload(): Promise { } async closeWindow(): Promise { } + async closeWindowById(): Promise { } async quit(): Promise { } async openDevTools(options?: Electron.OpenDevToolsOptions | undefined): Promise { } async toggleDevTools(): Promise { } diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index eae7291b6ff..04a72cf3db6 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -24,9 +24,7 @@ import 'vs/workbench/browser/actions/navigationActions'; import 'vs/workbench/browser/actions/windowActions'; import 'vs/workbench/browser/actions/workspaceActions'; import 'vs/workbench/browser/actions/workspaceCommands'; - -import 'vs/workbench/browser/parts/quickopen/quickOpenActions'; -import 'vs/workbench/browser/parts/quickinput/quickInputActions'; +import 'vs/workbench/browser/actions/quickAccessActions'; //#endregion @@ -42,9 +40,6 @@ import 'vs/workbench/api/browser/viewsExtensionPoint'; //#region --- workbench parts -import 'vs/workbench/browser/parts/quickinput/quickInput'; -import 'vs/workbench/browser/parts/quickopen/quickOpenController'; -import 'vs/workbench/browser/parts/titlebar/titlebarPart'; import 'vs/workbench/browser/parts/editor/editorPart'; import 'vs/workbench/browser/parts/activitybar/activitybarPart'; import 'vs/workbench/browser/parts/panel/panelPart'; @@ -87,6 +82,7 @@ import 'vs/workbench/services/workingCopy/common/workingCopyService'; import 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import 'vs/workbench/services/views/browser/viewDescriptorService'; +import 'vs/workbench/services/quickinput/browser/quickInputService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; @@ -144,11 +140,14 @@ import 'vs/workbench/contrib/preferences/browser/preferences.contribution'; import 'vs/workbench/contrib/preferences/browser/keybindingsEditorContribution'; import 'vs/workbench/contrib/preferences/browser/preferencesSearch'; +// Notebook +import 'vs/workbench/contrib/notebook/browser/notebook.contribution'; + // Logs import 'vs/workbench/contrib/logs/common/logs.contribution'; -// Quick Open Handlers -import 'vs/workbench/contrib/quickopen/browser/quickopen.contribution'; +// Quickaccess +import 'vs/workbench/contrib/quickaccess/browser/quickAccess.contribution'; // Explorer import 'vs/workbench/contrib/files/browser/explorerViewlet'; @@ -164,7 +163,6 @@ import 'vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution'; // Search import 'vs/workbench/contrib/search/browser/search.contribution'; import 'vs/workbench/contrib/search/browser/searchView'; -import 'vs/workbench/contrib/search/browser/openAnythingHandler'; // Search Editor import 'vs/workbench/contrib/searchEditor/browser/searchEditor.contribution'; @@ -175,7 +173,6 @@ import 'vs/workbench/contrib/scm/browser/scmViewlet'; // Debug import 'vs/workbench/contrib/debug/browser/debug.contribution'; -import 'vs/workbench/contrib/debug/browser/debugQuickOpen'; import 'vs/workbench/contrib/debug/browser/debugEditorContribution'; import 'vs/workbench/contrib/debug/browser/breakpointEditorContribution'; import 'vs/workbench/contrib/debug/browser/callStackEditorContribution'; @@ -197,7 +194,6 @@ import 'vs/workbench/contrib/customEditor/browser/webviewEditor.contribution'; // Extensions Management import 'vs/workbench/contrib/extensions/browser/extensions.contribution'; -import 'vs/workbench/contrib/extensions/browser/extensionsQuickOpen'; import 'vs/workbench/contrib/extensions/browser/extensionsViewlet'; // Output View @@ -205,8 +201,8 @@ import 'vs/workbench/contrib/output/browser/output.contribution'; import 'vs/workbench/contrib/output/browser/outputView'; // Terminal +import 'vs/workbench/contrib/terminal/common/environmentVariable.contribution'; import 'vs/workbench/contrib/terminal/browser/terminal.contribution'; -import 'vs/workbench/contrib/terminal/browser/terminalQuickOpen'; import 'vs/workbench/contrib/terminal/browser/terminalView'; // Relauncher diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index fdf7e6ae5db..b36f4771014 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -43,14 +43,14 @@ import 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; import 'vs/workbench/services/telemetry/electron-browser/telemetryService'; import 'vs/workbench/services/configurationResolver/electron-browser/configurationResolverService'; import 'vs/workbench/services/extensionManagement/node/extensionManagementService'; -import 'vs/workbench/services/accessibility/node/accessibilityService'; +import 'vs/workbench/services/accessibility/electron-browser/accessibilityService'; import 'vs/workbench/services/remote/node/tunnelService'; import 'vs/workbench/services/backup/node/backupFileService'; import 'vs/workbench/services/url/electron-browser/urlService'; import 'vs/workbench/services/workspaces/electron-browser/workspacesService'; import 'vs/workbench/services/workspaces/electron-browser/workspaceEditingService'; +import 'vs/workbench/services/userDataSync/electron-browser/storageKeysSyncRegistryService'; import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncService'; -import 'vs/workbench/services/userDataSync/electron-browser/settingsSyncService'; import 'vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService'; import 'vs/workbench/services/authentication/electron-browser/authenticationTokenService'; import 'vs/workbench/services/authentication/browser/authenticationService'; @@ -69,8 +69,11 @@ import 'vs/workbench/services/extensionResourceLoader/electron-browser/extension import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService'; +import { TitlebarPart } from 'vs/workbench/electron-browser/parts/titlebar/titlebarPart'; +import { ITitleService } from 'vs/workbench/services/title/common/titleService'; registerSingleton(ICredentialsService, KeytarCredentialsService, true); +registerSingleton(ITitleService, TitlebarPart); //#endregion @@ -104,6 +107,9 @@ import 'vs/workbench/contrib/debug/electron-browser/extensionHostDebugService'; // Webview import 'vs/workbench/contrib/webview/electron-browser/webview.contribution'; +// Notebook +import 'vs/workbench/contrib/notebook/electron-browser/notebook.contribution'; + // Extensions Management import 'vs/workbench/contrib/extensions/electron-browser/extensions.contribution'; diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 9f18e7bf59d..f6c014a91f2 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -64,27 +64,6 @@ interface IShowCandidate { (host: string, port: number, detail: string): Thenable; } -interface IApplicationLink { - - /** - * A link that is opened in the OS. If you want to open VSCode it must - * follow our expected structure of links: - * - * ://// - * - * For example: - * - * vscode://vscode-remote/vsonline+2005711d/home/vsonline/workspace for - * a remote folder in VSO or vscode://file/home/workspace for a local folder. - */ - uri: URI; - - /** - * A label for the application link to display. - */ - label: string; -} - interface ICommand { /** @@ -94,9 +73,13 @@ interface ICommand { id: string, /** - * A function that is being executed with any arguments passed over. + * A function that is being executed with any arguments passed over. The + * return type will be send back to the caller. + * + * Note: arguments and return type should be serializable so that they can + * be exchanged across processes boundaries. */ - handler: (...args: any[]) => void; + handler: (...args: any[]) => unknown; } interface IWorkbenchConstructionOptions { @@ -186,18 +169,6 @@ interface IWorkbenchConstructionOptions { */ readonly resolveCommonTelemetryProperties?: ICommontTelemetryPropertiesResolver; - /** - * Provide entries for the "Open in Desktop" feature. - * - * Depending on the returned elements the behaviour is: - * - no elements: there will not be a "Open in Desktop" affordance - * - 1 element: there will be a "Open in Desktop" affordance that opens on click - * and it will use the label provided by the link - * - N elements: there will be a "Open in Desktop" affordance that opens - * a picker on click to select which application to open. - */ - readonly applicationLinks?: readonly IApplicationLink[]; - /** * A set of optional commands that should be registered with the commands * registry. @@ -230,8 +201,17 @@ interface IWorkbenchConstructionOptions { * @param domElement the container to create the workbench in * @param options for setting up the workbench */ +let created = false; async function create(domElement: HTMLElement, options: IWorkbenchConstructionOptions): Promise { + // Assert that the workbench is not created more than once. We currently + // do not support this and require a full context switch to clean-up. + if (created) { + throw new Error('Unable to create the VSCode workbench more than once.'); + } else { + created = true; + } + // Startup workbench await main(domElement, options); @@ -241,7 +221,7 @@ async function create(domElement: HTMLElement, options: IWorkbenchConstructionOp CommandsRegistry.registerCommand(command.id, (accessor, ...args) => { // we currently only pass on the arguments but not the accessor // to the command to reduce our exposure of internal API. - command.handler(...args); + return command.handler(...args); }); } } @@ -302,9 +282,6 @@ export { // External Uris IExternalUriResolver, - // Protocol Links - IApplicationLink, - // Commands ICommand }; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index a73f3a3e53e..b9defe4bb9e 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -62,15 +62,18 @@ import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { TunnelService } from 'vs/workbench/services/remote/common/tunnelService'; import { ILoggerService } from 'vs/platform/log/common/log'; import { FileLoggerService } from 'vs/platform/log/common/fileLogService'; -import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataSyncLogService, ISettingsSyncService, IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataSyncLogService, IUserDataAutoSyncService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { StorageKeysSyncRegistryService, IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; import { AuthenticationService, IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; +import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; -import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; import { IAuthenticationTokenService, AuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; import { UserDataAutoSyncService } from 'vs/workbench/contrib/userDataSync/browser/userDataAutoSyncService'; import { AccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; +import { ITitleService } from 'vs/workbench/services/title/common/titleService'; +import { TitlebarPart } from 'vs/workbench/browser/parts/titlebar/titlebarPart'; registerSingleton(IExtensionManagementService, ExtensionManagementService); registerSingleton(IBackupFileService, BackupFileService); @@ -81,10 +84,12 @@ registerSingleton(ILoggerService, FileLoggerService); registerSingleton(IAuthenticationService, AuthenticationService); registerSingleton(IUserDataSyncLogService, UserDataSyncLogService); registerSingleton(IUserDataSyncStoreService, UserDataSyncStoreService); +registerSingleton(IUserDataSyncBackupStoreService, UserDataSyncBackupStoreService); +registerSingleton(IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService); registerSingleton(IAuthenticationTokenService, AuthenticationTokenService); registerSingleton(IUserDataAutoSyncService, UserDataAutoSyncService); -registerSingleton(ISettingsSyncService, SettingsSynchroniser); registerSingleton(IUserDataSyncService, UserDataSyncService); +registerSingleton(ITitleService, TitlebarPart); //#endregion @@ -119,7 +124,4 @@ import 'vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.con // Issues import 'vs/workbench/contrib/issue/browser/issue.contribution'; -// Open In Desktop -import 'vs/workbench/contrib/openInDesktop/browser/openInDesktop.web.contribution'; - //#endregion diff --git a/test/automation/src/application.ts b/test/automation/src/application.ts index 20e5e0e79b3..2e09e3a5a20 100644 --- a/test/automation/src/application.ts +++ b/test/automation/src/application.ts @@ -146,7 +146,7 @@ export class Application { } // wait a bit, since focus might be stolen off widgets - // as soon as they open (e.g. quick open) + // as soon as they open (e.g. quick access) await new Promise(c => setTimeout(c, 1000)); } } diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index 34bd09b739e..cdd4b2ea49d 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -134,7 +134,7 @@ export async function spawn(options: SpawnOptions): Promise { options.workspacePath, '--skip-getting-started', '--skip-release-notes', - '--sticky-quickopen', + '--sticky-quickinput', '--disable-telemetry', '--disable-updates', '--disable-crash-reporter', diff --git a/test/automation/src/extensions.ts b/test/automation/src/extensions.ts index ec35d8d867b..01d8efd61ef 100644 --- a/test/automation/src/extensions.ts +++ b/test/automation/src/extensions.ts @@ -37,7 +37,7 @@ export class Extensions extends Viewlet { async installExtension(id: string, name: string): Promise { await this.searchForExtension(id); const ariaLabel = `${name}. Press enter for extension details.`; - await this.code.waitAndClick(`div.extensions-viewlet[id="workbench.view.extensions"] .monaco-list-row[aria-label="${ariaLabel}"] .extension li[class='action-item'] .extension-action.install`); + await this.code.waitAndClick(`div.extensions-viewlet[id="workbench.view.extensions"] .monaco-list-row[aria-label="${ariaLabel}"] .extension-list-item li[class='action-item'] .extension-action.install`); await this.code.waitForElement(`.extension-editor .monaco-action-bar .action-item:not(.disabled) .extension-action.uninstall`); } } diff --git a/test/automation/src/index.ts b/test/automation/src/index.ts index f8301728be8..1eb4c8005e3 100644 --- a/test/automation/src/index.ts +++ b/test/automation/src/index.ts @@ -16,7 +16,7 @@ export * from './logger'; export * from './peek'; export * from './problems'; export * from './quickinput'; -export * from './quickopen'; +export * from './quickaccess'; export * from './scm'; export * from './search'; export * from './settings'; diff --git a/test/automation/src/quickaccess.ts b/test/automation/src/quickaccess.ts new file mode 100644 index 00000000000..918d213050b --- /dev/null +++ b/test/automation/src/quickaccess.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. + *--------------------------------------------------------------------------------------------*/ + +import { Editors } from './editors'; +import { Code } from './code'; +import { QuickInput } from './quickinput'; + +export class QuickAccess { + + constructor(private code: Code, private editors: Editors, private quickInput: QuickInput) { } + + async openQuickAccess(value: string): Promise { + let retries = 0; + + // other parts of code might steal focus away from quickinput :( + while (retries < 5) { + if (process.platform === 'darwin') { + await this.code.dispatchKeybinding('cmd+p'); + } else { + await this.code.dispatchKeybinding('ctrl+p'); + } + + try { + await this.quickInput.waitForQuickInputOpened(10); + break; + } catch (err) { + if (++retries > 5) { + throw err; + } + + await this.code.dispatchKeybinding('escape'); + } + } + + if (value) { + await this.code.waitForSetValue(QuickInput.QUICK_INPUT_INPUT, value); + } + } + + async openFile(fileName: string): Promise { + await this.openQuickAccess(fileName); + + await this.quickInput.waitForQuickInputElements(names => names[0] === fileName); + await this.code.dispatchKeybinding('enter'); + await this.editors.waitForActiveTab(fileName); + await this.editors.waitForEditorFocus(fileName); + } + + async runCommand(commandId: string): Promise { + await this.openQuickAccess(`>${commandId}`); + + // wait for best choice to be focused + await this.code.waitForTextContent(QuickInput.QUICK_INPUT_FOCUSED_ELEMENT); + + // wait and click on best choice + await this.quickInput.selectQuickInputElement(0); + } + + async openQuickOutline(): Promise { + let retries = 0; + + while (++retries < 10) { + if (process.platform === 'darwin') { + await this.code.dispatchKeybinding('cmd+shift+o'); + } else { + await this.code.dispatchKeybinding('ctrl+shift+o'); + } + + const text = await this.code.waitForTextContent(QuickInput.QUICK_INPUT_ENTRY_LABEL_SPAN); + + if (text !== 'No symbol information for the file') { + return; + } + + await this.quickInput.closeQuickInput(); + await new Promise(c => setTimeout(c, 250)); + } + } +} diff --git a/test/automation/src/quickinput.ts b/test/automation/src/quickinput.ts index 682d14eae7c..70b37ea9e2d 100644 --- a/test/automation/src/quickinput.ts +++ b/test/automation/src/quickinput.ts @@ -9,10 +9,19 @@ export class QuickInput { static QUICK_INPUT = '.quick-input-widget'; static QUICK_INPUT_INPUT = `${QuickInput.QUICK_INPUT} .quick-input-box input`; - static QUICK_INPUT_FOCUSED_ELEMENT = `${QuickInput.QUICK_INPUT} .quick-open-tree .monaco-tree-row.focused .monaco-highlighted-label`; + static QUICK_INPUT_ROW = `${QuickInput.QUICK_INPUT} .quick-input-list .monaco-list-row`; + static QUICK_INPUT_FOCUSED_ELEMENT = `${QuickInput.QUICK_INPUT_ROW}.focused .monaco-highlighted-label`; + static QUICK_INPUT_ENTRY_LABEL = `${QuickInput.QUICK_INPUT_ROW} .label-name`; + static QUICK_INPUT_ENTRY_LABEL_SPAN = `${QuickInput.QUICK_INPUT_ROW} .monaco-highlighted-label span`; constructor(private code: Code) { } + async submit(text: string): Promise { + await this.code.waitForSetValue(QuickInput.QUICK_INPUT_INPUT, text); + await this.code.dispatchKeybinding('enter'); + await this.waitForQuickInputClosed(); + } + async closeQuickInput(): Promise { await this.code.dispatchKeybinding('escape'); await this.waitForQuickInputClosed(); @@ -22,7 +31,11 @@ export class QuickInput { await this.code.waitForActiveElement(QuickInput.QUICK_INPUT_INPUT, retryCount); } - private async waitForQuickInputClosed(): Promise { + async waitForQuickInputElements(accept: (names: string[]) => boolean): Promise { + await this.code.waitForElements(QuickInput.QUICK_INPUT_ENTRY_LABEL, false, els => accept(els.map(e => e.textContent))); + } + + async waitForQuickInputClosed(): Promise { await this.code.waitForElement(QuickInput.QUICK_INPUT, r => !!r && r.attributes.style.indexOf('display: none;') !== -1); } diff --git a/test/automation/src/quickopen.ts b/test/automation/src/quickopen.ts deleted file mode 100644 index b40afe76642..00000000000 --- a/test/automation/src/quickopen.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 { Editors } from './editors'; -import { Code } from './code'; - -export class QuickOpen { - - static QUICK_OPEN = 'div.monaco-quick-open-widget'; - static QUICK_OPEN_HIDDEN = 'div.monaco-quick-open-widget[aria-hidden="true"]'; - static QUICK_OPEN_INPUT = `${QuickOpen.QUICK_OPEN} .quick-open-input input`; - static QUICK_OPEN_FOCUSED_ELEMENT = `${QuickOpen.QUICK_OPEN} .quick-open-tree .monaco-tree-row.focused .monaco-highlighted-label`; - static QUICK_OPEN_ENTRY_SELECTOR = 'div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties .monaco-tree-row .quick-open-entry'; - static QUICK_OPEN_ENTRY_LABEL_SELECTOR = 'div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties .monaco-tree-row .quick-open-entry .label-name'; - - constructor(private code: Code, private editors: Editors) { } - - async openQuickOpen(value: string): Promise { - let retries = 0; - - // other parts of code might steal focus away from quickopen :( - while (retries < 5) { - if (process.platform === 'darwin') { - await this.code.dispatchKeybinding('cmd+p'); - } else { - await this.code.dispatchKeybinding('ctrl+p'); - } - - try { - await this.waitForQuickOpenOpened(10); - break; - } catch (err) { - if (++retries > 5) { - throw err; - } - - await this.code.dispatchKeybinding('escape'); - } - } - - if (value) { - await this.code.waitForSetValue(QuickOpen.QUICK_OPEN_INPUT, value); - } - } - - async closeQuickOpen(): Promise { - await this.code.dispatchKeybinding('escape'); - await this.waitForQuickOpenClosed(); - } - - async openFile(fileName: string): Promise { - await this.openQuickOpen(fileName); - - await this.waitForQuickOpenElements(names => names[0] === fileName); - await this.code.dispatchKeybinding('enter'); - await this.editors.waitForActiveTab(fileName); - await this.editors.waitForEditorFocus(fileName); - } - - async waitForQuickOpenOpened(retryCount?: number): Promise { - await this.code.waitForActiveElement(QuickOpen.QUICK_OPEN_INPUT, retryCount); - } - - private async waitForQuickOpenClosed(): Promise { - await this.code.waitForElement(QuickOpen.QUICK_OPEN_HIDDEN); - } - - async submit(text: string): Promise { - await this.code.waitForSetValue(QuickOpen.QUICK_OPEN_INPUT, text); - await this.code.dispatchKeybinding('enter'); - await this.waitForQuickOpenClosed(); - } - - async selectQuickOpenElement(index: number): Promise { - await this.waitForQuickOpenOpened(); - for (let from = 0; from < index; from++) { - await this.code.dispatchKeybinding('down'); - } - await this.code.dispatchKeybinding('enter'); - await this.waitForQuickOpenClosed(); - } - - async waitForQuickOpenElements(accept: (names: string[]) => boolean): Promise { - await this.code.waitForElements(QuickOpen.QUICK_OPEN_ENTRY_LABEL_SELECTOR, false, els => accept(els.map(e => e.textContent))); - } - - async runCommand(command: string): Promise { - await this.openQuickOpen(`> ${command}`); - - // wait for best choice to be focused - await this.code.waitForTextContent(QuickOpen.QUICK_OPEN_FOCUSED_ELEMENT, command); - - // wait and click on best choice - await this.code.waitAndClick(QuickOpen.QUICK_OPEN_FOCUSED_ELEMENT); - } - - async openQuickOutline(): Promise { - let retries = 0; - - while (++retries < 10) { - if (process.platform === 'darwin') { - await this.code.dispatchKeybinding('cmd+shift+o'); - } else { - await this.code.dispatchKeybinding('ctrl+shift+o'); - } - - const text = await this.code.waitForTextContent('div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties div.monaco-tree-row .quick-open-entry .monaco-icon-label .label-name .monaco-highlighted-label span'); - - if (text !== 'No symbol information for the file') { - return; - } - - await this.closeQuickOpen(); - await new Promise(c => setTimeout(c, 250)); - } - } -} diff --git a/test/automation/src/settings.ts b/test/automation/src/settings.ts index 537ec269d2e..87366ed5e03 100644 --- a/test/automation/src/settings.ts +++ b/test/automation/src/settings.ts @@ -8,11 +8,11 @@ import * as path from 'path'; import { Editor } from './editor'; import { Editors } from './editors'; import { Code } from './code'; -import { QuickOpen } from './quickopen'; +import { QuickAccess } from './quickaccess'; export class SettingsEditor { - constructor(private code: Code, private userDataPath: string, private editors: Editors, private editor: Editor, private quickopen: QuickOpen) { } + constructor(private code: Code, private userDataPath: string, private editors: Editors, private editor: Editor, private quickaccess: QuickAccess) { } async addUserSetting(setting: string, value: string): Promise { await this.openSettings(); @@ -32,6 +32,6 @@ export class SettingsEditor { } private async openSettings(): Promise { - await this.quickopen.runCommand('Preferences: Open Settings (JSON)'); + await this.quickaccess.runCommand('workbench.action.openSettingsJson'); } } diff --git a/test/automation/src/terminal.ts b/test/automation/src/terminal.ts index 82b7d9151e5..207f8c90b16 100644 --- a/test/automation/src/terminal.ts +++ b/test/automation/src/terminal.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Code } from './code'; -import { QuickOpen } from './quickopen'; +import { QuickAccess } from './quickaccess'; const PANEL_SELECTOR = 'div[id="workbench.panel.terminal"]'; const XTERM_SELECTOR = `${PANEL_SELECTOR} .terminal-wrapper`; @@ -12,10 +12,10 @@ const XTERM_TEXTAREA = `${XTERM_SELECTOR} textarea.xterm-helper-textarea`; export class Terminal { - constructor(private code: Code, private quickopen: QuickOpen) { } + constructor(private code: Code, private quickaccess: QuickAccess) { } async showTerminal(): Promise { - await this.quickopen.runCommand('View: Toggle Integrated Terminal'); + await this.quickaccess.runCommand('workbench.action.terminal.toggleTerminal'); await this.code.waitForActiveElement(XTERM_TEXTAREA); await this.code.waitForTerminalBuffer(XTERM_SELECTOR, lines => lines.some(line => line.length > 0)); } diff --git a/test/automation/src/workbench.ts b/test/automation/src/workbench.ts index 212f265ca66..e2373948924 100644 --- a/test/automation/src/workbench.ts +++ b/test/automation/src/workbench.ts @@ -5,7 +5,7 @@ import { Explorer } from './explorer'; import { ActivityBar } from './activityBar'; -import { QuickOpen } from './quickopen'; +import { QuickAccess } from './quickaccess'; import { QuickInput } from './quickinput'; import { Extensions } from './extensions'; import { Search } from './search'; @@ -26,7 +26,7 @@ export interface Commands { export class Workbench { - readonly quickopen: QuickOpen; + readonly quickaccess: QuickAccess; readonly quickinput: QuickInput; readonly editors: Editors; readonly explorer: Explorer; @@ -44,19 +44,19 @@ export class Workbench { constructor(code: Code, userDataPath: string) { this.editors = new Editors(code); - this.quickopen = new QuickOpen(code, this.editors); this.quickinput = new QuickInput(code); + this.quickaccess = new QuickAccess(code, this.editors, this.quickinput); this.explorer = new Explorer(code, this.editors); this.activitybar = new ActivityBar(code); this.search = new Search(code); this.extensions = new Extensions(code); - this.editor = new Editor(code, this.quickopen); + this.editor = new Editor(code, this.quickaccess); this.scm = new SCM(code); - this.debug = new Debug(code, this.quickopen, this.editors, this.editor); + this.debug = new Debug(code, this.quickaccess, this.editors, this.editor); this.statusbar = new StatusBar(code); this.problems = new Problems(code); - this.settingsEditor = new SettingsEditor(code, userDataPath, this.editors, this.editor, this.quickopen); + this.settingsEditor = new SettingsEditor(code, userDataPath, this.editors, this.editor, this.quickaccess); this.keybindingsEditor = new KeybindingsEditor(code); - this.terminal = new Terminal(code, this.quickopen); + this.terminal = new Terminal(code, this.quickaccess); } } diff --git a/test/integration/browser/src/index.ts b/test/integration/browser/src/index.ts index 7972dd76521..99a66c318f8 100644 --- a/test/integration/browser/src/index.ts +++ b/test/integration/browser/src/index.ts @@ -3,12 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'path'; -import * as cp from 'child_process'; -import * as playwright from 'playwright'; -import * as url from 'url'; -import * as tmp from 'tmp'; -import * as rimraf from 'rimraf'; +import * as path from 'path'; +import * as cp from 'child_process'; +import * as playwright from 'playwright'; +import * as url from 'url'; +import * as tmp from 'tmp'; +import * as rimraf from 'rimraf'; import { URI } from 'vscode-uri'; import * as kill from 'tree-kill'; import * as optimistLib from 'optimist'; @@ -44,7 +44,7 @@ async function runTestsInBrowser(browserType: 'chromium' | 'firefox' | 'webkit', const testFilesUri = url.format({ pathname: URI.file(path.resolve(optimist.argv.extensionTestsPath)).path, protocol, host, slashes: true }); const folderParam = testWorkspaceUri; - const payloadParam = `[["extensionDevelopmentPath","${testExtensionUri}"],["extensionTestsPath","${testFilesUri}"]]`; + const payloadParam = `[["extensionDevelopmentPath","${testExtensionUri}"],["extensionTestsPath","${testFilesUri}"],["enableProposedApi",""]]`; await page.goto(`${endpoint.href}&folder=${folderParam}&payload=${payloadParam}`); @@ -103,7 +103,7 @@ async function launchServer(): Promise<{ endpoint: url.UrlWithStringQuery, serve let serverProcess = cp.spawn( serverLocation, - ['--browser', 'none', '--driver', 'web'], + ['--browser', 'none', '--driver', 'web', '--enable-proposed-api'], { env } ); diff --git a/test/smoke/README.md b/test/smoke/README.md index 7bec799f89d..8f1d567c3e6 100644 --- a/test/smoke/README.md +++ b/test/smoke/README.md @@ -7,7 +7,6 @@ Make sure you are on **Node v12.x**. ```bash # Install Dependencies and Compile yarn --cwd test/smoke -yarn --cwd test/automation # Dev (Electron) yarn smoketest @@ -78,6 +77,6 @@ yarn watch - Beware of **focus**. **Never** depend on DOM elements having focus using `.focused` classes or `:focus` pseudo-classes, since they will lose that state as soon as another window appears on top of the running VS Code window. A safe approach which avoids this problem is to use the `waitForActiveElement` API. Many tests use this whenever they need to wait for a specific element to _have focus_. -- Beware of **timing**. You need to read from or write to the DOM... but is it the right time to do that? Can you 100% guarantee that `input` box will be visible at that point in time? Or are you just hoping that it will be so? Hope is your worst enemy in UI tests. Example: just because you triggered Quick Open with `F1`, it doesn't mean that it's open and you can just start typing; you must first wait for the input element to be in the DOM as well as be the current active element. +- Beware of **timing**. You need to read from or write to the DOM... but is it the right time to do that? Can you 100% guarantee that `input` box will be visible at that point in time? Or are you just hoping that it will be so? Hope is your worst enemy in UI tests. Example: just because you triggered Quick Access with `F1`, it doesn't mean that it's open and you can just start typing; you must first wait for the input element to be in the DOM as well as be the current active element. - Beware of **waiting**. **Never** wait longer than a couple of seconds for anything, unless it's justified. Think of it as a human using Code. Would a human take 10 minutes to run through the Search viewlet smoke test? Then, the computer should even be faster. **Don't** use `setTimeout` just because. Think about what you should wait for in the DOM to be ready and wait for that instead. diff --git a/test/smoke/src/areas/editor/editor.test.ts b/test/smoke/src/areas/editor/editor.test.ts index 8832f070225..111569e8fac 100644 --- a/test/smoke/src/areas/editor/editor.test.ts +++ b/test/smoke/src/areas/editor/editor.test.ts @@ -9,14 +9,14 @@ export function setup() { describe('Editor', () => { it('shows correct quick outline', async function () { const app = this.app as Application; - await app.workbench.quickopen.openFile('www'); + await app.workbench.quickaccess.openFile('www'); - await app.workbench.quickopen.openQuickOutline(); - await app.workbench.quickopen.waitForQuickOpenElements(names => names.length >= 6); + await app.workbench.quickaccess.openQuickOutline(); + await app.workbench.quickinput.waitForQuickInputElements(names => names.length >= 6); }); // it('folds/unfolds the code correctly', async function () { - // await app.workbench.quickopen.openFile('www'); + // await app.workbench.quickaccess.openFile('www'); // // Fold // await app.workbench.editor.foldAtLine(3); diff --git a/test/smoke/src/areas/extensions/extensions.test.ts b/test/smoke/src/areas/extensions/extensions.test.ts index bf43a5013fd..5d729a2c2cf 100644 --- a/test/smoke/src/areas/extensions/extensions.test.ts +++ b/test/smoke/src/areas/extensions/extensions.test.ts @@ -24,7 +24,7 @@ export function setup() { if (app.remote) { await app.reload(); } - await app.workbench.quickopen.runCommand('Smoke Test Check'); + await app.workbench.quickaccess.runCommand('Smoke Test Check'); await app.workbench.statusbar.waitForStatusbarText('smoke test', 'VS Code Smoke Test Check'); }); }); diff --git a/test/smoke/src/areas/languages/css.test.ts b/test/smoke/src/areas/languages/languages.test.ts similarity index 73% rename from test/smoke/src/areas/languages/css.test.ts rename to test/smoke/src/areas/languages/languages.test.ts index 02daa15c7b9..6d7a7d1f039 100644 --- a/test/smoke/src/areas/languages/css.test.ts +++ b/test/smoke/src/areas/languages/languages.test.ts @@ -3,21 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Application, ProblemSeverity, Problems } from '../../../../automation'; +import { Application, ProblemSeverity, Problems } from '../../../../automation/out'; export function setup() { - describe('Languages - CSS', () => { + describe('Language Features', () => { it('verifies quick outline', async function () { const app = this.app as Application; - await app.workbench.quickopen.openFile('style.css'); + await app.workbench.quickaccess.openFile('style.css'); - await app.workbench.quickopen.openQuickOutline(); - await app.workbench.quickopen.waitForQuickOpenElements(names => names.length === 2); + await app.workbench.quickaccess.openQuickOutline(); + await app.workbench.quickinput.waitForQuickInputElements(names => names.length === 2); }); - it('verifies warnings for the empty rule', async function () { + it('verifies problems view', async function () { const app = this.app as Application; - await app.workbench.quickopen.openFile('style.css'); + await app.workbench.quickaccess.openFile('style.css'); await app.workbench.editor.waitForTypeInEditor('style.css', '.foo{}'); await app.code.waitForElement(Problems.getSelectorInEditor(ProblemSeverity.WARNING)); @@ -27,10 +27,10 @@ export function setup() { await app.workbench.problems.hideProblemsView(); }); - it('verifies that warning becomes an error once setting changed', async function () { + it('verifies settings', async function () { const app = this.app as Application; await app.workbench.settingsEditor.addUserSetting('css.lint.emptyRules', '"error"'); - await app.workbench.quickopen.openFile('style.css'); + await app.workbench.quickaccess.openFile('style.css'); await app.code.waitForElement(Problems.getSelectorInEditor(ProblemSeverity.ERROR)); diff --git a/test/smoke/src/areas/multiroot/multiroot.test.ts b/test/smoke/src/areas/multiroot/multiroot.test.ts index 0a1090a3ff5..604d0f5a2df 100644 --- a/test/smoke/src/areas/multiroot/multiroot.test.ts +++ b/test/smoke/src/areas/multiroot/multiroot.test.ts @@ -45,10 +45,10 @@ export function setup() { it('shows results from all folders', async function () { const app = this.app as Application; - await app.workbench.quickopen.openQuickOpen('*.*'); + await app.workbench.quickaccess.openQuickAccess('*.*'); - await app.workbench.quickopen.waitForQuickOpenElements(names => names.length === 6); - await app.workbench.quickopen.closeQuickOpen(); + await app.workbench.quickinput.waitForQuickInputElements(names => names.length === 6); + await app.workbench.quickinput.closeQuickInput(); }); it('shows workspace name in title', async function () { diff --git a/test/smoke/src/areas/preferences/preferences.test.ts b/test/smoke/src/areas/preferences/preferences.test.ts index 721f46a7de6..1ea98dc7481 100644 --- a/test/smoke/src/areas/preferences/preferences.test.ts +++ b/test/smoke/src/areas/preferences/preferences.test.ts @@ -10,7 +10,7 @@ export function setup() { it('turns off editor line numbers and verifies the live change', async function () { const app = this.app as Application; - await app.workbench.quickopen.openFile('app.js'); + await app.workbench.quickaccess.openFile('app.js'); await app.code.waitForElements('.line-numbers', false, elements => !!elements.length); await app.workbench.settingsEditor.addUserSetting('editor.lineNumbers', '"off"'); @@ -31,6 +31,9 @@ export function setup() { after(async function () { const app = this.app as Application; await app.workbench.settingsEditor.clearUserSettings(); + + // Wait for settings to be applied, which will happen after the settings file is empty + await app.workbench.activitybar.waitForActivityBar(ActivityBarPosition.LEFT); }); }); } diff --git a/test/smoke/src/areas/search/search.test.ts b/test/smoke/src/areas/search/search.test.ts index 68c9e5f2854..e724b8c143d 100644 --- a/test/smoke/src/areas/search/search.test.ts +++ b/test/smoke/src/areas/search/search.test.ts @@ -10,8 +10,8 @@ export function setup() { describe('Search', () => { after(function () { const app = this.app as Application; - cp.execSync('git checkout .', { cwd: app.workspacePathOrFolder }); - cp.execSync('git reset --hard origin/master', { cwd: app.workspacePathOrFolder }); + cp.execSync('git checkout . --quiet', { cwd: app.workspacePathOrFolder }); + cp.execSync('git reset --hard origin/master --quiet', { cwd: app.workspacePathOrFolder }); }); it('searches for body & checks for correct result number', async function () { @@ -57,8 +57,8 @@ export function setup() { }); }); - describe('Quick Open', () => { - it('quick open search produces correct result', async function () { + describe('Quick Access', () => { + it('quick access search produces correct result', async function () { const app = this.app as Application; const expectedNames = [ '.eslintrc.json', @@ -70,12 +70,12 @@ export function setup() { 'jsconfig.json' ]; - await app.workbench.quickopen.openQuickOpen('.js'); - await app.workbench.quickopen.waitForQuickOpenElements(names => expectedNames.every(n => names.some(m => n === m))); + await app.workbench.quickaccess.openQuickAccess('.js'); + await app.workbench.quickinput.waitForQuickInputElements(names => expectedNames.every(n => names.some(m => n === m))); await app.code.dispatchKeybinding('escape'); }); - it('quick open respects fuzzy matching', async function () { + it('quick access respects fuzzy matching', async function () { const app = this.app as Application; const expectedNames = [ 'tasks.json', @@ -83,8 +83,8 @@ export function setup() { 'package.json' ]; - await app.workbench.quickopen.openQuickOpen('a.s'); - await app.workbench.quickopen.waitForQuickOpenElements(names => expectedNames.every(n => names.some(m => n === m))); + await app.workbench.quickaccess.openQuickAccess('a.s'); + await app.workbench.quickinput.waitForQuickInputElements(names => expectedNames.every(n => names.some(m => n === m))); await app.code.dispatchKeybinding('escape'); }); }); diff --git a/test/smoke/src/areas/statusbar/statusbar.test.ts b/test/smoke/src/areas/statusbar/statusbar.test.ts index 34ea9661154..4cbf56526b4 100644 --- a/test/smoke/src/areas/statusbar/statusbar.test.ts +++ b/test/smoke/src/areas/statusbar/statusbar.test.ts @@ -17,7 +17,7 @@ export function setup(isWeb) { await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.SYNC_STATUS); await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.PROBLEMS_STATUS); - await app.workbench.quickopen.openFile('app.js'); + await app.workbench.quickaccess.openFile('app.js'); if (!isWeb) { // Encoding picker currently hidden in web (only UTF-8 supported) await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.ENCODING_STATUS); @@ -28,14 +28,14 @@ export function setup(isWeb) { await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.SELECTION_STATUS); }); - it(`verifies that 'quick open' opens when clicking on status bar elements`, async function () { + it(`verifies that 'quick input' opens when clicking on status bar elements`, async function () { const app = this.app as Application; await app.workbench.statusbar.clickOn(StatusBarElement.BRANCH_STATUS); await app.workbench.quickinput.waitForQuickInputOpened(); await app.workbench.quickinput.closeQuickInput(); - await app.workbench.quickopen.openFile('app.js'); + await app.workbench.quickaccess.openFile('app.js'); await app.workbench.statusbar.clickOn(StatusBarElement.INDENTATION_STATUS); await app.workbench.quickinput.waitForQuickInputOpened(); await app.workbench.quickinput.closeQuickInput(); @@ -74,19 +74,19 @@ export function setup(isWeb) { it(`checks if 'Go to Line' works if called from the status bar`, async function () { const app = this.app as Application; - await app.workbench.quickopen.openFile('app.js'); + await app.workbench.quickaccess.openFile('app.js'); await app.workbench.statusbar.clickOn(StatusBarElement.SELECTION_STATUS); - await app.workbench.quickopen.waitForQuickOpenOpened(); + await app.workbench.quickinput.waitForQuickInputOpened(); - await app.workbench.quickopen.submit(':15'); + await app.workbench.quickinput.submit(':15'); await app.workbench.editor.waitForHighlightingLine('app.js', 15); }); it(`verifies if changing EOL is reflected in the status bar`, async function () { const app = this.app as Application; - await app.workbench.quickopen.openFile('app.js'); + await app.workbench.quickaccess.openFile('app.js'); await app.workbench.statusbar.clickOn(StatusBarElement.EOL_STATUS); await app.workbench.quickinput.waitForQuickInputOpened(); diff --git a/test/smoke/src/areas/workbench/data-loss.test.ts b/test/smoke/src/areas/workbench/data-loss.test.ts index 07ca7f83886..d225304b569 100644 --- a/test/smoke/src/areas/workbench/data-loss.test.ts +++ b/test/smoke/src/areas/workbench/data-loss.test.ts @@ -17,7 +17,7 @@ export function setup() { const readmeMd = 'readme.md'; const textToType = 'Hello, Code'; - await app.workbench.quickopen.openFile(readmeMd); + await app.workbench.quickaccess.openFile(readmeMd); await app.workbench.editor.waitForTypeInEditor(readmeMd, textToType); await app.reload(); diff --git a/test/smoke/src/areas/workbench/data-migration.test.ts b/test/smoke/src/areas/workbench/data-migration.test.ts index 659fd62952a..75bd46fee46 100644 --- a/test/smoke/src/areas/workbench/data-migration.test.ts +++ b/test/smoke/src/areas/workbench/data-migration.test.ts @@ -24,11 +24,11 @@ export function setup(stableCodePath: string, testDataPath: string) { await stableApp!.start(); // Open 3 editors and pin 2 of them - await stableApp.workbench.quickopen.openFile('www'); - await stableApp.workbench.quickopen.runCommand('View: Keep Editor'); + await stableApp.workbench.quickaccess.openFile('www'); + await stableApp.workbench.quickaccess.runCommand('View: Keep Editor'); - await stableApp.workbench.quickopen.openFile('app.js'); - await stableApp.workbench.quickopen.runCommand('View: Keep Editor'); + await stableApp.workbench.quickaccess.openFile('app.js'); + await stableApp.workbench.quickaccess.runCommand('View: Keep Editor'); await stableApp.workbench.editors.newUntitledFile(); @@ -70,7 +70,7 @@ export function setup(stableCodePath: string, testDataPath: string) { const readmeMd = 'readme.md'; const textToType = 'Hello, Code'; - await stableApp.workbench.quickopen.openFile(readmeMd); + await stableApp.workbench.quickaccess.openFile(readmeMd); await stableApp.workbench.editor.waitForTypeInEditor(readmeMd, textToType); await stableApp.stop(); diff --git a/test/smoke/src/areas/workbench/localization.test.ts b/test/smoke/src/areas/workbench/localization.test.ts index 07e9199080e..4e15358eeb0 100644 --- a/test/smoke/src/areas/workbench/localization.test.ts +++ b/test/smoke/src/areas/workbench/localization.test.ts @@ -36,11 +36,12 @@ export function setup() { await app.workbench.scm.openSCMViewlet(); await app.workbench.scm.waitForTitle(title => /quellcodeverwaltung/i.test(title)); - await app.workbench.debug.openDebugViewlet(); - await app.workbench.debug.waitForTitle(title => /starten/i.test(title)); + // See https://github.com/microsoft/vscode/issues/93462 + // await app.workbench.debug.openDebugViewlet(); + // await app.workbench.debug.waitForTitle(title => /starten/i.test(title)); - await app.workbench.extensions.openExtensionsViewlet(); - await app.workbench.extensions.waitForTitle(title => /extensions/i.test(title)); + // await app.workbench.extensions.openExtensionsViewlet(); + // await app.workbench.extensions.waitForTitle(title => /extensions/i.test(title)); }); }); } diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index f4d51dba717..5a1dd9f11cc 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -25,7 +25,7 @@ import { setup as setupDataMigrationTests } from './areas/workbench/data-migrati import { setup as setupDataLossTests } from './areas/workbench/data-loss.test'; import { setup as setupDataPreferencesTests } from './areas/preferences/preferences.test'; import { setup as setupDataSearchTests } from './areas/search/search.test'; -import { setup as setupDataLanguagesTests } from './areas/languages/css.test'; +import { setup as setupDataLanguagesTests } from './areas/languages/languages.test'; import { setup as setupDataEditorTests } from './areas/editor/editor.test'; import { setup as setupDataStatusbarTests } from './areas/statusbar/statusbar.test'; import { setup as setupDataExtensionTests } from './areas/extensions/extensions.test'; diff --git a/test/smoke/yarn.lock b/test/smoke/yarn.lock index 8649c9859c9..319fb686589 100644 --- a/test/smoke/yarn.lock +++ b/test/smoke/yarn.lock @@ -2,11 +2,17 @@ # yarn lockfile v1 +"@types/events@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" + integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== + "@types/glob@*": - version "5.0.33" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-5.0.33.tgz#3dff7c6ce09d65abe919c7961dc3dee016f36ad7" - integrity sha512-BcD4yyWz+qmCggaYMSFF0Xn7GkO6tgwm3Fh9Gxk/kQmEU3Z7flQTnVlMyKBUNvXXNTCCyjqK4XT4/2hLd1gQ2A== + version "7.1.1" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" + integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w== dependencies: + "@types/events" "*" "@types/minimatch" "*" "@types/node" "*" @@ -16,9 +22,9 @@ integrity sha1-0q4smHTsiCngPwC6pGNbQinOjPE= "@types/minimatch@*": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.1.tgz#b683eb60be358304ef146f5775db4c0e3696a550" - integrity sha512-rUO/jz10KRSyA9SHoCWQ8WX9BICyj5jZYu1/ucKEJKb4KzLZCKMURdYbadP157Q6Zl1x0vHsrU+Z/O0XlhYQDw== + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" + integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== "@types/mkdirp@0.5.1": version "0.5.1" @@ -40,14 +46,14 @@ "@types/node" "*" "@types/node@*": - version "8.0.51" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.51.tgz#b31d716fb8d58eeb95c068a039b9b6292817d5fb" - integrity sha512-El3+WJk2D/ppWNd2X05aiP5l2k4EwF7KwheknQZls+I26eSICoWRhRIJ56jGgw2dqNGQ5LtNajmBU2ajS28EvQ== + version "13.11.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.0.tgz#390ea202539c61c8fa6ba4428b57e05bc36dc47b" + integrity sha512-uM4mnmsIIPK/yeO+42F2RQhGUIs39K2RFmugcJANppXe6J1nvH87PvzPZYpza7Xhhs8Yn9yIAVdLZ84z61+0xQ== "@types/node@^12.11.7": - version "12.11.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a" - integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA== + version "12.12.34" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.34.tgz#0a5d6ae5d22612f0cf5f10320e1fc5d2a745dcb8" + integrity sha512-BneGN0J9ke24lBRn44hVHNeDlrXRYF+VRp0HbSUNnEZahXGAysHZIqnf/hER6aabdBgzM4YOV4jrR8gj4Zfi0g== "@types/rimraf@2.0.2": version "2.0.2" @@ -57,26 +63,11 @@ "@types/glob" "*" "@types/node" "*" -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== - ansi-colors@3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== -ansi-regex@^0.2.0, ansi-regex@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-0.2.1.tgz#0d8e946967a3d8143f93e24e298525fc1b2235f9" - integrity sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk= - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - ansi-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" @@ -87,11 +78,6 @@ ansi-regex@^4.1.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== -ansi-styles@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.1.0.tgz#eaecbf66cd706882760b2f4691582b8f55d7a7de" - integrity sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94= - ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -107,19 +93,6 @@ anymatch@^1.3.0: micromatch "^2.1.5" normalize-path "^2.0.0" -aproba@^1.0.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== - -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -149,21 +122,6 @@ arr-union@^3.1.0: resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= -array-filter@~0.0.0: - version "0.0.1" - resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec" - integrity sha1-fajPLiZijtcygDWB/SH2fKzS7uw= - -array-map@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662" - integrity sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI= - -array-reduce@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b" - integrity sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys= - array-unique@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" @@ -184,7 +142,7 @@ async-each@^1.0.0: resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== -atob@^2.1.1: +atob@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== @@ -220,15 +178,22 @@ binary-extensions@^1.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + bluebird@^2.9.34: version "2.11.0" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" integrity sha1-U0uQM8AiyVecVro7Plpcqvu2UOE= brace-expansion@^1.1.7: - version "1.1.8" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" - integrity sha1-wHshHHyVLsH479Uad+8NHTmQopI= + 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" @@ -283,18 +248,7 @@ camelcase@^5.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -chalk@0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.5.1.tgz#663b3a648b68b55d04690d49167aa837858f2174" - integrity sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ= - dependencies: - ansi-styles "^1.1.0" - escape-string-regexp "^1.0.0" - has-ansi "^0.1.0" - strip-ansi "^0.3.0" - supports-color "^0.2.0" - -chalk@^2.0.1: +chalk@^2.0.1, chalk@^2.4.1: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -324,11 +278,6 @@ chokidar@^1.6.0: optionalDependencies: fsevents "^1.0.0" -chownr@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" - integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g== - class-utils@^0.3.5: version "0.3.6" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" @@ -348,11 +297,6 @@ cliui@^5.0.0: strip-ansi "^5.2.0" wrap-ansi "^5.1.0" -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" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" @@ -379,9 +323,9 @@ commander@2.6.0: integrity sha1-nfflL7Kgyw+4kFjugMMQQiXzfh0= commander@^2.8.1: - version "2.11.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" - integrity sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ== + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== component-emitter@^1.2.1: version "1.3.0" @@ -394,33 +338,29 @@ concat-map@0.0.1: integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= concurrently@^3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-3.5.1.tgz#ee8b60018bbe86b02df13e5249453c6ececd2521" - integrity sha512-689HrwGw8Rbk1xtV9C4dY6TPJAvIYZbRbnKSAtfJ7tHqICFGoZ0PCWYjxfmerRyxBG0o3sbG3pe7N8vqPwIHuQ== + version "3.6.1" + resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-3.6.1.tgz#2f95baec5c4051294dfbb55b57a3b98a3e2b45ec" + integrity sha512-/+ugz+gwFSEfTGUxn0KHkY+19XPRTXR8+7oUK/HxgiN1n7FjeJmkrbSiXAJfyQ0zORgJYPaenmymwon51YXH9Q== dependencies: - chalk "0.5.1" + chalk "^2.4.1" commander "2.6.0" date-fns "^1.23.0" lodash "^4.5.1" + read-pkg "^3.0.0" rx "2.3.24" spawn-command "^0.0.2-1" supports-color "^3.2.3" tree-kill "^1.1.0" -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= - copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= core-js@^2.4.0: - version "2.6.9" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2" - integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A== + version "2.6.11" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" + integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== core-util-is@~1.0.0: version "1.0.2" @@ -450,11 +390,11 @@ crypt@~0.0.1: integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= date-fns@^1.23.0: - version "1.29.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6" - integrity sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw== + version "1.30.1" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c" + integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw== -debug@3.2.6, debug@^3.2.6: +debug@3.2.6, debug@^3.1.0: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== @@ -468,13 +408,6 @@ debug@^2.2.0, debug@^2.3.3: dependencies: ms "2.0.0" -debug@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -485,11 +418,6 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - define-properties@^1.1.2, define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -519,50 +447,40 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= - -detect-libc@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= - diff@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== dom-serializer@0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" - integrity sha1-BzxpdUbOB4DOI75KKOKT5AvDDII= + version "0.2.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" + integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== dependencies: - domelementtype "~1.1.1" - entities "~1.1.1" + domelementtype "^2.0.1" + entities "^2.0.0" -domelementtype@1, domelementtype@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" - integrity sha1-sXrtguirWeUt2cGbF1bg/BhyBMI= +domelementtype@1, domelementtype@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" + integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== -domelementtype@~1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" - integrity sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs= +domelementtype@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d" + integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ== domhandler@^2.3.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.1.tgz#892e47000a99be55bbf3774ffea0561d8879c259" - integrity sha1-iS5HAAqZvlW783dP/qBWHYh5wlk= + version "2.4.2" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" + integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== dependencies: domelementtype "1" domutils@^1.5.1: - version "1.6.2" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.6.2.tgz#1958cc0b4c9426e9ed367fb1c8e854891b0fa3ff" - integrity sha1-GVjMC0yUJuntNn+xyOhUiRsPo/8= + version "1.7.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" + integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== dependencies: dom-serializer "0" domelementtype "1" @@ -577,37 +495,50 @@ emoji-regex@^7.0.1: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== -entities@^1.1.1, entities@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" - integrity sha1-blwtClYhtdra7O+AuQ7ftc13cvA= +entities@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" + integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== -es-abstract@^1.5.1: - version "1.14.2" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.14.2.tgz#7ce108fad83068c8783c3cdf62e504e084d8c497" - integrity sha512-DgoQmbpFNOofkjJtKwr87Ma5EW4Dc8fWhD0R+ndq7Oc456ivUfGOOP6oAZTTKl5/CcNMP+EN+e3/iUzgE0veZg== +entities@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" + integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== dependencies: - es-to-primitive "^1.2.0" + is-arrayish "^0.2.1" + +es-abstract@^1.17.0-next.1, es-abstract@^1.17.5: + version "1.17.5" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" + integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== + dependencies: + es-to-primitive "^1.2.1" function-bind "^1.1.1" has "^1.0.3" - has-symbols "^1.0.0" - is-callable "^1.1.4" - is-regex "^1.0.4" - object-inspect "^1.6.0" + has-symbols "^1.0.1" + is-callable "^1.1.5" + is-regex "^1.0.5" + object-inspect "^1.7.0" object-keys "^1.1.1" - string.prototype.trimleft "^2.0.0" - string.prototype.trimright "^2.0.0" + object.assign "^4.1.0" + string.prototype.trimleft "^2.1.1" + string.prototype.trimright "^2.1.1" -es-to-primitive@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377" - integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg== +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== dependencies: is-callable "^1.1.4" is-date-object "^1.0.1" is-symbol "^1.0.2" -escape-string-regexp@1.0.5, escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.5: +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= @@ -618,11 +549,11 @@ esprima@^4.0.0: integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== exec-sh@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.1.tgz#163b98a6e89e6b65b47c2a28d215bc1f63989c38" - integrity sha512-aLt95pexaugVtQerpmE51+4QfWrNc304uez7jvj6fWnN8GeEHpttB8F36n8N7uVhUMbH/1enbxQ9HImZ4w/9qg== + version "0.2.2" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.2.tgz#2a5e7ffcbd7d0ba2755bdecb16e5a427dfbdec36" + integrity sha512-FIUCJz1RbuS0FKTdaAafAByGS0CPvU3R0MeHxgtl+djzCc//F8HakL8GzmVNZanasTbTAY/3DRFA0KpVqj/eAw== dependencies: - merge "^1.1.3" + merge "^1.2.0" expand-brackets@^0.1.4: version "0.1.5" @@ -687,6 +618,11 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + filename-regex@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" @@ -751,45 +687,24 @@ fragment-cache@^0.2.1: dependencies: map-cache "^0.2.2" -fs-minipass@^1.2.5: - version "1.2.6" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07" - integrity sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ== - dependencies: - minipass "^2.2.1" - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= fsevents@^1.0.0: - version "1.2.9" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f" - integrity sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw== + version "1.2.12" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.12.tgz#db7e0d8ec3b0b45724fd4d83d43554a8f1f0de5c" + integrity sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q== dependencies: + bindings "^1.5.0" nan "^2.12.1" - node-pre-gyp "^0.12.0" function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - get-caller-file@^2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -834,10 +749,10 @@ glob@7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.5: - version "7.1.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" - integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ== +glob@^7.0.5, glob@^7.1.3: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -846,23 +761,16 @@ glob@^7.0.5: once "^1.3.0" path-is-absolute "^1.0.0" -graceful-fs@^4.1.11: - version "4.2.0" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.0.tgz#8d8fdc73977cb04104721cb53666c1ca64cd328b" - integrity sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg== +graceful-fs@^4.1.11, graceful-fs@^4.1.2: + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== growl@1.10.5: version "1.10.5" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== -has-ansi@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-0.1.0.tgz#84f265aae8c0e6a88a12d7022894b7568894c62e" - integrity sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4= - dependencies: - ansi-regex "^0.2.0" - has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" @@ -873,15 +781,10 @@ has-flag@^3.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= -has-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" - integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= - -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= +has-symbols@^1.0.0, has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== has-value@^0.3.1: version "0.3.1" @@ -914,7 +817,7 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" -has@^1.0.1, has@^1.0.3: +has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== @@ -926,31 +829,22 @@ he@1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +hosted-git-info@^2.1.4: + version "2.8.8" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" + integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + htmlparser2@^3.9.2: - version "3.9.2" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338" - integrity sha1-G9+HrMoPP55T+k/M6w9LTLsAszg= + version "3.10.1" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" + integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== dependencies: - domelementtype "^1.3.0" + domelementtype "^1.3.1" domhandler "^2.3.0" domutils "^1.5.1" entities "^1.1.1" inherits "^2.0.1" - readable-stream "^2.0.2" - -iconv-lite@^0.4.4: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -ignore-walk@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" - integrity sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ== - dependencies: - minimatch "^3.0.4" + readable-stream "^3.1.1" inflight@^1.0.4: version "1.0.6" @@ -960,15 +854,10 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - -ini@~1.3.0: - version "1.3.4" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" - integrity sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4= +inherits@2, inherits@^2.0.1, inherits@^2.0.3, 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== is-accessor-descriptor@^0.1.6: version "0.1.6" @@ -984,6 +873,11 @@ is-accessor-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + is-binary-path@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" @@ -997,14 +891,14 @@ is-buffer@^1.1.5, is-buffer@~1.1.1: integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== is-buffer@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725" - integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw== + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623" + integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A== -is-callable@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" - integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== +is-callable@^1.1.4, is-callable@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" + integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== is-data-descriptor@^0.1.4: version "0.1.4" @@ -1021,9 +915,9 @@ is-data-descriptor@^1.0.0: kind-of "^6.0.0" is-date-object@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" - integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY= + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" + integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== is-descriptor@^0.1.0: version "0.1.6" @@ -1072,13 +966,6 @@ is-extglob@^1.0.0: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" integrity sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA= -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" @@ -1127,19 +1014,19 @@ is-primitive@^2.0.0: resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU= -is-regex@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" - integrity sha1-VRdIm1RwkbCTDglWVM7SXul+lJE= +is-regex@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" + integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== dependencies: - has "^1.0.1" + has "^1.0.3" is-symbol@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" - integrity sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw== + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== dependencies: - has-symbols "^1.0.0" + has-symbols "^1.0.1" is-windows@^1.0.2: version "1.0.2" @@ -1176,10 +1063,10 @@ js-yaml@3.13.1: argparse "^1.0.7" esprima "^4.0.0" -jsonify@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" - integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" @@ -1201,9 +1088,19 @@ kind-of@^5.0.0: integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" - integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +load-json-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" + integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= + dependencies: + graceful-fs "^4.1.2" + parse-json "^4.0.0" + pify "^3.0.0" + strip-bom "^3.0.0" locate-path@^3.0.0: version "3.0.0" @@ -1213,21 +1110,11 @@ locate-path@^3.0.0: p-locate "^3.0.0" path-exists "^3.0.0" -lodash@^4.16.4: - version "4.17.10" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" - integrity sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg== - -lodash@^4.17.15: +lodash@^4.16.4, lodash@^4.17.15, lodash@^4.5.1: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== -lodash@^4.5.1: - version "4.17.5" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" - integrity sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw== - log-symbols@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" @@ -1261,10 +1148,10 @@ md5@^2.1.0: crypt "~0.0.1" is-buffer "~1.1.1" -merge@^1.1.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" - integrity sha1-dTHjnUlJwoGma4xabgJl6LBYlNo= +merge@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145" + integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ== micromatch@^2.1.5: version "2.3.11" @@ -1311,30 +1198,10 @@ minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= - -minimist@^1.1.0, minimist@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= - -minipass@^2.2.1, minipass@^2.3.5: - version "2.3.5" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" - integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA== - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - -minizlib@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" - integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA== - dependencies: - minipass "^2.2.1" +minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== mixin-deep@^1.2.0: version "1.3.2" @@ -1344,17 +1211,17 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@0.5.1, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= +mkdirp@0.5.4, mkdirp@^0.5.1, mkdirp@~0.5.1: + version "0.5.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.4.tgz#fd01504a6797ec5c9be81ff43d204961ed64a512" + integrity sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw== dependencies: - minimist "0.0.8" + minimist "^1.2.5" mocha-junit-reporter@^1.17.0: - version "1.17.0" - resolved "https://registry.yarnpkg.com/mocha-junit-reporter/-/mocha-junit-reporter-1.17.0.tgz#2e5149ed40fc5d2e3ca71e42db5ab1fec9c6d85c" - integrity sha1-LlFJ7UD8XS48px5C21qx/snG2Fw= + version "1.23.3" + resolved "https://registry.yarnpkg.com/mocha-junit-reporter/-/mocha-junit-reporter-1.23.3.tgz#941e219dd759ed732f8641e165918aa8b167c981" + integrity sha512-ed8LqbRj1RxZfjt/oC9t12sfrWsjZ3gNnbhV1nuj9R/Jb5/P3Xb4duv2eCfCDMYH+fEu0mqca7m4wsiVjsxsvA== dependencies: debug "^2.2.0" md5 "^2.1.0" @@ -1371,9 +1238,9 @@ mocha-multi-reporters@^1.1.7: lodash "^4.16.4" mocha@^6.1.4: - version "6.2.1" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-6.2.1.tgz#da941c99437da9bac412097859ff99543969f94c" - integrity sha512-VCcWkLHwk79NYQc8cxhkmI8IigTIhsCwZ6RTxQsqK6go4UvEhzJkYuHm8B2YtlSxcYq2fY+ucr4JBwoD6ci80A== + version "6.2.3" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-6.2.3.tgz#e648432181d8b99393410212664450a4c1e31912" + integrity sha512-0R/3FvjIGH3eEuG17ccFPk117XL2rWxatr81a57D+r/x2uTYZRbdZ4oVidEUMh2W2TJDa7MdAb12Lm2/qrKajg== dependencies: ansi-colors "3.2.3" browser-stdout "1.3.1" @@ -1387,7 +1254,7 @@ mocha@^6.1.4: js-yaml "3.13.1" log-symbols "2.2.0" minimatch "3.0.4" - mkdirp "0.5.1" + mkdirp "0.5.4" ms "2.1.1" node-environment-flags "1.0.5" object.assign "4.1.0" @@ -1395,8 +1262,8 @@ mocha@^6.1.4: supports-color "6.0.0" which "1.3.1" wide-align "1.1.3" - yargs "13.3.0" - yargs-parser "13.1.1" + yargs "13.3.2" + yargs-parser "13.1.2" yargs-unparser "1.6.0" ms@2.0.0: @@ -1404,11 +1271,16 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -ms@2.1.1, ms@^2.1.1: +ms@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + nan@^2.12.1: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" @@ -1436,15 +1308,6 @@ ncp@^2.0.0: resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3" integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M= -needle@^2.2.1: - version "2.4.0" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c" - integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg== - dependencies: - debug "^3.2.6" - iconv-lite "^0.4.4" - sax "^1.2.4" - node-environment-flags@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.5.tgz#fa930275f5bf5dae188d6192b24b4c8bbac3d76a" @@ -1453,29 +1316,15 @@ node-environment-flags@1.0.5: object.getownpropertydescriptors "^2.0.3" semver "^5.7.0" -node-pre-gyp@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149" - integrity sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A== +normalize-package-data@^2.3.2: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4" - -nopt@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" - integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= - dependencies: - abbrev "1" - osenv "^0.1.4" + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" normalize-path@^2.0.0, normalize-path@^2.0.1: version "2.1.1" @@ -1484,39 +1333,6 @@ normalize-path@^2.0.0, normalize-path@^2.0.1: dependencies: remove-trailing-separator "^1.0.1" -npm-bundled@^1.0.1: - version "1.0.6" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" - integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g== - -npm-packlist@^1.1.6: - version "1.4.4" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.4.tgz#866224233850ac534b63d1a6e76050092b5d2f44" - integrity sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw== - dependencies: - ignore-walk "^3.0.1" - npm-bundled "^1.0.1" - -npmlog@^4.0.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - -object-assign@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - object-copy@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" @@ -1526,10 +1342,10 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-inspect@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b" - integrity sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ== +object-inspect@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" + integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" @@ -1543,7 +1359,7 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@4.1.0: +object.assign@4.1.0, object.assign@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== @@ -1554,12 +1370,12 @@ object.assign@4.1.0: object-keys "^1.0.11" object.getownpropertydescriptors@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" - integrity sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY= + version "2.1.0" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" + integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== dependencies: - define-properties "^1.1.2" - es-abstract "^1.5.1" + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" object.omit@^2.0.0: version "2.0.1" @@ -1583,28 +1399,15 @@ once@^1.3.0: dependencies: wrappy "1" -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - -os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: +os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= -osenv@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - p-limit@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.1.tgz#aa07a788cc3151c939b5131f63570f0dd2009537" - integrity sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg== + version "2.2.2" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" + integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ== dependencies: p-try "^2.0.0" @@ -1630,6 +1433,14 @@ parse-glob@^3.0.4: is-extglob "^1.0.0" is-glob "^2.0.0" +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + pascalcase@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" @@ -1650,6 +1461,18 @@ path-parse@^1.0.6: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== +path-type@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" + integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== + dependencies: + pify "^3.0.0" + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= + portastic@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/portastic/-/portastic-1.0.1.tgz#1c9805d43fae8f6a40cf0dbc7794091a2e9d0d2a" @@ -1669,11 +1492,6 @@ preserve@^0.2.0: resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= -process-nextick-args@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" - integrity sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M= - process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -1688,33 +1506,19 @@ randomatic@^3.0.0: kind-of "^6.0.0" math-random "^1.0.1" -rc@^1.2.7: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== +read-pkg@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" + integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" + load-json-file "^4.0.0" + normalize-package-data "^2.3.2" + path-type "^3.0.0" readable-stream@^2.0.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" - integrity sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - safe-buffer "~5.1.1" - string_decoder "~1.0.3" - util-deprecate "~1.0.1" - -readable-stream@^2.0.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" - integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== dependencies: core-util-is "~1.0.0" inherits "~2.0.3" @@ -1724,6 +1528,15 @@ readable-stream@^2.0.6: string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@^3.1.1: + 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" + readdirp@^2.0.0: version "2.2.1" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" @@ -1783,10 +1596,10 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@^1.1.7: - version "1.11.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.1.tgz#ea10d8110376982fef578df8fc30b9ac30a07a3e" - integrity sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw== +resolve@^1.1.7, resolve@^1.10.0: + version "1.15.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" + integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== dependencies: path-parse "^1.0.6" @@ -1796,23 +1609,23 @@ ret@~0.1.10: integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== rimraf@^2.6.1: - version "2.6.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" - integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w== + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== dependencies: - glob "^7.0.5" + glob "^7.1.3" rx@2.3.24: version "2.3.24" resolved "https://registry.yarnpkg.com/rx/-/rx-2.3.24.tgz#14f950a4217d7e35daa71bbcbe58eff68ea4b2b7" integrity sha1-FPlQpCF9fjXapxu8vljv9o6ksrc= -safe-buffer@^5.0.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" - integrity sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg== +safe-buffer@^5.0.1, safe-buffer@~5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== -safe-buffer@^5.1.2: +safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== @@ -1824,27 +1637,12 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sax@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -semver@^5.3.0: - version "5.7.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" - integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== - -semver@^5.7.0: +"semver@2 || 3 || 4 || 5", semver@^5.7.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -set-blocking@^2.0.0, set-blocking@~2.0.0: +set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= @@ -1860,19 +1658,9 @@ set-value@^2.0.0, set-value@^2.0.1: split-string "^3.0.1" shell-quote@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767" - integrity sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c= - dependencies: - array-filter "~0.0.0" - array-map "~0.0.0" - array-reduce "~0.0.0" - jsonify "~0.0.0" - -signal-exit@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" - integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + version "1.7.2" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" + integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== snapdragon-node@^2.0.1: version "2.1.1" @@ -1905,11 +1693,11 @@ snapdragon@^0.8.1: use "^3.1.0" source-map-resolve@^0.5.0: - version "0.5.2" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" - integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA== + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== dependencies: - atob "^2.1.1" + atob "^2.1.2" decode-uri-component "^0.2.0" resolve-url "^0.2.1" source-map-url "^0.4.0" @@ -1930,6 +1718,32 @@ spawn-command@^0.0.2-1: resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0" integrity sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A= +spdx-correct@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" + integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" + integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== + +spdx-expression-parse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" + integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.5" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" + integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== + split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" @@ -1950,15 +1764,6 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - "string-width@^1.0.2 || 2": version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" @@ -1976,28 +1781,46 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string.prototype.trimleft@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz#6cc47f0d7eb8d62b0f3701611715a3954591d634" - integrity sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw== +string.prototype.trimend@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.0.tgz#ee497fd29768646d84be2c9b819e292439614373" + integrity sha512-EEJnGqa/xNfIg05SxiPSqRS7S9qwDhYts1TSLR1BQfYUfPe1stofgGKvwERK9+9yf+PpfBMlpBaCHucXGPQfUA== dependencies: define-properties "^1.1.3" - function-bind "^1.1.1" + es-abstract "^1.17.5" -string.prototype.trimright@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz#669d164be9df9b6f7559fa8e89945b168a5a6c58" - integrity sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg== +string.prototype.trimleft@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz#4408aa2e5d6ddd0c9a80739b087fbc067c03b3cc" + integrity sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw== dependencies: define-properties "^1.1.3" - function-bind "^1.1.1" + es-abstract "^1.17.5" + string.prototype.trimstart "^1.0.0" -string_decoder@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" - integrity sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ== +string.prototype.trimright@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz#c76f1cef30f21bbad8afeb8db1511496cfb0f2a3" + integrity sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg== dependencies: - safe-buffer "~5.1.0" + define-properties "^1.1.3" + es-abstract "^1.17.5" + string.prototype.trimend "^1.0.0" + +string.prototype.trimstart@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.0.tgz#afe596a7ce9de905496919406c9734845f01a2f2" + integrity sha512-iCP8g01NFYiiBOnwG1Xc3WZLyoo+RuBymwIlWncShXDDJYWN6DbnM3odslBJdgCdRlq94B5s63NWAZlcn2CS4w== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +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" string_decoder@~1.1.1: version "1.1.1" @@ -2006,20 +1829,6 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -strip-ansi@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.3.0.tgz#25f48ea22ca79187f3174a4db8759347bb126220" - integrity sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA= - dependencies: - ansi-regex "^0.2.1" - -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - strip-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" @@ -2034,7 +1843,12 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-json-comments@2.0.1, strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + +strip-json-comments@2.0.1, strip-json-comments@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= @@ -2053,11 +1867,6 @@ supports-color@6.0.0: dependencies: has-flag "^3.0.0" -supports-color@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a" - integrity sha1-2S3iaU6z9nMjlz1649i1W0wiGQo= - supports-color@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" @@ -2072,19 +1881,6 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -tar@^4: - version "4.4.10" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.10.tgz#946b2810b9a5e0b26140cf78bea6b0b0d689eba1" - integrity sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA== - dependencies: - chownr "^1.1.1" - fs-minipass "^1.2.5" - minipass "^2.3.5" - minizlib "^1.2.1" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.3" - tmp@0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -2118,9 +1914,9 @@ to-regex@^3.0.1, to-regex@^3.0.2: safe-regex "^1.1.0" tree-kill@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.0.tgz#5846786237b4239014f05db156b643212d4c6f36" - integrity sha512-DlX6dR0lOIRDFxI0mjL9IYg6OTncLm/Zt+JiBhE5OlFcAR8yc9S7FFXU9so0oda47frdM/JFsk7UjNt9vscKcg== + version "1.2.2" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== typescript@3.7.5: version "3.7.5" @@ -2155,11 +1951,19 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== -util-deprecate@~1.0.1: +util-deprecate@^1.0.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 sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + watch@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/watch/-/watch-1.0.2.tgz#340a717bde765726fa0aa07d721e0147a551df0c" @@ -2180,7 +1984,7 @@ which@1.3.1: dependencies: isexe "^2.0.0" -wide-align@1.1.3, wide-align@^1.1.0: +wide-align@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== @@ -2211,15 +2015,10 @@ y18n@^4.0.0: resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== -yallist@^3.0.0, yallist@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" - integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== - -yargs-parser@13.1.1, yargs-parser@^13.1.1: - version "13.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0" - integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ== +yargs-parser@13.1.2, yargs-parser@^13.1.2: + version "13.1.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" + integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== dependencies: camelcase "^5.0.0" decamelize "^1.2.0" @@ -2233,10 +2032,10 @@ yargs-unparser@1.6.0: lodash "^4.17.15" yargs "^13.3.0" -yargs@13.3.0, yargs@^13.3.0: - version "13.3.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83" - integrity sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA== +yargs@13.3.2, yargs@^13.3.0: + version "13.3.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" + integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== dependencies: cliui "^5.0.0" find-up "^3.0.0" @@ -2247,4 +2046,4 @@ yargs@13.3.0, yargs@^13.3.0: string-width "^3.0.0" which-module "^2.0.0" y18n "^4.0.0" - yargs-parser "^13.1.1" + yargs-parser "^13.1.2" diff --git a/test/unit/README.md b/test/unit/README.md index f471ebb7d8e..cf338465a74 100644 --- a/test/unit/README.md +++ b/test/unit/README.md @@ -4,7 +4,7 @@ ./scripts/test.[sh|bat] -All unit tests are run inside a electron-browser environment which access to DOM and Nodejs api. This is the closest to the enviroment in which VS Code itself ships. Notes: +All unit tests are run inside a electron-browser environment which access to DOM and Nodejs api. This is the closest to the environment in which VS Code itself ships. Notes: - use the `--debug` to see an electron window with dev tools which allows for debugging - to run only a subset of tests use the `--run` or `--glob` options diff --git a/test/unit/browser/index.js b/test/unit/browser/index.js index d6e15c5e28d..aa2320aca4d 100644 --- a/test/unit/browser/index.js +++ b/test/unit/browser/index.js @@ -155,7 +155,7 @@ async function runTestsInBrowser(testModules, browserType) { }); try { - // @ts-ignore + // @ts-expect-error await page.evaluate(modules => loadAndRun(modules), testModules); } catch (err) { console.error(err); diff --git a/yarn.lock b/yarn.lock index a820c6344ac..8008be9ffe2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -239,6 +239,11 @@ resolved "https://registry.yarnpkg.com/@types/keytar/-/keytar-4.4.0.tgz#ca24e6ee6d0df10c003aafe26e93113b8faf0d8e" integrity sha512-cq/NkUUy6rpWD8n7PweNQQBpw2o0cf5v6fbkUVEpOB9VzzIvyPvSEId1/goIj+MciW2v1Lw5mRimKO01XgE9EA== +"@types/minimist@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6" + integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY= + "@types/mocha@2.2.39": version "2.2.39" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.39.tgz#f68d63db8b69c38e9558b4073525cf96c4f7a829" @@ -574,9 +579,9 @@ acorn-jsx@^5.1.0: integrity sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw== acorn@^5.0.0, acorn@^5.6.2: - version "5.7.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.1.tgz#f095829297706a7c9776958c0afc8930a9b9d9d8" - integrity sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ== + version "5.7.4" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" + integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== acorn@^6.0.2: version "6.0.7" @@ -1223,13 +1228,14 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" -bl@^1.0.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c" - integrity sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA== +bl@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.2.tgz#52b71e9088515d0606d9dd9cc7aa48dc1f98e73a" + integrity sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ== dependencies: - readable-stream "^2.3.5" - safe-buffer "^5.1.1" + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" block-stream@*: version "0.0.9" @@ -1401,19 +1407,6 @@ browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: caniuse-db "^1.0.30000639" electron-to-chromium "^1.2.7" -buffer-alloc-unsafe@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-0.1.1.tgz#ffe1f67551dd055737de253337bfe853dfab1a6a" - integrity sha1-/+H2dVHdBVc33iUzN7/oU9+rGmo= - -buffer-alloc@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.1.0.tgz#05514d33bf1656d3540c684f65b1202e90eca303" - integrity sha1-BVFNM78WVtNUDGhPZbEgLpDsowM= - dependencies: - buffer-alloc-unsafe "^0.1.0" - buffer-fill "^0.1.0" - buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" @@ -1424,11 +1417,6 @@ buffer-equal@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" integrity sha1-WWFrSYME1Var1GaWayLu2j7KX74= -buffer-fill@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-0.1.1.tgz#76d825c4d6e50e06b7a31eb520c04d08cc235071" - integrity sha512-YgBMBzdRLEfgxJIGu2wrvI2E03tMCFU1p7d1KhB4BOoMN0VxmTFjSyN5JtKt9z8Z9JajMHruI6SE25W96wNv7Q== - buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" @@ -1448,6 +1436,14 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" +buffer@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.5.0.tgz#9c3caa3d623c33dd1c7ef584b89b88bf9c9bc1ce" + integrity sha512-9FTEDjLjwoAkEwyMGDjYJQN2gfRgOKBKRfiglhvibGbpeeU/pQn1bJxQqm32OD/AIeEuHxU9roxXxg34Byp/Ww== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + buffers@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" @@ -1676,6 +1672,11 @@ chownr@^1.0.1: resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181" integrity sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE= +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + chrome-remote-interface@0.26.1: version "0.26.1" resolved "https://registry.yarnpkg.com/chrome-remote-interface/-/chrome-remote-interface-0.26.1.tgz#6c7d4479742b6d236752d716a9bc2d322d7d8ad2" @@ -2386,6 +2387,13 @@ decompress-response@^3.3.0: dependencies: mimic-response "^1.0.0" +decompress-response@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" + integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== + dependencies: + mimic-response "^2.0.0" + decompress-zip@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/decompress-zip/-/decompress-zip-0.3.0.tgz#ae3bcb7e34c65879adfe77e19c30f86602b4bdb0" @@ -2755,6 +2763,13 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0: dependencies: once "^1.4.0" +end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + enhanced-resolve@4.1.0, enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f" @@ -4642,6 +4657,11 @@ inherits@2.0.1: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= +inherits@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + ini@^1.3.4, ini@~1.3.0: version "1.3.4" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" @@ -5376,13 +5396,12 @@ just-debounce@^1.0.0: resolved "https://registry.yarnpkg.com/just-debounce/-/just-debounce-1.0.0.tgz#87fccfaeffc0b68cd19d55f6722943f929ea35ea" integrity sha1-h/zPrv/AtozRnVX2cilD+SnqNeo= -keytar@^4.11.0: - version "4.11.0" - resolved "https://registry.yarnpkg.com/keytar/-/keytar-4.11.0.tgz#891569045b287a0dabe69320e2381e059b02363f" - integrity sha512-cGn2xd4NY0yCBrU5zQ/lwIagP1UBOhUEemi6iSJU2gshN1RHkxHekSdLUji9IWNo5B1Va/iwXXWzGD2p8ziqfQ== +"keytar@github:rmacfarlane/node-keytar#334424bd26414923782f144110f4beda19168d24": + version "5.4.0" + resolved "https://codeload.github.com/rmacfarlane/node-keytar/tar.gz/334424bd26414923782f144110f4beda19168d24" dependencies: nan "2.14.0" - prebuild-install "5.3.0" + prebuild-install "5.3.3" keyv@^3.0.0: version "3.1.0" @@ -5941,6 +5960,11 @@ mimic-response@^1.0.1: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== +mimic-response@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" + integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== + minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -5976,6 +6000,11 @@ minimist@1.2.0, minimist@^1.2.0: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= +minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + minimist@~0.0.1: version "0.0.10" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" @@ -6020,6 +6049,11 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" +mkdirp-classic@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.2.tgz#54c441ce4c96cd7790e10b41a87aa51068ecab2b" + integrity sha512-ejdnDQcR75gwknmMw/tx02AuRs8jCtqFoFqDZMjiNxsu85sRIJVXDKHuLYvUUPRBUtV2FpSZa9bL1BUa3BdR2g== + mkdirp@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e" @@ -6610,7 +6644,7 @@ os-browserify@^0.3.0: resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= -os-homedir@^1.0.0, os-homedir@^1.0.1: +os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= @@ -6965,10 +6999,10 @@ pkg-dir@^3.0.0: dependencies: find-up "^3.0.0" -playwright-core@=0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-0.11.0.tgz#a2372833f6ec4e7886c4409e3da93df997aee61b" - integrity sha512-9UPP/Max65PMiZJz9DNWB3ZRWtTlYlceLFnm6JO8aU7m6Vw3gwCvuSGoC5W69H67q98jH0VPSPp546+EnkiR2g== +playwright-core@=0.12.1: + version "0.12.1" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-0.12.1.tgz#07581d1cbe84bb1e438ecdb188de3ed6d5e81ee0" + integrity sha512-NZ8Qe/kqsgAmFBxWZnUeE+MoZ04UzNI0DHOKA+I1p/5rbpaWhe1Vx5zVNa05A1iEvOtnKV1PdIEe4IPumG2y2w== dependencies: debug "^4.1.0" extract-zip "^1.6.6" @@ -6976,17 +7010,16 @@ playwright-core@=0.11.0: jpeg-js "^0.3.6" pngjs "^3.4.0" progress "^2.0.3" - proxy-from-env "^1.0.0" + proxy-from-env "^1.1.0" rimraf "^3.0.2" - uuid "^3.4.0" ws "^6.1.0" -playwright@0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-0.11.0.tgz#2abec99ea278b220bcd3902d7520ec22abc2d97e" - integrity sha512-cTJZ06OhwseMC9+D6KX1NmZXyEoaJl0o6GLkDhwmou3IFTrUFVOw7KYMBpcbJz0Rhb/de5ZPFlDTffLfEy/9lg== +playwright@0.12.1: + version "0.12.1" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-0.12.1.tgz#59445ede1aecec120091db7bc95b4e626451e0b0" + integrity sha512-icF4+I8y7A5HjhbTsa4Eqtl2fuGe3ECvW0Wrn6aRM5eL5/AqUIgIf2U/0e1S1bEsDfz1JVvClGl5Gqw4aI5H4w== dependencies: - playwright-core "=0.11.0" + playwright-core "=0.12.1" plist@^3.0.1: version "3.0.1" @@ -7324,10 +7357,10 @@ postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.5, postcss@^7.0. source-map "^0.6.1" supports-color "^6.1.0" -prebuild-install@5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.0.tgz#58b4d8344e03590990931ee088dd5401b03004c8" - integrity sha512-aaLVANlj4HgZweKttFNUVNRxDukytuIuxeK2boIMHjagNJCiVKWFsKF4tCE3ql3GbrD2tExPQ7/pwtEJcHNZeg== +prebuild-install@5.3.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.3.tgz#ef4052baac60d465f5ba6bf003c9c1de79b9da8e" + integrity sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g== dependencies: detect-libc "^1.0.3" expand-template "^2.0.3" @@ -7338,11 +7371,10 @@ prebuild-install@5.3.0: node-abi "^2.7.0" noop-logger "^0.1.1" npmlog "^4.0.1" - os-homedir "^1.0.1" - pump "^2.0.1" + pump "^3.0.0" rc "^1.2.7" - simple-get "^2.7.0" - tar-fs "^1.13.0" + simple-get "^3.0.3" + tar-fs "^2.0.0" tunnel-agent "^0.6.0" which-pm-runs "^1.0.0" @@ -7414,10 +7446,10 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= -proxy-from-env@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" - integrity sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4= +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== prr@~1.0.1: version "1.0.1" @@ -7445,14 +7477,6 @@ public-encrypt@^4.0.0: parse-asn1 "^5.0.0" randombytes "^2.0.1" -pump@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.3.tgz#5dfe8311c33bbf6fc18261f9f34702c47c08a954" - integrity sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - pump@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.2.tgz#3b3ee6512f94f0e575538c17995f9f16990a5d51" @@ -7627,7 +7651,7 @@ read@^1.0.7: dependencies: mute-stream "~0.0.4" -"readable-stream@1 || 2", readable-stream@^2.0.6, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6: +"readable-stream@1 || 2", readable-stream@^2.0.6, readable-stream@^2.3.3, readable-stream@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== @@ -7682,6 +7706,15 @@ readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable string_decoder "~1.0.3" util-deprecate "~1.0.1" +readable-stream@^3.1.1, readable-stream@^3.4.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-stream@~2.0.0: version "2.0.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" @@ -8304,12 +8337,12 @@ simple-concat@^1.0.0: resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6" integrity sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY= -simple-get@^2.7.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-2.8.1.tgz#0e22e91d4575d87620620bc91308d57a77f44b5d" - integrity sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw== +simple-get@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3" + integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA== dependencies: - decompress-response "^3.3.0" + decompress-response "^4.2.0" once "^1.3.1" simple-concat "^1.0.0" @@ -8878,28 +8911,26 @@ tapable@^1.0.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.0.0.tgz#cbb639d9002eed9c6b5975eb20598d7936f1f9f2" integrity sha512-dQRhbNQkRnaqauC7WqSJ21EEksgT0fYZX2lqXzGkpo8JNig9zGZTYoMGvyI2nWmXlE2VSVXVDu7wLVGu/mQEsg== -tar-fs@^1.13.0: - version "1.16.2" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-1.16.2.tgz#17e5239747e399f7e77344f5f53365f04af53577" - integrity sha512-LdknWjPEiZC1nOBwhv0JBzfJBGPJar08dZg2rwZe0ZTLQoRGEzgrl7vF3qUEkCHpI/wN9e7RyCuDhMsJUCLPPQ== +tar-fs@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.1.tgz#e44086c1c60d31a4f0cf893b1c4e155dabfae9e2" + integrity sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA== dependencies: - chownr "^1.0.1" - mkdirp "^0.5.1" - pump "^1.0.0" - tar-stream "^1.1.2" + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.0.0" -tar-stream@^1.1.2: - version "1.6.1" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.1.tgz#f84ef1696269d6223ca48f6e1eeede3f7e81f395" - integrity sha512-IFLM5wp3QrJODQFPm6/to3LJZrONdBY/otxcvDIQzu217zKye6yVR3hhi9lAjrC2Z+m/j5oDxMPb1qcd8cIvpA== +tar-stream@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.2.tgz#6d5ef1a7e5783a95ff70b69b97455a5968dc1325" + integrity sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q== dependencies: - bl "^1.0.0" - buffer-alloc "^1.1.0" - end-of-stream "^1.0.0" + bl "^4.0.1" + end-of-stream "^1.4.1" fs-constants "^1.0.0" - readable-stream "^2.3.0" - to-buffer "^1.1.0" - xtend "^4.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" tar@^2.2.1: version "2.2.1" @@ -9039,11 +9070,6 @@ to-arraybuffer@^1.0.0: resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= -to-buffer@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" - integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg== - to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" @@ -9220,16 +9246,16 @@ typescript-formatter@7.1.0: commandpost "^1.0.0" editorconfig "^0.15.0" -typescript@3.8.2: - version "3.8.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.2.tgz#91d6868aaead7da74f493c553aeff76c0c0b1d5a" - integrity sha512-EgOVgL/4xfVrCMbhYKUQTdF37SQn4Iw73H5BgCrF1Abdun7Kwy/QZsE/ssAy0y4LxBbvua3PIbFsbRczWWnDdQ== - typescript@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" integrity sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q= +typescript@^3.9.0-dev.20200327: + version "3.9.0-dev.20200327" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.0-dev.20200327.tgz#52179aae816587f772a0526e91143760f2bee42f" + integrity sha512-/TWD/zPvhAcN2Toqx2NBQ+oDVGVj4iqupjWcUAwL45TfcODeHpzszneABR1b/EjHbtUObtLH40vy5Z6rdVvKzg== + uc.micro@^1.0.1, uc.micro@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192" @@ -9430,11 +9456,6 @@ uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== -uuid@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - v8-compile-cache@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe" @@ -9638,15 +9659,10 @@ vsce@1.48.0: yauzl "^2.3.1" yazl "^2.2.2" -vscode-debugprotocol@1.39.0: - version "1.39.0" - resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.39.0.tgz#0c639178d0d5ea7de7903b6478b53d2bc0d77461" - integrity sha512-Wkvgtuz90vjtQBcvw9Z+BYa4dA6W+sHwHMpqvJVNmwWSuT3JZdl0XDhZNLqtMXkVF4okxtAe0MmbupPSt+gnAQ== - -vscode-minimist@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/vscode-minimist/-/vscode-minimist-1.2.2.tgz#65403f44f0c6010d259b2271d36eb5c6f4ad8aab" - integrity sha512-DXMNG2QgrXn1jOP12LzjVfvxVkzxv/0Qa27JrMBj/XP2esj+fJ/wP2T4YUH5derj73Lc96dC8F25WyfDUbTpxQ== +vscode-debugprotocol@^1.40.0: + version "1.40.0" + resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.40.0.tgz#63e1f670a6f5c4928f3f91b27b259a21c4db7861" + integrity sha512-Fwze+9qbLDPuQUhtITJSu/Vk6zIuakNM1iR2ZiZRgRaMEgBpMs2JSKaT0chrhJHCOy6/UbpsUbUBIseF6msV+g== vscode-nls-dev@^3.3.1: version "3.3.1"